ccks数据集_如何让Bert在finetune小数据集时更“稳”一点

最近刷到一篇论文,题目是Revisiting Few-sample BERT Fine-tuning 。论文刚挂到arxiv上,虽然关注的人还不是很多,但是读完之后发现内容很实用,很适合应用到实际的业务中。本文主要就这篇论文中的一些观点进行解读和实验验证。

话不多说,直接进入正题。这篇论文主要探讨的主题是如何更有效地使用bert在小数据集上进行finetune。论文指出目前bert的finetune存在不稳定的问题,尤其是在小数据集上,训练初期,模型会持续震荡,进而会降低整个训练过程的效率,减慢收敛的速度,也会在一定程度上降低模型的精度。文章主要总结了三个优化的方向,分别从优化方法、权重参数、训练方式等角度探讨了如何在小数据集上稳定finetune bert模型。下面将分别从这三个的角度详细解读。

Adam优化的debiasing

不知道大家在使用tensorflow或者pytorch版本的官方bert源码时,有没有发现他们的Adam实现源码与原版的Adam实现略有不同。我们先来简单回顾一下Adam算法的流程:

126876571f6f0ab542c32466ef5c9998.png

adam主要是结合了一阶动量、二阶动量滑动平均,并辅以learning rate的adaptive change,使得模型训练能够更加高效且能够自适应改变learning rate。除此之外,adam还有一个算法细节需要关注,即bias correcting。注意到上图中红色框标注的部分,在梯度更新操作之前,需要对一阶动量和二阶动量进行bias修正。这样做的原因在于adam的动量均是使用0来初始化。因此在模型训练初期以及指数衰减率超参数(

)很小的时候,动量估计值很容易往0的方向偏移,此时需要对动量做偏移修正,具体的修正操作如图红色框所示。具体的推导可以参考原始的Adam论文。这里简单回顾一下,以二阶动量的推导为例:

推导的主要逻辑在于建立二阶动量

的期望
的期望
的表达式关系。

首先根据上图的步骤8,可以将二阶动量

转化为以历史时间戳上的梯度
为变量的函数:

对上式两边同时求期望,可以得到:


(updated on 2020.06.17)新增内容:这里的推导我又研究了一下,最后看到一个网站上的回答有些道理,这里贴出来供大家参考:Understanding a derivation of bias correction for the Adam optimizer

首先要弄清楚

是怎么得到的。推测
是根据当前时刻的梯度
去估计历史梯度
时的误差项。有了这个误差项,我们就可以将
项从求和公式中移出来,不再依赖i。而所有包含
的项此时可以看成是常量,可以从期望的括号中移出来。当二阶动量是一个稳态分布时,在每个时刻t上它都是一个常量,因此
为0。

那么接下来还有一个问题就是

如何化简为
的呢?这就需要用到有限等比数列求和的相关公式了。对于一个有限等比数列,它的求和可以表达为如下公式:

自我吐槽一下:高中数学全还给老师了,汗颜。。。

此时将

带入上式,同时由于我们已经将当前时刻的
从求和公式中提了出来,因此可以做出如下推导:

上述第二项等式通过将

乘到右边的除法项,同时分子分母同时乘以
就可以得到第三项。

其中,

可以通过控制衰减率超参数
,来让其接近于0。那么剩下的偏移影响因素就是
。因此,我们通过将
除以这个项来达到偏移修正的目的。
这块推导由于本人数学能力不强,所以理解得不是很深入,欢迎数学不错的同学前来拍砖。

Bert的adam

我们查看google给出的官方bert源码工程(https://github.com/google-research/bert)中的optimization.py,在其AdamWeightDecay类中,可以看到其省略了上述偏移修正的步骤:

m 
(updated on 2020.06.18)通过查阅Bert的原始论文,并没有发现作者在这块有具体的说明, 只能推测使用bert做预训练时,由于训练语料规模非常庞大,且训练的步数也是非常多,因此即使不做偏移修正,模型仍然能够在训练过程中慢慢保持稳定状态,且减去了偏移修正的计算量,整体的计算成本还降低了一些(这个推测不太靠谱,权当一个引子,能引出大家对这个问题的思考)

然而,如果在样本较少的下游任务场景下,仍然使用这种优化方式就会出现训练不稳定的问题。为了验证这个结论,论文作者做了细致的比对实验,他们在四个不同的数据集上,尝试了50种不同的随机种子,分别用带偏移修正的原始adam和不带修正的bertAdam去做finetune任务。实验结果分别从不同角度来验证上述的观点,比如下图:

5ee05ba3190b0de7a8da5384f16bd673.png

这是一个模型在不同数据集上的测试集效果箱线图,图中表明在四个数据集上,使用偏移修正的adam能够极大提升模型在测试集上的效果。

再看下面这个图:

ef7ccab658c9de160411664fde2f7975.png

这张图反映了模型在小数据集RTE上的训练曲线。可以看到,使用偏移修正的Adam来finetune能够更快达到收敛,同时获得更小的loss。

再次验证

实践出真知,为了验证上述结论的有效性,我决定找一个小数据集进行实际测试。正好最近有一个ccks举办的实体识别比赛,名称是面向试验鉴定的命名实体识别任务。这个比赛的训练样本只有400条,实体类型有4种,足以称得上是小数据量了,正好可以拿来做实验。实验的模型主体是Bert+crf框架,超参数和随机种子都固定不变,唯一改变条件的就是是否使用偏移修正。

updated on 2020.06.17)在tf的bert实现中,要将原始的偏移修正补充进去,要添加一定量的代码,主要是增加

的计算和更新,以及偏移修正的逻辑计算。通过阅读原始的tf的adam源码,可以发现它的偏移修正是通过对learning_rate进行修正,即
。除此之外,tensorflow还对
的更新和赋值有自己的计算图优化逻辑,所以相比较于keras的代码更为复杂。下面贴出补充完误差修正后的adamweightdecay代码,可与原始的adam代码对比查看:
class 

主要关注_get_beta_accumulators,_finish,以及在各个_apply_方法中进行偏移修正的计算逻辑:

beta2_power 

最后,根据实验结果,验证了上述结论的有效性。通过使用误差修正,模型训练效率显著提升,只用了一半的训练步数就达到了未用误差修正的训练loss,相当于加快了收敛的速度。最终模型的精度也有小幅的提升。

(update on 2020.06.18)

(上述实验均基于bert4keras框架,tf的代码虽然初步测试通过,但是目前在一些数据上面测试时有一些bug,仅供参考)

这里提一下苏神的bert4keras框架中,很早就注意到了误差修正这个问题,增加了误差修正的选项和步骤,有兴趣的同学可以去研究一下该框架。建议对adam理解不深入的同学阅读苏神的代码,很简洁易懂,而tf中的源码为了优化图计算写了很多复杂代码。
在bert4keras中,具体的内容在optimizers.py的Adam类中,具体代码如下:
with 

Weight Re-initializing

论文提到的第二个优化点是权重再初始化。我们使用bert做finetune时,通常会使用bert的预训练权重去初始化下游任务中的模型参数,这样做是为了充分利用bert在预训练过程中学习到的语言知识,将其能够迁移到下游任务的学习当中。众所周知,bert主要由很多transformer层堆叠构成,那么问题来了,是否所有的transformer层都对下游任务有帮助呢?

之前有一些论文专门讨论了bert中不同层的权重分别学习到了哪些信息,大致思想是靠近底部的层(靠近input)学到的是比较通用的语义方面的信息,比如词性、词法等语言学知识,而靠近顶部的层会倾向于学习到接近下游任务的知识,对于预训练来说就是类似masked word prediction、next sentence prediction任务的相关知识。当使用bert预训练模型finetune其他下游任务(比如序列标注)时,如果下游任务与预训练任务差异较大,那么bert顶层的权重所拥有的知识反而会拖累整体的finetune进程,使得模型在finetune初期产生训练不稳定的问题。

因此,我们可以在finetune时,只保留接近底部的bert权重,对于靠近顶部的层的权重,可以重新随机初始化,从头开始学习。论文做了如下实验来验证上述结论:重新初始化bert的pooler层(文本分类会用到),同时尝试重新初始化bert的top-L层权重,

。该超参数可以使用交叉验证法来调整。具体步骤和实验结果如图所示:

da311caa588f1c864d15d40b12d1b0ea.png

ce3f09090739d06f738391109a4a7ae1.png

根据上述实验结果,在四个数据集上,模型通过重新初始化部分权重,在精度上都有不同程度的提升。另外,作者还做了一个实验验证到底该对多少层的权重进行重新初始化。实验结果表明这个并没有显著规律,实际上初始化层数与具体的任务和数据集相关的,需要通过调参来决定。但是有一点是可以肯定的,对于需要用到pooler层的分类任务,对pooler层进行重新初始化肯定能对模型的训练有一定的帮助。

再次验证

同样的,我也在ccks的实体识别比赛中验证了上述的想法。通过固定其他参数(包括不使用偏移修正的Adam),我对bert的前6层进行了重新初始化,具体代码实现只需要在modeling.py中的get_assignment_map_from_checkpoint方法中,将需要重新初始化的权重层参数从assignment_map中过滤掉就可以了,具体如下:

def 

通过实验,验证了上述的结论。将bert顶部的6层权重重新初始化后,模型的训练效率有了较大提升,收敛速度加快了30-40%,然而最后模型的精度似乎没有太大的变化,应该还是需要根据验证集来调整最合适的重新初始化层数,才能达到精度的提升。

用更长的步数来finetune

这块优化内容我感觉似乎没有太大的亮点。作者的意思是通过增加训练步数能够提升finetune的效果。但是一般我都是用early-stopping机制来控制训练的步数,因此感觉这块内容帮助不大,这里我就不过多介绍了。

更多对比实验

论文在最后还做了一组对比实验,他将目前几个比较经典的解决训练震荡的方法列了出来,具体如下:

1、Pre-trained Weight Decay,传统的weight decay中,权重参数会减去一个正则项

。而pre-trained weight decay则是在finetune时,将预训练时的权重
引入到weight decay计算中 ,最终正则项为
。通过这种方式,能够使得模型的训练变得更稳定。

2、Mixout。在finetune时,每个训练iter都会设定给一个概率p,模型会根据这个p将模型参数随机替换成预训练的权重参数。这个方法主要是为了减缓灾难性遗忘,让模型不至于在finetune任务时忘记预训练时学习到的知识。

3、Layerwise Learning Rate Decay。这个方法我也经常会去尝试,即对于不同的层数,会使用不同的学习率。因为靠近底部的层学习到的是比较通用的知识,所以在finetune时并不需要它过多的去更新参数,相反靠近顶部的层由于偏向学习下游任务的相关知识,因此需要更多得被更新。

4、Transferring via an Intermediate Task。即在finetune一个小样本数据集任务时,先在一个较大的过渡任务上进行finetune。

作者将上述四个方法与本论文中的几个优化点做了对比实验,最后发现相对于只使用偏移修正的Adam优化算法,Pre-trained Weight Decay、Mixout、Layerwise Learning Rate Decay并没有显著的优势。当结合了偏移修正和权重重新初始化之后,上述三个方法的效果是明显有差距的。而对于Transferring via an Intermediate Task,虽然它的效果很好,但是它需要额外的标注数据,成本比较高。而且我自己也做了一些验证测试,我使用了MSRA的中文NER数据集先做了finetune,然后再用其权重参数尝试了ccks的NER任务,结果并没有得到明显的提升,个人认为这个过度任务可能需要与目标任务的领域有一定的相关性,不然还需要做领域迁移的工作。

小结

本文主要解读了论文Revisiting Few-sample BERT Fine-tuning。通过深入研究bert在finetune小样本数据集时遇到的训练不稳定问题,提出了几个优化方法,包括使用带偏移修正的adam优化方法、重新初始化部分权重参数等。作者做了详尽的实验来验证上述方法,同时本人也在一个小样本任务上做了简单的二次验证,最终证明上述方法是有效的。由于上述方法操作非常简便,对原始的代码改动很少,因此非常适合应用于实际的项目中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值