优化器总结

优化器总结
Holi本文参考:https://juejin.cn/post/7084409806492008456
设代优化的模型参数为$\theta$,目标函数(损失函数)为$J(\theta)$,学习率为$\eta$,迭代周期为$t$,损失函数$J(\theta)$关于当前参数$\theta$的梯度为$g_t=\nabla_\theta J(\theta)$。
梯度下降(Gradient Descent)
参数更新方法为:
该方法存在如下的一些缺点:
- 训练速度慢:在大规模数据集上进行训练时,每输入一个样本都要进行一次参数更新,而每次迭代都要遍历所有的样本。
- 容易陷入局部解:如果走到局部的洼地,可能会认为已经达到最低点。
批量梯度下降(Batch Gradient Descent)
不像标准GD那样,对每个样本输入都进行参数更新,而是直接对所有数据的输入进行参数更新(相当于单独计算每个样本的梯度,然后将其求平均):
由于每一步迭代都使用了全部的样本,因此每次下降的方向为整体的平均梯度,收敛过程会比较稳定。
缺点是每次更新会用到所有的样本计算梯度,计算起来很慢。
随机梯度下降(Stochastic Gradient Descent)
每次更新不需要计算整个数据样本集的梯度,而是每次参数更新时仅仅选取一个样本$(x_i,y_i)$计算其梯度,参数更新公式为:
SGD由于每次参数更新仅仅需要计算一个样本的梯度,因此训练速度很快,但是由于不是每次都向着整体最优化方向,导致梯度下降波动很大,更容易从一个局部最优跳到另一个局部最优,导致准确度下降。有论文提到,当缓慢降低学习率时,SGD会显示与BGD相同的收敛行为,几乎一定会收敛到局部或全局最小值。
优点:
- 由于每次迭代只使用了一个样本计算梯度,训练速度快,包含一定随机性,但是从期望来看,每次计算的梯度基本是正确的导数的。虽然看起来SGD波动非常大,会走很多弯路,但是对梯度的要求很低(计算梯度快),而且对于引入噪声,大量的理论和实践工作证明,只要噪声不是特别大,SGD都能很好地收敛。
- 应用大型数据集时,训练速度很快。比如每次从百万数据样本中,取几百个数据点,算一个SGD梯度,更新一下模型参数。相比于标准梯度下降法的遍历全部样本,每输入一个样本更新一次参数,要快得多。
缺点:
- 更新频繁,带有随机性,会造成损失函数在收敛过程中严重震荡。SGD没能单独克服局部最优解的问题(主要)。
- SGD在随机选择梯度的同时会引入噪声,使得权值更新的方向不一定正确(次要)。
小批量梯度下降(Mini-batch Gradient Descent)
该算法是BGD和SGD的折中,对于一个含有$n$个训练样本的数据集,每次参数更新选择一个大小为$m$的小批量计算其平均梯度,参数更新公式为
这样既保证了训练的速度,也保证了最后收敛的准确率,目前SGD一般默认为小批量梯度下降。
优点:
- 可以降低参数更新时的方差,收敛更稳定,另一方面可以充分地利用深度学习库中高度优化的矩阵操作来进行更有效的梯度计算。
缺点:
- Mini-batch gradient descent 不能保证很好的收敛性,learning rate 如果选择的太小,收敛速度会很慢,如果太大,loss function 就会在极小值处不停地震荡甚至偏离(有一种措施是先设定大一点的学习率,当两次迭代之间的变化低于某个阈值后,就减小 learning rate,不过这个阈值的设定需要提前写好,这样的话就不能够适应数据集的特点)。对于非凸函数,还要避免陷于局部极小值处,或者鞍点处,因为鞍点所有维度的梯度都接近于0,SGD 很容易被困在这里(会在鞍点或者局部最小点震荡跳动,因为在此点处,如果是BGD的训练集全集带入,则优化会停止不动,如果是mini-batch或者SGD,每次找到的梯度都是不同的,就会发生震荡,来回跳动)。
- SGD对所有参数更新时应用同样的 learning rate,如果我们的数据是稀疏的,我们更希望对出现频率低的特征进行大一点的更新, 且learning rate会随着更新的次数逐渐变小。
动量优化法(Momentum)
在梯度下降公式中,存在两个改进点,一是学习率,二是梯度。其分别衍生出自适应(adaptive)学习率方法与动量(momentum)方法
动量优化法引入了动量的概念,假如小球处于坡度上时,其会不断加速滚到坡底,当其反冲上坡时则会不断减速。带有动量的小球不但可以加速梯度下降,还可以借助积累的动量,冲过小坡,避免落入局部最优点。【其实引入动量的话也避免了在病态情形下的剧烈震荡】
Momentum
梯度下降容易被困在局部最小的沟壑来回震荡,并且收敛速度仍然不够快。动量法可以一定程度上解决该问题。
momentum的思想:在参数更新时一定程度上保留之前更新的方向,同时又利用当前的批次的方向微调最终的更新方向,即通过积累此前的动量来加速当前的梯度。
SGD只使用了当步参数的梯度,随机性较大。如果将历次迭代的梯度按比例融合,可能更加稳定、更有利于跳出局部最优。
设$m_t$为$t$时刻的动量,$\gamma$表示动量因子,通常取值$0.9$或近似值,在SGD的基础上增加动量,则参数更新公式为
一阶动量$m_t$为各个时刻梯度方向的指数移动平均值,约等于最近$\frac{1}{(1-\gamma)}$个时刻的梯度向量和的平均值。也就是说,$t$时刻的下降方向,不仅由当前点的梯度方向决定,而且由此前累积的下降方向决定。
动量因子 γ\gammaγ 的经验值为0.9,这就意味着下降方向主要是此前累积的下降方向,并略微偏向当前时刻的下降方向。在梯度方向改变时,momentum能够降低参数更新速度,从而减少震荡,在梯度方向相同时,momentum可以加速参数更新, 从而加速收敛。
动量主要解决SGD的两个问题:
- 随机梯度的方法(引入的噪声)
- Hessian矩阵病态问题(可以理解为SGD在收敛过程中和正确梯度相比来回摆动比较大的问题)
优点:
- 前后梯度一致的时候能够加速学习;前后梯度不一致的时候能够抑制震荡,越过局部极小值(加速收敛,减小震荡)
缺点:
- 增加了一个超参数
NAG(Nesterov accelerated gradient)
为了增强探索性,进一步引入了 nesterov 动量。momentum保留了上一时刻的梯度 $\nabla_\theta J(\theta)$,对其没有进行任何改变,NAG是momentum的改进版,在梯度更新时做了一个矫正,具体做法是在当前的梯度$\nabla_\theta J(\theta)$上添加上一个时刻的动量$\gamma\cdot m_t$,梯度改变为$\nabla_\theta J(\theta-\gamma\cdot m_t)$
加上nesterov项后,梯度在大的跳跃后,进行计算对当前梯度进行校正。momentum首先计算一个梯度,然后在加速更新梯度的方向进行一个大的跳跃,nesterov项首先在之前加速的梯度方向进行一个大的跳跃,计算梯度然后进行校正。
Nesterov动量梯度的计算在模型参数施加当前速度之后,因此可以理解为往标准动量中添加了一个校正因子。在凸批量梯度的情况下,Nesterov动量将额外误差收敛率从$O(1/k)$(k步后) 改进到$O(1/k^2)$,然而,在随机梯度情况下,Nesterov动量对收敛率的作用却不是很大。
自适应学习率优化算法
传统优化算法往往将学习率设置为常数,或者根据训练次数调节学习率,然而学习率对模型性能有着显著的影响,因此上述策略有些僵化,最好可以动态地更新学习率。
使用统一的全局学习率的缺点:
- 对于某些参数,通过算法已经优化到了极小值附近,但是有的参数仍然有着很大的梯度。
- 如果学习率太小,则梯度很大的参数会有一个很慢的收敛速度; 如果学习率太大,则已经优化得差不多的参数可能会出现不稳定的情况。 解决方案:对每个参与训练的参数设置不同的学习率,在整个学习过程中通过一些算法自动适应这些参数的学习率。 如果损失与某一指定参数的偏导的符号相同,那么学习率应该增加; 如果损失与该参数的偏导的符号不同,那么学习率应该减小。
AdaGrad(Adaptive Gradient)
该方法对学习率进行了约束,对于经常更新的参数,我们已经积累了大量的相关知识,不希望被单个样本影响太大,希望学习速率慢一些;对于偶尔更新的参数,我们了解的信息太少,希望能从每个偶然出现的样本(稀疏特征的样本)身上多学一些,即学习速率大一些。而该方法中开始使用二阶动量,才意味着“自适应学习率”优化算法时代的到来。
AdaGrad 算法,独立地适应所有模型参数的学习率,缩放每个参数反比于其所有梯度历史平均值总和的平方根。
- 具有损失函数最大梯度的参数相应地有个快速下降的学习率。
- 而具有小梯度的参数在学习率上有相对较小的下降。
其梯度更新公式为
其中,$g_{t,i}$为$t$时刻$\theta_i$的梯度
如果是普通的SGD,那么每一个时刻的梯度更新公式为
不过可以看到AdaGrad的更新公式中,矫正的学习率$\frac{\eta}{\sqrt{G_{t,ii}+\epsilon}}$也随着$t$和$i$而变化,也就是所谓的“自适应”。(小平滑项$\epsilon$是为了防止分母为零)
上式中的$G_t$为对角矩阵,$(i,i)$元素就是到$t$时刻为止,参数$\theta_i$的累积梯度平方和,也就是“二阶动量”。$\sqrt{G_{t,ii}+\epsilon}$是恒大于0的,而且参数更新越频繁,二阶动量就越大,学习率$\frac{\eta}{\sqrt{G_{t,ii}+\epsilon}}$就越小,所以在稀疏的数据场景下表现比较好。
优点:
- 自适应的学习率,可以减少人工调节
缺点:
- 仍需要手工设置一个全局学习率$\eta$,如果设置过大的话,会使regularizer过于敏感,对梯度的调节太大
- 中后期,分母上梯度累加的平方和会越来越大,使得参数更新量趋于0,训练提前结束,无法进一步学习
RMSprop
由于AdaGrad调整学习率变化过于激进,我们考虑一个改变二阶动量计算方法的策略:不累积全部历史梯度,而只关注过去一段时间窗口的下降梯度,即RMSprop只累加固定大小的项,并且也不直接存储这些项,仅仅是近似计算对应的平均值(指数移动平均值),这就避免了二阶动量持续累积、导致训练过程提前结束的问题了,参数更新公式如下
与AdaGrad相比,分母的$G$变成了过去梯度平方的衰减平均值(指数衰减平均值)。其中$E$的计算公式如下,$t$时刻的值依赖于前一时刻的平均和当前的梯度:
这个分母相当于梯度的均方根,所以可以用RMS简写为
优点:
- 进一步发展了AdaGrad,缓解了学习率过低的问题
缺点:
- 仍然依赖于全局学习率$\eta$
Adadelta
可以将RMSprop中的学习率$\eta$换成$RMS[\Delta\theta]$,这样的话,就不需要提前设定学习率了
优点:
- 不依赖全局学习率
- 训练前中期,加速效果不错,很快
缺点:
- 训练后期,反复在局部最小值附近抖动
Adam(Adaptive Moment Estimation)
Adam 结合了前面方法的一阶动量和二阶动量,相当于 Ada + Momentum,SGD-M和NAG在SGD基础上增加了一阶动量,AdaGrad和AdaDelta在SGD基础上增加了二阶动量。
Adam 除了像 Adadelta 和 RMSprop 一样存储了过去梯度的平方$v_t$的指数衰减平均值 ,也像 momentum 一样保持了过去梯度$m_t$的指数衰减平均值:
如果$m_t$和$v_t$被初始化为 0向量,那么它们就会向0偏置,所以做了偏差校正 ,通过计算偏差校正后的$m_t$和$v_t$来抵消这些偏差
最终的更新公式为
优点:
- Adam梯度经过偏置校正后,每一次迭代学习率都有一个固定范围,使得参数比较平稳。
- 结合了Adagrad善于处理稀疏梯度和RMSprop善于处理非平稳目标的优点。
- 为不同的参数计算不同的自适应学习率。
- 也适用于大多非凸优化问题——适用于大数据集和高维空间。
缺点:
- Adam 使用动量的滑动平均,可能会随着训练数据变化而抖动比较剧烈,在online场景可能波动较大,在广告场景往往效果不如 AdaGrad。
Nadam
Adam遗漏了Nesterov项,因此在Adam的基础上,加上Nesterov项就是Nadam了,参数更新公式如下:
其中
Nadam对学习率有更强的约束,同时对梯度的更新也有更直接的影响。一般而言,在使用带动量的RMSprop或Adam的问题上,使用Nadam可以取得更好的结果。
AdamW
Adam有很多的优点,但是在很多数据集上的最好效果还是用SGD with Momentum细调出来的。可见Adam的泛化性并不如SGD with Momentum。有论文提出其中一个重要原因就是 Adam中L2正则化项并不像在SGD中那么有效。
L2正则和Weight Decay在Adam这种自适应学习率算法中并不等价,只有在标准SGD的情况下,可以将L2正则和Weight Decay看做一样。特别是,当与自适应梯度相结合时,L2正则化导致具有较大历史参数和/或梯度幅度的权重比使用权重衰减时更小。
使用Adam优化带L2正则的损失并不有效,如果引入L2正则化项,在计算梯度的时候会加上正则项求梯度的结果。正常的权重衰减是对所有的权重都采用相同的系数进行更新,本身比较大的一些权重对应的梯度也会比较大,惩罚也越大。但由于Adam计算步骤中减去项会有除以梯度平方的累积,使得梯度大的减去项偏小,从而具有大梯度的权重不会像解耦权重衰减那样得到正则化。 这导致自适应梯度算法的L2和解耦权重衰减正则化的不等价。
而在常见的深度学习库中只提供了L2正则,并没有提供权重衰减的实现。这可能就是导致Adam跑出来的很多效果相对SGD with Momentum有偏差的一个原因
AdamW 使用了严谨的 weight decay(非L2正则),即权重衰减不参与一、二动量计算,只在最后的更新公式中使用。其更新公式如下: