吴恩达机器学习之多变量线性回归实现部分

C++实现梯度下降法

“linear_regression.h”

//多变量线性回归模型
struct elem_var2
{
	double y;
	double* x;              //用数组传入自变量数据(x[0]=1,便于之后的计算)
};
class var2_lin_reg
{
public:
	var2_lin_reg(int xnum, elem_var2* p, int size, double rate);               //初始化
	~var2_lin_reg();                                                           //析构
	void scaling();                                                            //特征缩放器(将所有特征值统一缩放至1--10)
	void scalback();                                                           //特征还原(参数缩放)
	double cost_fuction();                                                     //返回当前预测方程对应代价函数的值
	void update();                                                             //同时更新方程参数
	void find();                                                               //最小化代价函数,找到收敛点时对应的方程参数
	double* get_par(int &par_num);                                             //获得当前方程的参数
	double est_val(double* x);                                                  //使用拟合后的回归方程进行预测


private:
	int x_num;                         //自变量个数
	elem_var2* tran_set;               //训练集(读入时将x【0】赋值为1)
	int setsize;                       //训练集数据量
	double* par;                       //参数数组(大小为实际x_num+1):h(x)=par0*x0(赋值为1)+par1*x1+par2+x^2+...+par(x_num)*x^x_num
	double learn_rate;                 //学习速率
	double* x_scal;                    //特征缩放率

};

“linear_regression.cpp”

//多变量线性回归
var2_lin_reg::var2_lin_reg(int xnum, elem_var2* p, int size, double rate)
{//参数列表:自变量数目,训练集地址,训练集容量,学习速率
	x_num = xnum+1;                  //设置自变量数目
	setsize = size;                  //获取训练集大小
	tran_set = p;                    //指针指向训练集数组
	learn_rate = rate;               //设置学习速率
	par = new double[xnum + 1];      //系数初始化为0
	memset(par, 0, sizeof(double)*(xnum + 1));
	x_scal = new double[xnum + 1];   //特征缩放率初始化为1
	for (int i = 0;i < x_num;i++)
		x_scal[i] = 1;
}

var2_lin_reg::~var2_lin_reg()
{
	tran_set = NULL;
	setsize = 0;
	delete[]par;
	par = NULL;
	delete[]x_scal;
	x_scal = NULL;
}

void var2_lin_reg::scaling()
{//特征缩放器
	for (int j = 0;j < x_num;j++)
	{//以第一组数据确定缩放率
		while (tran_set[0].x[j] > 10 || tran_set[0].x[j] < 1)
		{
			if (tran_set[0].x[j] > 10)
			{
				tran_set[0].x[j] /= 10.0;
				x_scal[j] *= 10.0;
			}
			else
			{
				tran_set[0].x[j] *= 10.0;
				x_scal[j] /= 10.0;
			}
		}
	}
    for (int i = 1;i < setsize;i++)
	{//对剩余数据进行缩放
		for (int j = 0;j < x_num;j++)
		{
			tran_set[i].x[j] /= x_scal[j];
		}
	}

}

void var2_lin_reg::scalback()
{//特征还原,参数缩放
	for (int i = 0;i < x_num;i++)
		par[i] /= x_scal[i];
}

double var2_lin_reg::cost_fuction()
{ //返回当前预测方程对应代价函数的值
	double hx, sum = 0;
	for (int i = 0;i < setsize;i++)
	{
		hx = 0;
		for (int j = 0;j < x_num;j++)
		{
			hx += par[j] * tran_set[i].x[j];
		}
		sum += (hx - tran_set[i].y)*(hx - tran_set[i].y);
	}
	return (sum / 2.0 / setsize);
}

void var2_lin_reg::update()
{//同时更新方程参数
	double hx;
	double* sum = new double[x_num];
	for (int j = 0;j < x_num;j++)
	{
		sum[j] = 0;
		for (int i = 0;i < setsize;i++)
		{
			hx = 0;
			for (int t = 0;t < x_num;t++)
			{
				hx += par[t] * tran_set[i].x[t];
			}
			sum[j] += (hx - tran_set[i].y)*tran_set[i].x[j];
		}
	}
	for (int i = 0;i < x_num;i++)
		par[i] -= learn_rate * sum[i] / (double)setsize;
	delete[]sum;
	
}

void var2_lin_reg::find()
{//最小化代价函数,找到收敛点时对应的方程参数
	scaling();                     //数据放缩
	double cost_pre, cost_last;
	cost_pre = cost_fuction();
	update();                      //更新参数
	cost_last = cost_fuction();
	while (cost_pre != cost_last)
	{//寻找收敛点
        /*
		cout << cost_pre << " " << cost_last << endl;        //用来选择学习率
		*/
		cost_pre = cost_last;
		update();
		cost_last = cost_fuction();
	}
	//获得假设函数最优拟合时的参数
	scalback();
	//特征还原,参数缩放
}

double* var2_lin_reg::get_par(int &par_num)
{//获得当前方程的参数
	par_num = x_num;
	return par;
}

double var2_lin_reg::est_val(double* x)
{//使用拟合后的回归方程进行预测
	double hx = 0;
	for (int i = 0;i < x_num;i++)
	{
		hx += par[i] * x[i];
	}
	return hx;
}

主函数部分:

int main()
{//多变量线性回归测试
	int size, xnum;
	cout << "请输入训练集容量:";
	cin >> size;
	cout << "请输入变量个数: ";
	cin >> xnum;
	elem_var2 transet[200];
	for (int i = 0;i < size;i++)
	{
		transet[i].y = 0;
		transet[i].x = new double[xnum + 1];
		memset(transet[i].x, 0, sizeof(double)*(xnum + 1));
	}
	
	cout << "请输入训练集数据:" << endl;
	for (int i = 0;i < size;i++)
	{
		transet[i].x[0] = 1;
		for (int j = 1;j <= xnum;j++)
			cin >> transet[i].x[j];
		cin >> transet[i].y;
	}
	var2_lin_reg obj(xnum, transet, size, 0.042);
	obj.find();
	double*par = NULL;
	int parnum;
	par=obj.get_par(parnum);
	cout << "h(x)=" << par[0];
	for (int i = 1;i < parnum;i++)
	{
		if (par[i] > 0)
			cout << '+' << par[i] << "*x" << i;
		else
		{
			if (par[i] < 0)
				cout << par[i] << "*x" << i;
		}
	}
	cout << endl;

	/*
	double*x = new double[xnum + 1];
	memset(x, 0, sizeof(double)*(xnum + 1));
	double flag;
	while (cin >> flag)
	{//预测部分
		if (flag == 0)
			break;
		x[0] = 1;
		for (int i = 1;i <= xnum;i++)
			cin >> x[i];
		flag = obj.est_val(x);
		cout << flag << endl;
	}
	delete[]x;
	*/

	for (int i = 0;i < size;i++)
	{
		delete[]transet[i].x;
	}
	return 0;
}

关于一些操作细节:
以吴恩达老师的训练集为例。
1.学习率选择:
find函数中有一行用于选择学习率的代码:
cout << cost_pre << " " << cost_last << endl;
下面来实际操作一下:
主函数学习率设为:0.042是通过测试得出的。
在这里插入图片描述
如果将学习率设置较大,输出的代价函数值结果如下:
在这里插入图片描述
在这里插入图片描述

所以,可将学习率适当减小,通过不断测试得出一下结果:
在这里插入图片描述
在这里插入图片描述
2.特征缩放:将所有特征缩放至【1–10】
特征缩放和特征还原:
void scaling(); //特征缩放器(将所有特征值统一缩放至1–10)
void scalback(); //特征还原(参数缩放)
在find函数中先进行特征缩放,将数据集内的特征值缩放,最后得出的对应参数便放缩(反向),之后再进行特征还原(例如特征值缩小,最后得出的参数便增大,需要将对应参数再进行缩小(缩小比例与特征值缩小比例一致))

Octave实现正规方程法

>> A=load('space(x1)_bedroom(x2).txt');
>> B=load('price(y).txt');
>> t=ones(47,1);
>> X=[t A];
>>Y=B;
>>par=pinv(X'*X)*X'*Y
par =

   684.1446
     1.3544
    -5.8816

获得的参数向量与上面的梯度下降法得出的结果近似。

猜你喜欢

转载自blog.csdn.net/qq_40432881/article/details/84590806