作者 | 梁唐
大家好,我是梁唐。
上一篇文章我们回顾了梯度下降的概念和定义,以及它的公式表达。
文末留下了一个问题,就是当我们使用梯度下降算法时,选择的参数学习率是不是越大越好呢?
显然对于这种越什么什么越好的问题的标准答案往往都是不一定,需要根据具体的情况进行分析。在这个问题当中,如果我们选择的学习率过大,很有可能会导致在迭代的过程当中错过最优点。就好像油门踩猛了,一下子就过头了,于是可能会出现永远也无法收敛的情况。比如我们可以参考下面这张图:
从这张图上可以看到,由于我们选择的学习率过大,导致无法收敛到极值点,一直在极值点附近震荡。
如果学习率设置得小一些是不是就没事了?也不是,如果设置的学习率过小,除了会导致迭代的次数非常庞大以至于训练花费的时间过久之外,还有可能由于小数的部分过大,导致超出了浮点数精度的范围,以至于出现非法值Nan这种情况出现。同样,我们可以参考一下下图:
这张图画的是学习率过小,导致一直在迭代,迟迟不能收敛的情况。
从上面这两张图,我们可以看得出来,在机器学习领域学习率的设置非常重要。一个好的参数不仅可以缩短模型训练的时间,也可以使模型的效果更好。但是设置学习率业内虽然有种种方法,但是不同的问题场景,不同的模型的学习率设置方法都略有差别,有的时候还是需要通过多次实验来调整。
不过总体来说,大多数情况下学习率的设置并不会太影响模型的效果。工程师们往往也都有自己比较偏爱的默认值,比如我个人比较喜欢设置成0.01。也有些同事喜欢设置得稍大一点,比如0.1,因为使用了学习率衰减算法,一般来说大家最终得到的模型效果是差不多的。
下面我们来看下一个理想的学习率应该是什么样的:
理想状况下,学习率一开始设置得会比较大一些,方便我们快速逼近极值。随着我们距离极值越来越近,逐渐缩小学习率方便收敛。
这也是目前的常规做法,即设置一个衰减算法,随着训练的进行逐渐缩小学习率。对应的算法有很多,比如常用的Adam、RMSprop等等。
到这里还没有结束,好的学习率并不能解决所有的问题。在有些问题有些模型当中,很有可能最优解本身就是无法达到的,即使用非常科学的方法,设置非常好的参数。我们再来看一张图:
image-20211130092336121
这张图有不止一个极值点,如果我们一开始的时候,参数落在了区间的左侧,那么很快模型就会收敛到一个极值,但是它并不是全局最优解,只是一个局部最优解。这时候无论我们如何设置学习率,都不可能找到右侧的那个全局最优解。
同样,如果我们一开始参数落在了区间右侧,那里的曲线非常平坦,使得每次迭代的梯度都非常小,非常接近0那么虽然最终可以到达全局最优解,但是需要经过漫长的迭代过程。
所以,模型训练、梯度下降虽然方法简单,但是真实的使用场景也是非常复杂的。我们不可以掉以轻心,不过好在,对于线性回归的最小二乘法来说,损失函数是一个凸函数,意味着它一定有全局最优解,并且只有一个。随着我们的迭代,一定可以达到收敛。
代码实战
Talk is cheap, show me the code.
光说不练假把式,既然我们已经学习到了梯度下降的精髓,也该亲身用代码体验一下了。我们还是用之前线性回归的问题。还是和之前一样,我们先生成一批点:
代码语言:javascript复制import numpy as np
X = 2 * np.random.rand(100, 1)
y = 4 3 * X np.random.randn(100, 1)
代码语言:javascript复制eta = 0.1 # 学习率
n_iterations = 1000 # 迭代次数
m = 100
theta = np.random.randn(2,1) # 随机初始值
X = np.c_[np.ones(100).T, X]
for iteration in range(n_iterations):
gradients = 1/m * X.T.dot(X.dot(theta) - y) # 根据梯度公式迭代
theta = theta - eta * gradients
我们调用一下这段代码,来查看一下结果:
和我们设置的参数非常接近,效果算是很不错了。如果我们调整学习率和迭代次数,最后的效果可能会更好。
观察一下代码可以发现,我们在实现梯度下降的时候,用到了全部的样本。显然,随着样本的数量增大,计算梯度需要的时间会变得更长。为了解决这个问题,计算学家们后续推出了许多优化的方法。不过由于篇幅的限制,我们会在下一篇文章当中和大家分享,感兴趣的同学可以小小地期待一下。
梯度下降非常重要,可以说是机器学习领域至关重要的基础之一,希望大家都能学会。