引言
最近依旧在做命名实体识别的任务,一直在想如何能在保证效率的前提下,提升BERT+BiLSTM+CRF这个主流模型的准确率。“ 达观杯 ”的获奖方案中有的队伍使用了Lookahead+Adam的优化器,所以我也打算从优化器的方向入手看看能否有效果的提升。本以为BERT中使用的是用烂了的Adam,一看源码发现是重写的优化器,叫AdamWeightDecayOptimizer,本来Adam都没太搞懂,又来一个WeightDecay,一起学一下吧。
不查不知道,一查吓一跳,2014年被提出的Adam优化器的收敛性被证明是错误的,之前大部分机器学习框架中对于Adam的权重衰减的实现也都是错误的。关注其收敛性的论文也获得了ICLR 2017的Best Paper,在2017年的论文《Fixing Weight Decay Regularization in Adam》中提出了一种新的方法用于修复Adam的权重衰减错误,命名为AdamW。实际上,L2正则化和权重衰减在大部分情况下并不等价,只在SGD优化的情况下是等价的。而大多数框架中对于Adam+L2正则使用的是权重衰减的方式,两者不能混为一谈。
先回顾一下Adam优化器的前置知识,并结合源码理解Adam优化器,再来看AdamW与之的不同之处,本文依旧不会有复杂的数学公式,相关实现以python代码的形式展示。
Adam前置知识
1. 梯度下降法
最基本的优化方法,沿着负梯度的方向更新参数,实现如下:
# 梯度下降法
x += - learning_rate * dx
其中learning_rate是超参数代表学习率,被更新的变量为x,其梯度为dx,梯度->位置,很好理解。
但是梯度下降法相关的优化方法容易产生震荡,且容易被困在鞍点,迟迟不能到达全局最优值。
2. 动量法
动量法是一类从物理中的动量获得启发的优化方法,可以简单理解为:当我们将一个小球从山上滚下来时,没有阻力的话,它的动量会越来越大,但是如果遇到了阻力,速度就会变小。实现如下:
# 动量法
v = mu * v - learning_rate * dx # 梯度影响速度
x += v # 速度决定位置
变量v的初始值被定为0,超参数mu在优化过程中被视为动量,其物理意义可以视为摩擦系数,加入的这一项,可以使得梯度方向不变的维度上速度变快,梯度方向有所改变的维度上的更新速度变慢,这样就可以加快收敛并减小震荡。和之前不同的是梯度不会直接对位置造成影响,梯度->速度->位置。
3. RMSprop
RMSprop是一种自适应学习率方法,依旧是基于梯度对位置进行更新。为了消除梯度下降中的摆动,加入了梯度平方的指数加权平均。梯度大的指数加权平均就大,梯度小的指数加权平均就小,保证各维度的梯度都在一个良机,进而减少摆动。
关于指数加权平均的通俗理解可以参考https://zhuanlan.zhihu.com/p/29895933
# RMSprop
cache = decay_rate * cache + (1 - decay_rate) * dx**2 # 梯度平方的指数加权平均
x += - learning_rate * dx / (np.sqrt(cache) + eps) # 基于梯度更新
其中decay_rate和eps都是超参数,每一步的变量cache的值都不同,所以可以看做自适应得对学习率进行调整。
还有一些其他效果较好的优化器,由于这些前置知识已经足够理解Adam了,所以在此不做过多介绍。
Adam
Adam可以看做动量法和RMSprop的结合
# Adam
m = beta1*m + (1-beta1)*dx
v = beta2*v + (1-beta2)*(dx**2)
x += - learning_rate * m / (np.sqrt(v) + eps)
对于m和v的处理,同样使用了指数加权平均。相比于RMSprop,梯度换为了平滑的m,而cache的处理基本没有变化。超参数beta1和beta2的初始值接近于1,因此,计算出的偏差项接近于0。
AdamW
AdamW是在Adam+L2正则化的基础上进行改进的算法。
使用Adam优化带L2正则的损失并不有效。如果引入L2正则项,在计算梯度的时候会加上对正则项求梯度的结果。那么如果本身比较大的一些权重对应的梯度也会比较大,由于Adam计算步骤中减去项会有除以梯度平方的累积,使得减去项偏小。按常理说,越大的权重应该惩罚越大,但是在Adam并不是这样。而权重衰减对所有的权重都是采用相同的系数进行更新,越大的权重显然惩罚越大。在常见的深度学习库中只提供了L2正则,并没有提供权重衰减的实现。
图片中红色是传统的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正则化了。
# m = beta1*m + (1-beta1)*dx
next_m = (tf.multiply(self.beta_1, m) + tf.multiply(1.0 - self.beta_1, grad))
# v = beta2*v + (1-beta2)*(dx**2)
next_v = (tf.multiply(self.beta_2, v) + tf.multiply(1.0 - self.beta_2, tf.square(grad)))
# m / (np.sqrt(v) + eps)
update = next_m / (tf.sqrt(next_v) + self.epsilon)
# Just adding the square of the weights to the loss function is *not*
# the correct way of using L2 regularization/weight decay with Adam,
# since that will interact with the m and v parameters in strange ways.
#
# Instead we want ot decay the weights in a manner that doesn't interact
# with the m/v parameters. This is equivalent to adding the square
# of the weights to the loss with plain (non-momentum) SGD.
if self._do_use_weight_decay(param_name):
update += self.weight_decay_rate * param
update_with_lr = self.learning_rate * update
# x += - learning_rate * m / (np.sqrt(v) + eps)
next_param = param - update_with_lr
原有的英文注释中也解释了Adam和传统Adam+L2正则化的差异,好了到这里应该能理解Adam了,并且也能理解AdamW在Adam上的改进了。
Lookahead,RAdam?
Lookahead和RAdam都是比较新的优化器,具体原理在此不过多介绍。但是我有疑问需要大神来解答一下。
在BERT中引入优化器的源码中有这样一句注释
# It is recommended that you use this optimizer for fine tuning, since this
# is how the model was trained (note that the Adam m/v variables are NOT
# loaded from init_checkpoint.)
也就是说在微调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
请大神解答一下,为什么达观杯有的队伍用了Lookahead取得了较好的效果(可能在训练BERT模型时就用了Lookahead?)。那是什么原因导致的我实验中微调官方提供的BERT模型时Lookahead和RAdam效果不好,是超参数的问题还是预训练好的BERT模型不适用于这些优化器那?
参考资料
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