梯度下降实现一元线性回归[C++]

梯度下降实现一元线性回归[C++]

原理

梯度下降是一种在机器学习和深度学习中广泛使用的优化方法,常用于回归和分类问题中。在函数表示的曲线上的一点,其梯度方向表示函数值上升最快的方向,由于在机器学习中的梯度是损失函数的梯度,因此我们想要损失函数最小,就要将参数往负梯度方向进行调整。
以一元线性回归为例,我们的数据是由
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。
梯度下降实现一元线性回归[C++]_第1张图片
在得到原始数据以后,就可以利用梯度下降算法,回归原来的直线了。梯度下降算法首先需要有数据,这里我们采用SGD方法求的梯度,就是每次都用一个样本。
设模型参数y = wx+b.这里的w和b就是待回归的参数,这里我们随机赋初始值令w=-2,b=4。首先获得在此模型参数下得到的y值
y = w
x+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!

你可能感兴趣的:(Machine,Learning,c++,机器学习,随机梯度下降,线性回归)