机器学习之误差函数与梯度下降

1,705 阅读8分钟

需要解决的问题

误差函数和梯度下降是机器学习里面的两个最为基础的概念。在解释这两个概念之前,先来说说机器学习所要解决的问题,机器学习是基于之前的数据对将来做预测。那进一步的问题来了,首先是怎么样去做这个预测,还有就是 如何评估这个预测的准确度

先回答第一个问题,如何去做预测。由于我们是基于之前的数据来做预测,所以我们需要从之前的数据中找到一定的规律,一般说来可以预测的事情大多都是有一定的因果相关性的,比如说我们会根据年龄来预测收入(这里只是用作例子),这个例子中影响收入的因素只有一个,我们可以用一些变量来表示这些因素:

x -> 年龄

y -> 月收入

机器学习要做的事情就是找出这两者之间的关系,因为这些关系是可以用数学表达式表示的,因此我们可以建立数学模型,假如说你的样本数据显示如下:

通过观察数据的大致的分布位置,我们可以选用线性模型来做预测,于是你可以得到下面的模型表达式:

y = \theta_0 + \theta_1 x

这里的问题在于如何得到较为准确的 \theta_0\theta_1,还有就是,你如何评判 \theta_0\theta_1 好不好呢?这就需要误差函数了,误差函数所解决的问题就是计算预测和真实情况的差别,这个差别越小就说明模型的参数选的越好。另外一个问题是,如何选择合适的 \theta_0\theta_1 ?梯度下降在这里就是为了解决这个问题,梯度下降的目的就是通过不断地降低误差函数来计算出较为合适的 \theta_0\theta_1


误差函数

前面提到,误差函数是用来计算实际和预测的差别,预测和实际的差别可以用下面的表达式表示:

y - y' = \theta_0 + \theta_1 x - y'

这里的 y' 表示的实际值,当然仅仅是写成这样还是不够的,因为我们可能有多条数据,于是我们可以写成一个求和的形式:

\sum_{i=1}^{m}{} (\theta_0 + \theta_1 x^{(i)} - y'^{(i)})

这里 m 表示的是我们有 m 条样本数据,i 表示的是其中第 i 条数据,同理 x^{(i)} 表示的是第 i 条数据中的年龄,y'^{(i)} 表示的是第 i 条数据中的实际的收入。但是这里还是有一个问题,\theta_0 + \theta_1 x^{(i)} - y'^{(i)} 可能为正也可能为负,正的算是误差,负的也是误差,但是正负相加会被抵消,这并不能很好地反应真实情况,于是我们可以考虑将其取平方再求和,于是得出了下面的式子:

\sum_{i=1}^{m}{} (\theta_0 + \theta_1 x^{(i)} - y'^{(i)})^2

如果你结合实际情况去思考,你会发现上面式子的结果和数据量存在正相关,也就是数据量增多误差就会增大,当我们用不同的数据量去训练模型,误差函数的结果不能横向对比。于是我们考虑用平均值来描述误差函数,可以得到下面的式子:

\frac{1}{2m} \sum_{i=1}^{m}{} (\theta_0 + \theta_1 x^{(i)} - y'^{(i)})^2

这里乘上 1/2 主要是为了后面求导的方便,这样我们就可以基于该模型得出误差函数的表达式如下:

J(\theta_0, \theta_1) = \frac{1}{2m} \sum_{i=1}^{m}{} (\theta_0 + \theta_1 x^{(i)} - y'^{(i)})^2

需要注意的是,误差函数的自变量是 \theta_0\theta_1,也就是模型的参数。通过样本数据我们可以画出 J(\theta_0, \theta_1),这里存在三个变量,因此画出来就是三维图形:

其中 z 轴表示的就是 J,我们需要让 J 尽可能地小,接下来就轮到梯度下降的工作了。


梯度下降

在 YouTube 上有一个很好的关于导数和梯度的 教学视频,视频仅仅 5 分多钟,但是结合图像讲解的非常形象易懂,或许对你有帮助。

梯度下降是一个应用很广泛的算法,主要用来求极小值,注意这里我说的是极小值而不是最小值,后面我会解释。在讲述梯度下降之前,我们需要了解另一个数学上面的知识点,那就是 求导。如果你在大学学过《高等数学》的话,对这个应该不会陌生,可能你知道如何求导,毕竟之前做了很多题目,套了很多公式,上课的时候多多少少看了老师给的例子,但是你明白求导的意义是什么吗?

我们拿二维坐标来举例,比如说在二维坐标上有一个曲线,那么在曲线上的某一点处求导就是求这一点的斜率:

你可以看到,我们其实可以在这一点上做切线,求导得出的斜率就是这个切线的斜率,那么斜率是什么?斜率可以反映因变量随着自变量变化的幅度,准确说来就是 \frac{\Delta J}{\Delta \theta},这个式子告诉我们,如果 \theta 变化相同,J 变化越大,斜率也就越大。斜率不仅仅可以描述变化的大小,还可以描述变化的方向,如果随着 \theta 的增大,J 也变大,那就是正相关,斜率也为正,反之就是负相关,斜率为负,当然只有二维曲线我们可以用做切线,到了多维的话,我们就需要借助向量了,但是基本的原理是不变的。

说这些的目的是什么呢?回顾一下前面我们讲到误差函数,我们说我们需要选择合适的 \theta_0\theta_1 来使得 J(\theta_0, \theta_1) 尽可能的小,我们可以给 \theta_0\theta_1 分别取一个起始值,形象地说就是在之前的三维图像上选择一个点,然后在这个点上求导得出 “斜率”,然后根据这个 “斜率” 不断地调整 \theta_0\theta_1,写成表达式如下:

\theta_0 := \theta_0 - \alpha \frac{1}{m} \sum_{i=1}^{m}{} (\theta_0 + \theta_1 x^{(i)} - y'^{(i)})
\theta_1 := \theta_1 - \alpha \frac{1}{m} \sum_{i=1}^{m}{} (\theta_0 + \theta_1 x^{(i)} - y'^{(i)})x^{(i)}

如果说在某一点上 \theta_0 的 “斜率” 为负,表明 J(\theta_0, \theta_1) 会随着 \theta_0 的增大而减小,那么我们希望 \theta_0 增大从而达到减小 J(\theta_0, \theta_1) 的目的,这个从表达式中你也可以看出来。这里的 \alpha 是 learning rate,用来表示每步变化的幅度,\alpha 的取值是我们可以调节的,取值不能过大也不能过小,如果过大的话会造成越过极值点,导致不能逼近极小值;取值过小的话,每步变化会很小,导致计算时间过长,算法运行效率降低。运用上面的公式不断地迭代运算,我们可以不断地降低 J(\theta_0, \theta_1),变化曲线如下:

对于梯度下降,有一点我们是要知道的,那就是梯度下降求出来的仅仅是局部最小,并不一定是全局最小,比如下面的例子:

这里我们选了两个不同的起始点,最后得出了两组不一样的参数。这个是梯度下降的一个性质,运用的时候需要注意。

我们运用梯度下降可以得到较为合理的 \theta_0 以及 \theta_1,有了这两个参数我们就可以得出我们的模型了:

有了这些,当再给定一个人的年龄,那么我们就可以基于我们训练出来的这个模型来预测这个人的一个大致的收入情况。这个是一个最为简单的模型,线性模型,或被称为线性回归,基于数据分布情况的不一样,我们还可以选用其他的模型,比如 平方,立方或是开方等等,基本的训练流程也是和线性模型相似的。


总结

这两个概念其实不难,但就是有时我们总是陷入到一些细节里,比如公式计算、推导等等。相比这些,深入理解概念更为重要,只有一些重要的概念理解了,计算,推导才会变得清晰,有意义,比如上面我们讲的求导的意义。学习这些东西切记不要想当然的觉得把公式背下来,看情况直接套公式就好,这样你很难做到对一个算法的优劣有全方位的认识,你需要反复思考公式中的每个变量的意义,每个推导步骤的意义,不断地发现问题,思辨,总结才能做到融会贯通。

关于计算,我们可以使用矩阵来辅助计算,像是 Octave/Matlab 这样的工具可以很好地帮助我们进行矩阵的运算,这比写简单的 for 循环要高效很多,因为 Octave/Matlab 底层对矩阵计算做了很多的优化。运用矩阵需要一些线性代数的知识,但是这个东西说到底也只是一个工具,用多了,自然就熟练了。重要的还是对算法本身的理解。