梯度下降是一种在机器学习和深度学习中广泛使用的优化方法,常用于回归和分类问题中。在函数表示的曲线上的一点,其梯度方向表示函数值上升最快的方向,由于在机器学习中的梯度是损失函数的梯度,因此我们想要损失函数最小,就要将参数往负梯度方向进行调整。
以一元线性回归为例,我们的数据是由
y= w_refx+b_ref
再加噪声生成,在这里我们假设w_ref=3,b_ref=2,因此原函数就是
y= 3x+2
我们在区间x∈[start,end]中等间距取n个点,并生成y值,然后再在此基础上加上e∈[-q,q]的随机浮点数噪声,最终的
y_ref = y+e。
在得到原始数据以后,就可以利用梯度下降算法,回归原来的直线了。梯度下降算法首先需要有数据,这里我们采用SGD方法求的梯度,就是每次都用一个样本。
设模型参数y = wx+b.这里的w和b就是待回归的参数,这里我们随机赋初始值令w=-2,b=4。首先获得在此模型参数下得到的y值
y = wx+b
然后得到损失函数
function_error = (y-y_ref)^2
再根据损失函数计算梯度
Δw = x*(y-y_ref)
Δb = (y-y_ref)
最后将梯度乘系数加到当前参数上得到一次优化后的参数
w = w - α_w∗Δw
b = b - α_b∗Δb
到达设定的终止条件后停止训练。
代码使用C++编写,将计算过程的各个部分封装成不同的函数,依次调用执行,有利于阅读和调试,并他们作为求解一元线性规划问题类的成员函数,进行调用。下面是类中使用的函数:
double w=-2.0;//初始参数
double b = 4;
double val(double x);//求模型输出y
vector<vector<double>> rand_val(double w_ref,double b_ref,double start,double end,int n);//生成样本集
vector<double> grad(double y,double y_ref,double x);//计算梯度
void train(vector<vector<double>> data,double alpha_w,double alpha_b);//训练函数
一、生成样本集
使用c++模板类vector构建二维数组,二维数组中每个一维数组是一个样本,其第一个元素是对应x坐标,第二个元素是对应的y_ref。x的取值范围是[start,end],总的采样个数是n,对应采样间隔是nuit_x。随机数据的取值范围为[-4,4]之间的浮点数。
vector<vector<double>> rand_val(double w_ref,double b_ref,double start,double end,int n){
double e = 0.0;
double unit_x = (end-start)/n;
vector<vector<double>> val(n);
while(n--){
e = -4 + (double)(rand()) /RAND_MAX * (4 - (-4));
val[n].push_back(start+n*unit_x);
val[n].push_back(w_ref*val[n][0]+b_ref+e);
//cout<<"x"<
}
return val;
}
二、计算梯度
梯度也是以数组的形式返回,第一个元素是关于w的导数,第二个元素是关于b的导数。这里函数直接返回的损失函数的梯度,没有显式得写出损失得表达。但是在计算梯度的时候同样需要当前模型输出y,因此首先要求解y。
//计算y
double val(double x){
return w*x+b;
}
//计算梯度
vector<double> grad(double y,double y_ref,double x){
vector<double> tmp;
tmp.push_back(x*(y-y_ref));//w's grad
tmp.push_back(y-y_ref);//d's grad
//cout<
return tmp;}
三、训练函数
训练函数中首先需要获取样本总数,因为是SGD得方式求梯度,因此每次在for循环中提取一个样本进行训练,还是以原理中解释得步骤,首先计算y值,然后计算梯度,最后对w和b分别以不同得学习率进行更新。
void train(vector<vector<double>> data,double alpha_w,double alpha_b){
int num = data.size();
vector<double> tmp;
double x(0.0),y(0.0),y_ref(0.0);
for(int i = 0;i<num;i++){
x = data[i][0];
y_ref = data[i][1];
y = val(x);
tmp = grad(y,y_ref,x);
w = w-alpha_w*tmp[0];
b = b-alpha_b*tmp[1];
}
}
四、主函数
主函数中主要进行参数设置以及启动训练,设置w_ref和b_ref可以让我们拟合任意一个想要的函数,检验自己的算法,w_init和b_init是类中随机初始化的参数值,sample是采样的样本个数,start_sample以及end_sample是采样区间,alpha_w和alpha_b是学习率。
double w_init = sol.w;
double b_init = sol.b;
double w_ref = 3.0;
double b_ref = 2.0;
double sample = 500;
double start_sample = -10;
double end_sample = 10.0;
double alpha_w = 0.002;
double alpha_b = 0.01;
github源码:
https://github.com/wangjunhe8127/linear-regression-by-Gradient-descent
…后续请移步我的古月居梯度下降实现一元线性回归[C++]
May the force be with you!