本文为李弘毅老师【BERT and its family - ELMo, BERT, GPT, XLNet, MASS, BART, UniLM, ELECTRA, and more】的课程笔记,课程视频youtube地址,点这里(需)。
下文中用到的图片均来自于李宏毅老师的PPT,若有侵权,必定删除。
文章索引:
上篇 - 7-2 BERT and its family - Introduction and Fine-tune
下篇 - 7-4 來自獵人暗黑大陸的模型 GPT-3
总目录
上篇讲了如何用pre-trained model去做一些NLP相关的任务,这篇就来讲一下如何得到这个pre-trained model,也就是如果pre-train。
最早的一种pre-train的方法就是利用翻译的任务去train一个encoder和decoder,而这个encoder就是我们想要的pre-trained model。用翻译的任务来做是因为翻译的时候需要考虑到上下文的信息,因此每个token对应的输出也是考虑了上下文的,然后用这些输出塞进decoder之后可以得到正确的翻译说明了这些输出特征包含了每个token的语义。但是,这样做的话,有一个不好的地方就是,我们没有那么多已经成对整理好的数据,这个数据的成本将非常大。
因此我们希望有一种不需要标注数据的方法去进行pre-train,这种方法就叫做self-supervised learning。没错,这个也就是unsupervised learning,但是Yann LeCun呼吁我们改口称之为self-supervised learning。因为其本质是用输入的一部分去预测输入的另一部分,还是有监督的。下图就是有监督和自监督的一个区别。
那么我们如何把一个句子 x x x够造成 x ′ x' x′和 x ′ ′ x'' x′′呢?最常见的一种做法就是predict next token。我们会输入 w 1 w_1 w1希望模型预测出 w 2 w_2 w2,然后再输入 w 2 w_2 w2希望模型预测出 w 3 w_3 w3,以此类推,只要注意在设计模型的时候,不要让模型看到它不该看到的答案就可以了。把这个输入 w i w_i wi,输出 h i h_i hi的模型基于LSTM去设计,就有我们的ELMo了。如果是基于self-attention去做,就有GPT,Megatron和Turing NLG。这个模型也就是language model。
聪明的小伙伴也许已经意识到了,我在预测 w 2 w_2 w2的时候,只看到了 w 1 w_1 w1,为什么不能也看一下 w 3 w_3 w3和 w 4 w_4 w4呢?把句子除了 w 2 w_2 w2之外的部分都看一遍,再去预测,才会比较准吧。没错,的确是需要这样做的。ELMO就用了两个LSTM分别从头开始看和从尾开始看,比如看了 w 1 w_1 w1到 w 4 w_4 w4去预测 w 5 w_5 w5,然后看了 w 7 w_7 w7到 w 5 w_5 w5去预测 w 4 w_4 w4,最后把这两个的features去concat得到最终 w 4 w_4 w4对应的feature。
ELMO的做法有一个问题就是两个LSTM是互相独立的,它们之间没有信息的交流。BERT则完美的解决了这个问题,BERT只要设计好一个MASK,然后盖住模型要预测的那个token就可以了,这就是self-attention相比于LSTM的优势啊!顺便一提,BERT的这种训练方法和CBOW是非常像的,可以说是超级版的CBOW。
不过,只盖住一个token就足够了吗?只盖住一个token,模型可能无法学到一些long-term的东西,只要依赖于附近的几个词猜猜就行了。所以,有人就提出了盖一个词去做,这个叫做Whole Word Masking。也有人提出先对句子做entity recoganition然后盖entity或者phrase,这个就是ERNIE。
还有一种叫做SpanBert的方法,就是随机去盖一排token,盖住的token的length满足一个分布,这个分布是盖的越长概率越小的一个分布。SpanBert的训练方法其实也和Bert有所不同,它用了一种叫做Span Boundary Objective的方法,就是利用被盖住部分两端最接近的两个embedding,然后再输入一个要预测被盖住的第几个token的数字,去预测最终的结果。这听上去有些想不通,为什么要这么搞一下,原来的方法不香吗?
再讲一个叫做XLNet的模型,这个模型比较难懂,这里只是说一下它的做法,说实话搞不懂为什么要这么做,有时间去看看paper再回来说下感悟。它的做法就是,它在预测被盖住的token的时候,是随机取其他已有的embedding的信息去预测的,当然也需要一个position encoding告诉它要去预测哪个token。就是这样,奇怪吧~
BERT在训练的时候都是看整个句子去预测的,因此不太适用于generation的任务,就是给一段句子,去预测后面的部分。Language model在训练的时候就是这么训练的因此没有太多问题。但是Bert都是给个mask然后去预测mask的内容的。硬要上的话,就是在句子的后面强加一个mask,然后让BERT去预测预测看。这种从左往右预测的叫做autoregressive model,但是今天,我们不一定要让模型从左往右去生成文本,如果是non-autoregressive model的话,说不定BERT就适用了。
seq2seq的pre-train model是如下图这样的,输入一串tokens,经过一个encoder和decoder之后,希望得到同样的一串tokens。当然,直接这么做的话未免太简单,模型学不到什么东西,所以,一般会对input的sequence做一些破坏。
Bart尝试了以下几种破坏方式,第一种是随机给一个token加mask;第二种是删除某些输入;第三种是对tokens做permutation;第四种是对tokens做rotation;第五中是在没有token的地方插入mask,然后盖住某些token,盖住的部分可能有两个token。其中效果最好的是最后的一种。第三和第四种效果最差。这样的结果其实也可以预期到,给模型看大量的打乱顺序的句子,模型就不知道什么是正常的句子了。
还有一种叫做UniLM的,是既可以像BERT那样训练(双向language model),又可以像GPT那样训练(left-to-right language model),还可以像BART那样训练(Seq-to-seq language model)。这里只提一下这个模型,不细讲了。
还有一个叫做ELECTRA的模型,用一个更简单的任务去预训练模型。它把输入中的某个token用另一个token替换调之后,输入模型,让模型去预测各个token有没有被替换过。这样一方面使得任务更为简单,另一方面也可以监督到每一个token的对应输出。
当然,如果随意替换的话,很容易就会被模型找出来了,学不到什么东西。所以,这篇文章的作者用了一个额外的small BERT来生成这个要被替换掉的位置的token。这个BERT不能太准,也不能太不准。太准的话就直接预测出原来的token了,太差的话和随机选差别不大。这种做法挺像GAN的,但它不是GAN,上下两个模型是各train各的,BERT没有被设定为要骗过上面的model。
ELECTRA的效果还挺惊人的,它可以用更少的FLOPS得到和大模型非常接近的结果。
有些时候,我们希望得到的并不是每个token的embedding,而是一个可以表示整个句子的sentence embedding。这个时候,又该如何训练呢?
训练sentence embedding的模型有两种方法,一种叫做skip Thought,就是给定一个句子,让模型去预测它的下一句话是什么,这样的生成任务很难;另一种叫做Quick Thought,在encoder分别输入句子1和句子2,encoder会分别输出feature1和feature2,如果这两个句子是相邻的,那么我们希望feature1和feature2很相似。
Bert的做法是NSP(Next sentence prediction),就是输入两个句子,在两个句子之间加一个"[SEP]“分隔符,然后用”[CLS]"这个token的输出来预测这两个句子是相邻的,还是不是相邻的。
NSP的这种做法的实际效果并不好,于是就有人提出了SOP(Sentence order prediction)。就是让两个句子来自于同一篇文章,如果这两个相邻的句子反了,那么它也是输出的No,只有在既相邻,顺序又对的情况下输出Yes。还有人提出了structBERT,就是结合了NSP和SOP。
最后来提一下T5(Transfer Text-to-Text Transformer),它是谷歌出的,用到了各式各样的预训练方法,真是有钱。