optimization
模型优化方法的选择直接关系到最终模型的性能。有时候效果不好,未必是特征的问题或者模型设计的问题,很可能是优化算法的问题,而且好的优化算法还能够帮助加速训练模型。
深度学习模型的发展进程:
SGD -> SGDM ->NAG -> AdaGrad -> AdaDelta -> Adam -> Nadam
1.整体框架
首先定义:
待优化参数$w$, 目标函数:$f(w)$,学习率$\alpha$
每次迭代:
计算目标函数关于参数的梯度:$gt=▽f(wt)$
根据历史梯度计算一阶动量和二阶动量:
$mt=ϕ(g1,g2,…,gt),Vt=ψ(g1,g2,…,gt)$
计算当前时刻的下降梯度:$ηt=α⋅m_t / \sqrt{V_t}$
根据下降梯度对参数进行更新:$wt+1=wt−ηt$
步骤3、4对于各个算法都是一致的,主要的差别就体现在1和2上。
1. 梯度下降法SGD
最基本的优化方法,沿着负梯度的方向更新参数,实现如下:
1 | # 梯度下降法 |
其中learning_rate是超参数代表学习率,被更新的变量为w,其梯度为dw,梯度->位置,很好理解。
缺点:
- 有可能会陷入局部最小值,容易被困在鞍点;
- 容易产生震荡不收敛,最终会一直在最小值附近波动,并不会达到最小值并停留在此;
- 下降速度慢,迟迟不能到达全局最优值;
- 选择合适的learning rate比较困难;
- 在所有方向上统一的缩放梯度,不适用于稀疏数据
2. 动量法Momentum
动量法是一类从物理中的动量获得启发的优化方法,可以简单理解为:当我们将一个小球从山上滚下来时,没有阻力的话,它的动量会越来越大,但是如果遇到了阻力,速度就会变小。实现如下:
1 | # 动量法 |
变量v的初始值被定为0,超参数mu在优化过程中被视为动量,其物理意义可以视为摩擦系数,加入的这一项,可以使得梯度方向不变的维度上速度变快,梯度方向有所改变的维度上的更新速度变慢,这样就可以加快收敛并减小震荡。和之前不同的是梯度不会直接对位置造成影响,梯度->速度->位置。
优点:
- 增加了稳定性;
- 收敛速度更快;
- 还有一定摆脱局部最优的能力。
3. RMSprop
RMSprop(Root Mean Square prop)是一种自适应学习率方法,依旧是基于梯度对位置进行更新。为了消除梯度下降中的摆动,加入了梯度平方的指数加权平均。梯度大的指数加权平均就大,梯度小的指数加权平均就小,保证各维度的梯度都在一个良机,进而减少摆动。
关于指数加权平均的通俗理解可以参考https://zhuanlan.zhihu.com/p/29895933
1 | # RMSprop |
其中decay_rate和eps都是超参数,每一步的变量cache的值都不同,所以可以看做自适应得对学习率进行调整。
还有一些其他效果较好的优化器,由于这些前置知识已经足够理解Adam了,所以在此不做过多介绍。
Adam
Adam可以看做动量法和RMSprop的结合
1 | # Adam |
对于m和v的处理,同样使用了指数加权平均。相比于RMSprop,梯度换为了平滑的m,而cache的处理基本没有变化。超参数beta1和beta2的初始值接近于1,因此,计算出的偏差项接近于0。
AdamW
AdamW是在Adam+L2正则化的基础上进行改进的算法。
使用Adam优化带L2正则的损失并不有效。如果引入L2正则项,在计算梯度的时候会加上对正则项求梯度的结果。那么如果本身比较大的一些权重对应的梯度也会比较大,由于Adam计算步骤中减去项会有除以梯度平方的累积,使得减去项偏小。按常理说,越大的权重应该惩罚越大,但是在Adam并不是这样。而权重衰减对所有的权重都是采用相同的系数进行更新,越大的权重显然惩罚越大。在常见的深度学习库中只提供了L2正则,并没有提供权重衰减的实现。
Adam+L2 VS AdamW
图片中红色是传统的Adam+L2 regularization的方式,绿色是Adam+weightdecay的方式。可以看出两个方法的区别仅在于“系数乘以上一步参数值“这一项的位置。再结合代码来看一下AdamW的具体实现。
以下代码来自https://github.com/macanv/BERT-BiLSTM-CRF-NER/blob/master/bert_base/bert/optimization.py中的AdamWeightDecayOptimizer中的apply_gradients函数中,BERT中的优化器就是使用这个方法。在代码中也做了一些注释用于对应之前给出的Adam简化版公式,方便理解。可以看出update += self.weight_decay_rate * param这一句是Adam中没有的,也就是Adam中绿色的部分对应的代码,weightdecay这一步是是发生在Adam中需要被更新的参数update计算之后,并且在乘以学习率learning_rate之前,这和图片中的伪代码的计算顺序是完全一致的。总之一句话,如果使用了weightdecay就不必再使用L2正则化了。
1 | # m = beta1*m + (1-beta1)*dx |
原有的英文注释中也解释了Adam和传统Adam+L2正则化的差异,好了到这里应该能理解Adam了,并且也能理解AdamW在Adam上的改进了。
Lookahead,RAdam?
Lookahead和RAdam都是比较新的优化器,具体原理在此不过多介绍。但是我有疑问需要大神来解答一下。
在BERT中引入优化器的源码中有这样一句注释
1 | # It is recommended that you use this optimizer for fine tuning, since this |
也就是说在微调BERT的时候强烈建议使用AdamW优化器。在自己的NER数据集上使用6层BERT,AdamW能得到98%左右的F1值,我尝试使用了RAdam,Lookahead+RAdam和Lookahead+AdamW,还有Ranger,得到的效果都非常差,要不就0的F1值,要不就是30%左右,好像完全没有效果。
项目参考源码:https://github.com/macanv/BERT-BiLSTM-CRF-NER
RAdam,Lookahead:https://github.com/lifeiteng/Optimizers
https://github.com/michaelrzhang/lookahead
Ranger:https://github.com/jyhengcoder/Ranger_tensorflow
LazyAdam
它是Adam optimizer的变种,可以更有效地处理稀疏更新(sparse updates)。
原始的Adam算法为每个训练变量(tranable variable)维护着两个moving-average累加器;该累加器会在每个step上被更新。该class为稀疏变量(sparse variables)的梯度更新提供了lazier handling机制。它只会为出现在当前batch中的稀疏变量(sparce variable indices)更新移动平均累积,而非为所有indices更新累积。对比原始的Adam optimizer,它可以为一些应用在模型训练吞吐上提供大的提升。然而,它与原始Adam算法有一些不同的语义,可能会导致不同的期望结果(empirical results)。
注意,当前不支持amsgrad,该参数只能为False。
参考资料
https://www.zhihu.com/question/323747423/answer/790457991
https://www.cnblogs.com/guoyaohua/p/8542554.html
https://zhuanlan.zhihu.com/p/63982470
https://zhuanlan.zhihu.com/p/38945390
https://www.jianshu.com/p/e17622b7ffee
https://blog.csdn.net/yinyu19950811/article/details/90476956