本文参加新星计划人工智能(Pytorch)赛道:https://bbs.csdn.net/topics/613989052
写在前言:
前半部分: 本文首先会介绍优化器的发展历程,以及每个优化器有什么特点,解决了什么问题,同时又存在什么问题,后续的改进有哪些,循序渐进,让你学会如何在自己的任务中游刃有余的使用优化器。 后半部分: 代码的角度,以 pyotrch框架为例,从源码的角度带你理解优化器的由来,实现,作用。
梯度下降法是最受欢迎的优化算法之一,目前为止普遍用于优化神经网络。与此同时,每个机器学习框架都包含各种各样梯度下降优化算法的实现。
pytorch的优化器:管理并更新模型中可学习参数的值,使得模型输出更接近真实标签。
基本概念:
导数:函数在指定坐标轴上的变化率
方向导数:指定方向上的变化率
梯度:一个向量,方向为方向导数取得最大的方向
关于三者的关系的直观解释可以看这里
梯度下降是通过在在目标函数的梯度的反方向去更新网络所学习的参数,学习率η决定了目标函数达到局部最优所需要的步数。其中下面的所有公式中模型参数表示为为θ,损失函数表示为J(θ),损失函数J(θ)关于参数θ的偏导数表示为,学习率表示为η。
梯度下降法目前主要是三种方法:区别在于每次参数更新时计算的样本数量不同,批量梯度下降法(BGD,Batch Gradient Descent),随机梯度下降法(SGD, Stochastic Gradient Descent)及小批量梯度下降法(Mini-batch Gradient Descent)。
批量梯度下降计算代价函数的梯度,并一次性对整个数据集参数θ进行更新。
由上式可以看出,每进行一次参数更新,需要计算整个数据样本集,因此导致批量梯度下降法的速度比较慢,尤其是数据集非常大的情况下,收敛速度会非常的慢;一次性将整个数据集加载进内存,非常消耗内存;但是每次的下降方向为总体平均梯度,他得到的会是一个全局最优解。
随机梯度下降每一次随机对一个训练样本计算梯度,并更新参数θ
批处理梯度下降对大型数据集执行冗余计算,因为它在每次参数更新之前为类似的例子重新计算梯度。SGD通过一次执行一次更新来消除这种冗余。因此,它通常速度更快,也可以用来在线学习。SGD以高方差执行频繁的更新,导致目标函数剧烈波动(震荡下行),如图中所述
小批量梯度下降算法,选择了一个折中的方案,即每次只取整个数据集的一小部分(n个样本)送入网络,计算代价函数,并进行参数更新,是目前普遍采用的方法。
优点:
减少了参数更新时的方差,即缓解了代价函数下降曲线出现剧烈震荡的现象
挑战:
选择合适的lr比较困难,学习率太低会收敛变慢,学习率过高会使收敛时发生剧烈震荡
学习率调整策略需要预先定义,无法适应数据集的特征,例如每个一定的迭代周期将学习率减小一点。
所有的参数都是用同样的lr,当数据是稀疏的并且我们的特征有着非常不同的频率时,我们希望能够不同程度的更新参数,罕见的特征我们进行较大程度的更新,频繁出现的特征进行小步幅的更新。
SGD容易收敛到局部最优,并且在某些情况下可能被困在鞍点。
为了解决上面的问题,后面的优化算法就提出了基于动量的一系列算法。这里不会去讨论不适合对高维数据集进行参数优化的算法(牛顿法(基于hessian矩阵))
动量优化方法引入物理学中的动量思想,加速梯度下降,有Momentum和Nesterov两种算法。当我们将一个小球从山上滚下来,没有阻力时,它的动量会越来越大,但是如果遇到了阻力,速度就会变小,动量优化法就是借鉴此思想,使得梯度方向在不变的维度上,参数更新变快,梯度有所改变时,更新参数变慢,这样就能够加快收敛并且减少动荡。
SGD without momentum SGD with momentum
momentum算法思想:参数更新时在一定程度上保留之前更新的方向,同时又利用当前batch的梯度微调最终的更新方向,简言之就是通过积累之前的动量来加速当前的梯度。假设 表示t时刻的动量,γ 表示动量因子,通常取值0.9或者近似值,在SGD的基础上增加动量,则参数更新公式如下:
在梯度方向改变时,momentum能够降低参数更新速度,从而减少震荡; 在梯度方向相同时,momentum可以加速参数更新, 从而加速收敛。总而言之,momentum能够加速SGD收敛,抑制震荡。
Nesterov旨在在加速的同时能够对下一步的梯度方向有预见性,这样能够确保当代价函数再向再次变得倾斜之前进行减速。计算θ − γvt−1可以给到我们参数更新后大概的位置。
通常我们将动量因子γ设为0.9。而动量方法首先计算当前梯度(小蓝色向量),然后采取一个大跳跃的方向更新的累积梯度(大蓝色向量),NAG首先做一个大跳跃的方向之前的累积梯度(棕色向量),测量梯度,然后进行修正(红色向量),导致完整的NAG更新(绿色向量)。这种预期的更新阻止了我们走得太快,并导致了响应性的增加,这显著提高了rnn在许多任务上的性能。
现在我们可以根据代价函数的梯度来调整我们的更新并加速SGD,现在我们想根据每个单独的参数来调整我们的更新,即根据参数的重要性来执行更大或者更小的更新。
在机器学习中,学习率是一个非常重要的超参数,但是学习率是非常难确定的,虽然可以通过多次训练来确定合适的学习率,但是一般也不太确定多少次训练能够得到最优的学习率,玄学事件,对人为的经验要求比较高,所以是否存在一些策略自适应地调节学习率的大小,从而提高训练速度。 目前的自适应学习率优化算法主要有:AdaGrad算法,RMSProp算法,Adam算法以及AdaDelta算法。
Adagrad可以根据参数自适应的调整学习率。频繁出现的特征对应的参数对应小的更行步幅(小的学习率),罕见的特征对应的参数对应更大的步幅(大的学习率),基于这一特点,该算法非常适用于稀疏的数据。
在之前的算法中,我们在一次对所有参数θ的更新中适用相同的学习率η,而Adagrad在每一时间步t的更新中,每一个参数都会对应一个不同的学习率。我们首先展示Adagrad算法每一个参数的更新方式,然后展示整个响亮的更新方式。为了简洁起见,我们使用
表示时间步t时的梯度。
表示参数
在时间步t时的偏导数:
SGD在时间步t下对参数的更新表示为:
在更新规则中,Adagrad一改前观,它会基于每一个时间步t下的每一个参数之前的梯度来改变学习率η:
∈
在这里是一个对角矩阵,其中每个元素i是直到时间步t的每一个时间步的代价函数梯度的平方。ε是为了防止分母为零的一个趋于零的参数(通常为1e-8),注意这里的开根号可能是实验性的探索,因为通过实验表示没有根号的情况下,该算法的性能会差很多。
因为向量矩阵包含所有参数过去时间步的梯度平方总和,我们现在可以通过对
和
进行向量乘积(⊙)方法实现Adagrad的整体向量表示:
优点:
消除了手动调整学习速率的需要。大多数实现使用默认值0.01,并保持该值。
缺点:
仍需要手工设置一个全局学习率 η , 如果η 设置过大的话,会使regularizer过于敏感,对梯度的调节太大。中后期,分母上梯度累加的平方和会越来越大,使得参数更新量趋近于0,使得训练提前结束,无法学习。
Adagrad会累加之前所有的梯度平方,而Adadelta只累加固定大小的项,并且也不直接存储这些项,仅仅是近似计算对应的衰减平均值。Adadelta在时间步t的梯度衰减平均值(类似于动量的影响因子γ)只和过去的
和目前的梯度
有关:
我们设置了一个与动量影响因子相似的值,约为0.9,为清晰起见,再次回顾SGD参数更新向量:
因此,我们之前推导出的Adagrad的参数更新向量的形式为:
我们现在只是简单地用过去的平方梯度上的衰减平均值来代替对角线矩阵:
由于分母是梯度的均方根(RMS)误差准则,我们可以将公式简化为:
作者注意到,本次更新中的单元(以及SGD、动量或Adagrad)中的单元不匹配,即更新中应该有与参数相同的假设单位。为了实现这一点,他们首先定义了另一个指数衰减的平均值,这次不是梯度的平方,而是参数的平方更新:
因此,参数更新的均方根误差为:
由于是未知的,我们用直到上一个时间步长参数更新的均方根来近似它。将之前的更新规则中的学习速率替换为
,最终生成Adadelta更新规则:
特点:
不需要设置学习率初值了。
训练初中期,加速效果不错,很快。
训练后期,反复在局部最小值附近抖动。
RMSprop和Adadelta都是在同一时间独立开发的,因为需要解决Adagrad急剧下降的学习率。RMSprop实际上与我们上面推导的Adadelta的第一个更新向量相同:
RMSprop还将学习速率除以平方梯度的指数衰减平均值。Hinton建议设置为0.9,而学习率的良好默认值为0.001。
特点:
其实RMSprop依然依赖于全局学习率 η
RMSprop算是Adagrad的一种发展,和Adadelta的变体,效果趋于二者之间
适合处理非平稳目标(包括季节性和周期性)对于RNN效果很好
自适应矩估计(Adam)是另一种计算每个参数的自适应学习率的方法。除了像Adadelta和RMSprop那样存储过去平方梯度的的指数衰减平均值之外,Adam还保持了过去梯度
的指数衰减平均值,类似于动量。而动量可以看作是一个沿着斜坡运行的球,亚当表现得像一个有摩擦的重球,因此更喜欢在误差表面有平坦的最小值。我们分别计算过去和过去的平方梯度的衰减平均值,如下
Adam中动量直接并入了梯度一阶矩(指数加权)的估计。其次,相比于缺少修正因子导致二阶矩估计可能在训练初期具有很高偏置的RMSProp,Adam包括偏置修正,修正从原点初始化的一阶矩(动量项)和(非中心的)二阶矩估计。
然后他们使用这些参数来更新参数,就像我们在Adadelta和RMSprop中看到的那样,它产生了Adam更新规则:
默认参数值设定为:
默认值为0.9,
为0.999, ε为1e-8。他们的经验表明,Adam在实践中工作得很好,并优于其他自适应学习方法算法。
特点:
Adam梯度经过偏置校正后,每一次迭代学习率都有一个固定范围,使得参数比较平稳。
结合了Adagrad善于处理稀疏梯度和RMSprop善于处理非平稳目标的优点
为不同的参数计算不同的自适应学习率
也适用于大多非凸优化问题——适用于大数据集和高维空间。
正如我们之前所看到的,Adam可以被看作是RMSprop和动量的组合: RMSprop贡献了过去平方梯度的指数衰减平均值,而动量解释了过去梯度的指数衰减平均值。我们还看到,Nesterov accelerated gradient(NAG)优于普通的动量。
Nadam(Nesterov-accelerated Adaptive Moment Estimation)因此结合了Adam和NAG。为了将NAG纳入Adam,我们需要修改它的动量项。
首先,让我们回顾一下使用我们当前的符号使用的动量更新规则:
其中J是我们的目标函数,γ是动量衰减影子,η是我们的学习率。展开第三个方程:
这再次表明,动量包括在上一个动量矢量的方向上迈出一步,以及在现在梯度的方向上迈出一步。
然后,NAG允许我们在计算梯度之前,通过使用动量步长更新参数,在梯度方向上执行更准确的步骤。因此,我们只需要修改梯度就可以得到NAG:
Dozat建议用以下方式修改NAG:而不是应用两次动量步骤:一次用于更新梯度,第二次用于更新参数
。我们现在直接应用前瞻性动量向量来更新当前参数:
注意,我们现在不再将上面先前的动量向量用于扩展的动量更新规则方程,而是使用当前的动量向量
来展望未来。为了增加 Nesterov动量,我们可以同样地用当前的动量矢量代替先前的动量矢量。首先,请记住,Adam更新规则如下(注意,我们不需要修改
):
使用上面和
的定义,来展开第三个公式,去除
项,得到:
注意,恰好是前一个时间步长的动量向量的偏置修正估计。因此,我们可以使用
来代替:
注意,为了简单起见,我们忽略了分母是,而不像我们将在下一步替换分母
那样。这个方程看起来再次与我们上面的扩展动量更新规则非常相似。我们现在可以添加内Nesterov,就像我们之前一样,简单地用当前动量向量
的偏修正估计替换前一步动量向量
的偏修正估计,这给了我们Nadam更新规则:
对于稀疏数据,尽量使用学习率可自适应的优化方法,不用手动调节,而且最好采用默认值。
SGD通常训练时间更长,但是在好的初始化和学习率调度方案的情况下,结果更可靠。
如果在意更快的收敛,并且需要训练较深较复杂的网络时,推荐使用学习率自适应的优化方法。
Adadelta,RMSprop,Adam是比较相近的算法,在相似的情况下表现差不多。
在想使用带动量的RMSprop,或者Adam的地方,大多可以使用Nadam取得更好的效果。
在第一张图中,我们看到了它们在损失面轮廓上的行为。Adagrad、Adadelta和RMSprop几乎立即向正确的方向前进,并以相似的速度收敛,而动量和NAG被引导出轨道,让人想起一个球从山上滚下来的图像。然而,NAG能够迅速纠正它的路线,因为它的响应能力,通过展望和走向最低。
第二张图显示了算法在鞍点上的行为,即一个维有正斜率,而另一个维有负斜率,这像我们前面提到的给SGD带来了困难。请注意,SGD、动量和NAG发现很难打破对称性,尽管后者最终设法逃脱了鞍点,而Adagrad、RMSprop和Adadelta迅速向下进入负斜坡。
正如我们所看到的,自适应学习速率方法,即Adagrad、Adadelta、RMSprop和Adam是最合适的,并为这些场景提供了最好的收敛。
参考链接:
https://www.zhihu.com/question/36301367
https://www.ruder.io/optimizing-gradient-descent/
https://zhuanlan.zhihu.com/p/22252270