ULMFiT解读(论文 + PyTorch源码)

可能是笔者孤陋寡闻,感觉这篇论文没有BERT、ELMo这么火,笔者也是在搜索相关话题的文章的时候,看到大家都会带着ULMFiT进行分析,因此也就去研究了一下。总体来说,这篇论文也是pretrain+finetune的思路,探索的比较浅,主要用来做文本分类,而且trick相对来说也比较多。但整体的思路比较值得借鉴。

一. 前言

这里简单复述一下论文的作者在第一章中提到的贡献:

  1. 提出了ULMFiT(Universal Language Model Fine-tuning),用于实现像CV领域的迁移学习,并可以用于任意NLP任务。
  2. 提出了一些训练的策略,比如discriminative fine-tuning、slanted triangular learning rates、gradual unfreezing等。
  3. 在6个文本分类的任务上表现不俗,甚至提升了18~24%。
  4. 可以用少量样本训练。
  5. 重点来了!有充足的源码、预训练模型等。

二. ULMFiT原理

ULMFiT,根据它的名字,基本就可以知道它的操作流程,具体见下图:

一共是分为3个阶段,首先是语言模型的预训练、然后是语言模型的finetune、最后是分类任务的finetune。其实如果读者之前有过CV中图像分类的经验的话,可以发现这里面的后两步实际上都是finetune的操作,只不过这里将其分开进行叙述。下面将一一进行剖析:

1. 通用域语言模型pretrain

这一步没什么好说的,就是用了一个外部大数据(Wikitext-103,103 million词),先对LM进行pretrain。

2. 目标域语言模型fineutune

这一步的insight很直观,就是觉得通用域的语言模型数据会与目标域的数据有分布上的差别,所以要用目标域的语言数据先把LM finetune一波。这里就用到了两个trick:

  1. discriminative fine-tuning

从名字上看,就是有区别性的finetune,在哪里有区别?论文中提到是在对不同层做finetune的时候,使用不同的学习率。作者通过经验发现,对于最后一层可先设置 n L n^L nL作为学习率,然后只训练最后一层,然后前面的层用 n l − 1 = n l / 2.6 n^{l-1} = n^l / 2.6 nl1=nl/2.6继续训练。

  1. slanted triangular learning rates(STLR)

这是一个学习率调整的方式,作者提到用这种方式的初衷是说,希望能先让参数较快收敛到一个合适的区域,然后再慢慢调整。所以他用这种类似三角的方式:

从图上直观来看长这样:

公式里面的 c u t cut cut就表示中间的那个尖对应的iteraion步数, T T T表示总的迭代步数, r a t i o ratio ratio就是一个比例参数, n m a x n_{max} nmax是最大的学习率(就是尖对应的纵坐标)。一般取 c u t _ f r a c = 0.1 , r a t i o = 32 , n m a x = 0.01 cut\_frac = 0.1, ratio=32, n_{max} = 0.01 cut_frac=0.1,ratio=32,nmax=0.01

3. 分类任务finetune

这里就是将前面的LM输出进行concat,然后在其上加入两个全连接模块(带BN和ReLU激活的),进行分类即可。

具体地,对于LM的输出,将其最后一个隐层输出,与时间上的maxpool及meanpool进行concat:

h c = [ h T , m a x p o o l ( H ) , m e a n p o o l ( H ) ] h_c = [h_T, maxpool(H), meanpool(H)] hc=[hT,maxpool(H),meanpool(H)]

同时也提出了3个trick,用于更好的训练:

  1. gradual unfreezing

其实就是在finetune的时候,逐层解冻前面的层。因为如果一次性finetune所有层的话,可能会出现灾难性遗忘(即训着训着就忘记了之前pretrain学到的东西),所以这里是逐层向前打开,逐渐加多finetune层数。

与这种方法相似的一个方法是"chain-thaw",这种方法是每次解冻一个层,每次也只训练那一个层,而不像这里,打开了过后,就一直训练下去。

  1. BPT3C

主要是应对长文本的,将长文本分成batch个短句子,然后每次训练的时候,都是用前面一个batch的隐层状态进行初始化(这个好像也是LM训练的一个小trick),但是梯度不会传递到前面去。

  1. 双向语言模型

单独训练两个方向的语言模型,最后预测的结果是这两个的融合。

三. 实验

1. 分类任务实验

实验的任务主要是用在了文本分类上,有情感分类、问题分类、主题分类三大类。统计信息如下:

结果如下:

对比的模型都是他们写论文的时候SoTA的模型。

2. 一些分析

作者在论文里面做了很多有趣的分析,比如:

  1. 少数据量的学习

这个图是表示训练样本与验证集错误率的关系示意图,从左到右依次是IMDb、TREC-6和AG数据集。模型里面的From scratch表示完全从头开始训练,supervised表示仅用当前任务的数据进行LM的finetune,semi-supervised表示可以用所有task的数据进行LM的finetune。明显看出,用了较多数据进行finetune过后的LM,需要的训练样本更少,而且最终收敛效果也最好。

  1. pretrain的影响

这个都不用多说了,直接看结果:

结论就是pretrain对于中小数据集来说,简直是救命,对于大数据集,也能提升表现。总之就是用就对了!

  1. LM模型选择的影响

这里作者比较的是用最原始的LM和一个改进版本的好LM进行比较(据说是他们当时的SoTA):

显然好的LM,效果会更好。

  1. finetune LM方式的影响

这部分就是验证2个trick的影响,结果如下:

这里证明了finetune LM的必要性,而且也证明了那两个trick非常好用!

  1. finetune分类器方式的影响

这部分主要是对比一些trick使用的效果:

  1. finetune分类器策略的稳定性

这部分主要是看了一下在finetune classifier的时候,直接finetune full model和用了trick的方式的对比,可见full的很不稳定。

  1. 双向模型的影响

一般双向融合都是能带来提升的。

四. PyTorch实现

ULMFiT在源码方面还是比较全面的,放出了论文中使用的所有脚本和详细的处理步骤,同时也提供了预训练好的模型,可以复现,也可以自己按照它那个步骤train自己想要的东西。下面笔者将按照论文中的三个步骤对相应的源码进行剖析:

1. 语言模型pretrain

语言模型的构建和训练部分比较简单,其代码如下:

# 构建模型
m = to_gpu(get_language_model(md.n_tok, em_sz, nh, nl, md.pad_idx, decode_train=False, dropouts=drops))
# 损失函数
crit = CrossEntDecoder(prs, m[1].decoder, n_neg=n_neg, sampled=sampled).cuda()
# 训练
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值