第6章改进梯度下降法

优化器在机器学习、深度学习中往往起着举足轻重的作用,同一个模型,因选择不同的优化器,性能有可能相差很大,甚至导致一些模型无法训练。所以,了解各种优化器的基本原理非常必要。本节重点介绍各种优化器或算法的主要原理,及各自的优点或不足。
梯度下降法的最大优点就是沿下降最快的方向迭代。但不足之处也不少。梯度下降法对学习率表敏感,而且迭代过程中始终不变,这是一个可以改进的地方;另外,梯度越到极值点就越小,或在比较平滑的地方梯度也很小,以至于趋近于0,这是可改进的另一个地点。接下来就这些改进进行说明。这些改进涉及一个重要概念指数加权平均,所以,先介绍这个概念的来源及其优点等。

6.1 指数加权平均法

表1常用平均法比较

【说明】

①F_(t-1)表示前期预测值
②F_t表示当前预测值
③x_t当前实际值
④α权重参数

指数加权平均法可参考:
https://zhuanlan.zhihu.com/p/32335746

6.2传统梯度优化的不足

传统梯度更新算法为最常见、最简单的一种参数更新策略。其基本思想是:先设定一个学习率λ,参数沿梯度的反方向移动。假设基于损失函数L(f(x,θ),y),其中θ需更新的参数,梯度为g,则其更新策略的伪代码如下所示:
这种梯度更新算法简洁,当学习率取值恰当时,可以收敛到全面最优点(凸函数)或局部最优点(非凸函数)。
但其不足也很明显,对超参数学习率比较敏感(过小导致收敛速度过慢,过大又越过极值点),如图6-1的右图所示。在比较平坦的区域,因梯度接近于0,易导致提前终止训练,如图6-1的左图所示,要选中一个恰当的学习速率往往要花费不少时间。
图6-1学习速率对梯度的影响
学习率除了敏感,有时还会因其在迭代过程中保持不变,很容易造成算法被卡在鞍点的位置,如图6-2所示。

图6-2算法卡在鞍点示意图
另外,在较平坦的区域,因梯度接近于0,优化算法往往因误判,还未到达极值点,就提前结束迭代,如图6-3所示。

图6-3在较平坦区域,梯度接近于0,优化算法因误判而提前终止迭代
传统梯度优化方面的这些不足,在深度学习中会更加明显。为此,人们自然想到如何克服这些不足的问题。从本小节前文更新策略的伪代码可知,影响优化的主要因素有:
•优化梯度方向
•优化学习率
•参数的初始值
•优化训练数据集的大小
所以很多优化方法大多从这些方面入手,数据集优化方面采用批量随机梯度下降方法,从梯度方向优化方面有动量更新策略;有些从学习率入手,这涉及自适应问题;还有从两方面同时入手等方法,接下来将介绍这些方法。

6.3 动量算法

动量(momentum)是模拟物理里动量的概念,具有物理上惯性的含义,一个物体在运动时具有惯性,把这个思想运用到梯度下降计算中,可以增加算法的收敛速度和稳定性,具体实现如图6-4所示。
图6-4动量算法示意图
由图6-4所示,可知动量算法每下降一步都是由前面下降方向的一个累积和当前点的梯度方向组合而成。
含动量的随机梯度下降法,其算法伪代码如下:
其中parameters是模型参数,假设模型为model,则parameters指model. parameters()。
具体使用动量算法时,动量项的计算公式如下:
v_k=\alpha v_{k-1}-\lambda \hat g(\theta_k)\tag{6.1}
其中\lambda为学习率,\alpha为动态因子(一般取值为0.9)。如果按时间展开,则第k次迭代使用了从1到k次迭代的所有负梯度值,且负梯度按动量系数\alpha指数级衰减,相当于使用了移动指数加权平均,具体展开过程如下:
由此可知,当在比较平缓处,但\alpha=0.5,0.9 时,将是分别是梯度下降法的2倍、10倍。
使用动量算法,不但可加速迭代速度,还可能跨过局部最优找到全局最优,如图6-5所示。
图6-5 使用动量算法的潜在优势
每个参数的实际更新差值取决于最近一段时间内梯度的指数加权平均值。
当某个参数在最近一段时间内的梯度方向振幅较大时,其真实的参数更新幅度变小,增加稳定性;相反,当在最近一段时间内的梯度方向都一致时,其真实的参数更新幅度变
大,起到加速作用。
我们以求解函数f(x_1,x_2)=0.05x_1^2+2x_1^2极值为例,使用梯度下降法和动量算法分别进行迭代求解,具体迭代过程如图6-6、图6-7所示(图实现代码请参考本书第5章代码部分)。
图6-6 梯度下降法的迭代轨迹 图6-7 使用动量项的迭代轨迹
从图6-6 可以看出,不使用动量算法的SGD学习速度比较慢,振幅比较大;图6-7 可以看出,使用动量算法的SGD,振幅较小,而且较快到达极值点。
批量随机梯度下降法PyTorch代码实现:

6.4 Nesterov动量算法

既然每一步都要将两个梯度方向(历史梯度、当前梯度)做一个合并再下降,那为什么不先按照历史梯度往前走那么一小步,按照前面一小步位置的“超前梯度”来做梯度合并呢?如此一来,可以先往前走一步,在靠前一点的位置(如图6-8中的C点)看到梯度,然后按照那个位置再来修正这一步的梯度方向,如图6-8所示。

图6-8 Nesterov下降法示意图
这就得到动量算法的一种改进算法,称为NAG(Nesterov Accelerated Gradient)算法,也称Nesterov动量算法。这种预更新方法能防止大幅振荡,不会错过最小值,并对参数更新更加敏感。如图6-9所示。
图6-9 Nesterov加速梯度下降法
NAG下降法的算法伪代码如下:
NAG算法的PyTorch实现如下:

NAG动量法和经典动量法的差别就在B点和C点梯度的不同。动量法,更多关注梯度下降方法的优化,如果能从方向和学习率同时优化,效果或许更理想。事实也确实如此,而且这些优化在深度学习中显得尤为重要。接下来我们介绍几种自适应优化算法,这些算法同时从梯度方向及学习率进行优化,效果非常好。

6.5 AdaGrad算法

传统梯度下降算法对学习率这个超参数非常敏感,难以驾驭;对参数空间的某些方向也没有很好的方法。这些不足在深度学习中,因高维空间、多层神经网络等因素,常会出现平坦、鞍点、悬崖等问题,因此,传统梯度下降法在深度学习中显得力不从心。还好现在已有很多解决这些问题的有效方法。上节介绍的动量算法在一定程度缓解对参数空间某些方向的问题,但需要新增一个参数,而且对学习率的控制还不很理想。为了更好驾驭这个超参数,人们想出来多种自适应优化算法,使用自适应优化算法,学习率不再是一个固定不变值,它会根据不同情况自动调整来适用情况。这些算法使深度学习向前迈出一大步!这节我们将介绍几种自适应优化算法。
AdaGrad算法是通过参数来调整合适的学习率λ,能独立地自动调整模型参数的学习率,对稀疏参数进行大幅更新和对频繁参数进行小幅更新,如图6-10所示。因此,Adagrad方法非常适合处理稀疏数据。AdaGrad算法在某些深度学习模型上效果不错。但还有些不足,可能因其累积梯度平方导致学习率过早或过量的减少所致。
图6-10 AdaGrad 算法迭代过程示意图
AdaGrad算法伪代码如下:
由上面算法的伪代码可知:
•随着迭代时间越长,累积梯度r越大,从而学习速率\frac{\lambda}{\delta+\sqrt{r}}随着时间就减小,在接近 目标值时,不会因为学习速率过大而越过极值点。
•不同参数之间学习速率不同,因此,与前面固定学习速率相比,不容易在鞍点卡住。
•如果梯度累积参数r比较小,则学习速率会比较大,所以参数迭代的步长就会比较大。相反,如果梯度累积参数比较大,则学习速率会比较小,所以迭代的步长会比较小。
AdaGrad算法的PyTorch实现代码:

6.6 RMSProp算法

RMSProp算法修改AdaGrad,为的是在非凸背景下的效果更好,在凸函数可能振幅较大,如图6-11所示。对梯度平方和累计越来越大的问题,RMSProp指数加权的移动平均代替梯度平方和。RMSProp为使用移动平均,引入了一个新的超参数ρ,用来控制移动平均的长度范围。
图6-11 RMSProp迭代过程示意图
RMSProp算法伪代码如下:
RMSProp算法在实践中已被证明是一种有效且实用的深度神经网络优化算法,在深度学习中得到广泛应用。
RMSProp算法PyTorch实现代码如下:

6.7 Adam算法

Adam(Adaptive Moment Estimation,自适应矩估计)算法本质上是带有动量项的RMSprop,它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。Adam的优点主要在于经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳,如图6-12所示。
图6-12 Adam算法迭代过程示意图
Adam算法是另一种学习速率自适应的深度神经网络方法,它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习速率。Adam算法伪代码如下:

Adam算法的PyTorch实现如下。

6.8 Yogi算法

Adam算法综合了动量算法及自适应算法的优点,是深度学习常用的算法,但也存在一些问题:即使在凸环境下,当 r的第二力矩估计值爆炸时,它可能无法收敛。为此可通过改进r和优化参数初始化等方法来解决。 其中通过改进r是一种有效方法,即把r\longleftarrow \rho_2 r +(1-\rho_2) \hat g^2(等价于r\longleftarrow r +(1-\rho_2)(\hat g^2-r)\hat g^2-r改为\hat g^2\odot sgn(\hat g^2-r),这就是Yogi更新。
Yogi算法的PyTorch代码如下:

前文介绍了深度学习的正则化方法,它是深度学习核心之一;优化算法也是深度学习的核心之一。优化算法很多,如随机梯度下降法、自适应优化算法等,那么具体使用时该如何选择呢?
RMSprop、Nesterov、Adadelta和Adam被认为是自适应优化算法,因为它们会自动更新学习率。而使用SGD时,必须手动选择学习率和动量参数,通常会随着时间的推移而降低学习率,下图是这些优化算法之间的关系:
图6-13 改进梯度下降法之间的关系
有时可以考虑综合使用这些优化算法,如采用先用Adam,然后用SGD优化方法,这个想法,实际上由于在训练的早期阶段SGD对参数调整和初始化非常敏感。因此,我们可以通过先使用Adam优化算法进行训练,这将大大节省训练时间,且不必担心初始化和参数调整,一旦用Adam训练获得较好的参数后,我们可以切换到SGD +动量优化,以达到最佳性能。采用这种方法有时能达到很好效果,如图6-14所示,迭代次数超过150后,用SGD效果好于Adam。

图6-14迭代次数与测试误差间的对应关系
注意算法比较:
图6-15 多种改进算法运行效果比较
从图6-15可知,SGD迭代一定次数后,收敛很慢;Momentum要好于SGD,但Adma下降最快,而且性能也最好。

6.9 权重初始化

在机器学习中,如何初始化权重非常重要,如果初始化不当,将直接导致无法收敛。比例如果把权重初始化为0或全为一样的值,多一个多维矩阵,如一个mxn的权重参数w,在误差反向传播时,所以的权重都会进行相同的更新,通过多次迭代后,可能更新为相同的值,这就严重偏离了源数据分布。所以为防止权重均一化,必须随机生成初始值(一般使随机初始化的数据满足正态分布)。
是否我们就一概为把权重参数都随机生成就成了呢?在实际操作时,我们还需要进一步分析,权重参数的正向传播和反向传播过程中,还受激活函数的影响。一般来说,对对称型的激活函数,如果sigmoid、softmax、tanh等,对随机生成的满足正态分布的数据,还需要考虑前一层的网络节点数(假设前一层的网络层输出节点数为n),如下图所示。使正态分布的标准差除以√(n+m) ,以使数据呈现更稳定和更有代表性,这种初始化方法称为Xavier初始化。
图6-16 权重初始化的输入节点、输出节点数
对于非对称的激活函数(如ReLU、Leak-ReLU等),因这些激活函数本身偏置(如ReLU激活函数把小于0的都置为0),为使数据更有广度、还需要对标准差惩罚力度减少一点,一般使参数初始化的范围比原来扩大一倍,这种初始化方法称为Kaiming_He初始化。以下是各种初始化小结:
【说明】其中m、n分别表示权重矩阵W的输入、输出节点数,详细含义请参考上图。
如果对卷积核参数的初始化,PyTorch中缺省采用Kaiming_He初始化中均匀分布。大家可从conv.py源码中找到:
init.kaiming_uniform_(self.weight, a=math.sqrt(5))
偏置的初始化源码为:
bound = 1 / math.sqrt(fan_in)
init.uniform_(self.bias, -bound, bound)
(1)PyTorch代码实现:
•Xavier初始化:
torch.nn.init.xavier_normal_(tensor, gain=1)
torch.nn.init.xavier_uniform_(tensor, gain=1)
•Kaiming_He初始化:
torch.nn.init.kaiming_uniform_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu')
torch.nn.init.kaiming_normal_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu')
(2)NumPy实现
np.random.uniform(-r,r)
np.random.randn(n,m)*σ