欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)

程序员文章站 2022-05-08 19:23:24
写在前面的话: 在第一学期做项目的时候用到过相应的知识,觉得挺有趣的,就记录整理了下来,基于C/C++语言 原贴地址:https://helloacm.com/cc-linear-regression-tutorial-using-gradient-descent/ 前言 在机器学习和数据挖掘处理等 ......

写在前面的话:

在第一学期做项目的时候用到过相应的知识,觉得挺有趣的,就记录整理了下来,基于c/c++语言

原贴地址:https://helloacm.com/cc-linear-regression-tutorial-using-gradient-descent/

---------------------------------------------------------------前言----------------------------------------------------------------------------------

在机器学习和数据挖掘处理等领域,梯度下降(gradient descent)是一种线性的、简单却比较有效的预测算法。它可以基于大量已知数据进行预测, 并可以通过控制误差率来确定误差范围。

--------------------------------------------------------准备------------------------------------------------------------------------

gradient descent

回到主题,线性回归算法有很多,但gradient descent是最简单的方法之一。对于线性回归,先假设数据满足线性关系,例如:

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)

所以,作为线性回归,我们的任务就是找到最合适 b0 和 b1, 使最后的结果y满足可接受的准确度。作为起步,首先让我们对b0和b1赋值初始值0,如下所示:

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)

设误差 error 为 e, 并引入下面几个点做例子:

 

x y
1 1
2 3
4 3
3 2
5 5

 

如果我们计算第一个点的误差,则得到 C / C ++ 基于梯度下降法的线性回归法(适用于机器学习) ,其中p(i)为表中的数据值,则 e 结果为  -1。但这只是开始,下面我们可以使用gradient descent来更新y中的系数。这就涉及到数据 / 机器学习, 所谓的 数据 / 机器学习,其实可以大致理解为在相应的函数模型下,通过不停地更新其中系数,使新函数曲线可以拟合原始数据并预测走势的过程。

回到主题,设刚才的初始状态为 t ,那么对于下一个状态 t+1 , b0可表示为 :

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)

其中 b0(t + 1)是系数的更新版本,为套入下一个点做准备。 是学习率,即为精度,这个我们可以自己设定。 越大,说明每次学习的跨度就越大,预测结果在相应的正确答案两边的摆幅也就越大,所以此情况下学习次数不易过多,否则越摆越离谱。ps:有次因为 值太大的原因导致结果不精准,结过以为是学习次数不够多,后来等到把2.3的数值摆到10个亿才反应过来是出来问题。话说10个亿真是个小目标呢。

言归正传,这里取 ∂ = 0.01,即可得以下式子,b0=0.01.

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习).

现在再来看 b1,在 t+1时刻,公式变为:

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)

同样赋值,也同样得到:

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)

--------------------------------------------------------操作------------------------------------------------------------------------

现在,我们可以重复迭代这种过程到下一个点,再到下下个点,一直到所有点结束,这称为1回(an epoch)。但是我们可以通过反复不停地迭代,来使得到的线性拟合曲线更接近初始数据。比如迭代4回,每回5个点,也就是20次。c / c ++代码如下:

double x[] = {1, 2, 4, 3, 5};
double y[] = {1, 3, 3, 2, 5};
 
double b0 = 0;
double b1 = 0;
double alpha = 0.01;
 
for (int i = 0; i < 20; i ++) {
    int idx = i % 5; //5个点
    double p = b0 + b1 * x[idx];
    double err = p - y[idx];
    b0 = b0 - alpha * err;
    b1 = b1 - alpha * err * x[idx];
}

把b0、b1还有 误差(error)的结果打印出来:

b0 = 0.01, b1 = 0.01, err = -1
b0 = 0.0397, b1 = 0.0694, err = -2.97
b0 = 0.066527, b1 = 0.176708, err = -2.6827
b0 = 0.0805605, b1 = 0.218808, err = -1.40335
b0 = 0.118814, b1 = 0.410078, err = -3.8254
b0 = 0.123526, b1 = 0.414789, err = -0.471107
b0 = 0.143994, b1 = 0.455727, err = -2.0469
b0 = 0.154325, b1 = 0.497051, err = -1.0331
b0 = 0.157871, b1 = 0.507687, err = -0.354521
b0 = 0.180908, b1 = 0.622872, err = -2.3037
b0 = 0.18287, b1 = 0.624834, err = -0.196221
b0 = 0.198544, b1 = 0.656183, err = -1.56746
b0 = 0.200312, b1 = 0.663252, err = -0.176723
b0 = 0.198411, b1 = 0.65755, err = 0.190068
b0 = 0.213549, b1 = 0.733242, err = -1.51384
b0 = 0.214081, b1 = 0.733774, err = -0.0532087
b0 = 0.227265, b1 = 0.760141, err = -1.31837
b0 = 0.224587, b1 = 0.749428, err = 0.267831
b0 = 0.219858, b1 = 0.735242, err = 0.472871
b0 = 0.230897, b1 = 0.790439, err = -1.10393

怎么样,能发现什么?不容易看出来没关系,我们把点画下来:

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)

从图中,我们可以看到误差正逐渐变小,所以我们的最终模型也就是第20次的模型:

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)

 

所以最后的曲线拟合结果如下:

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)

到这里其实不一定死板地局限于20次,也并不是迭代次数越多越好,因为这个过程像一个开口向下的二次函数, 适合的才是最好的。

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)因为最合适的点可能就在中间,迭代太多次就跑偏了。解决这个问题可以在源代码里简单地加一个 if () 函数,当误差满足xxx时跳出循环就完事了。

 

到这里应该就结束了。但原文章里多算了一次 root-mean-square 值,也就是均方根,常用来分析噪声或者误差,公式如下:

C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)   把每个点带入,得到rmse=0.72。

--------------------------------------------------------总结------------------------------------------------------------------------

其实gradient descent 通常适用于 量非常大且繁琐的数据(不在乎有那么几个因为跑偏而被淘汰的值)。

但如果要求数据足够精确、且数据模型复杂,不适合一次函数模型,那gradient descent  并不见得是一个好方法。