如果对自然语言处理在过去三年里面,最重要的文章做排序的话,你把 BERT 排在第二的位置,那么很难有另外一篇论文能够名正言顺地排在第一的位置,今天我们的任务就是来读一下BERT这篇文章,我们知道在计算机视觉里面很早的时候,我们就可以在一个大的数据集上,比如说 ImageNet 上面训练好一个CNN的模型,然后这个模型可以用来帮助一大片的计算机视觉的任务来提升它们的性能,但是在NLP在BERT之前一直没有一个深的神经网络使得我训练好之后能够帮助一大片的NLP的任务,就导致在NLP里面,我们很多时候还是对每个人构造自己的神经网络,然后在自己上面做训练,BERT的出现使得我们终于可以在一个大的数据集上,训练好一个比较深的神经网络,然后应用在很多NLP的任务上面,既简化了这些 NLP任务的训练,又提升了它的性能,所以 BERT 和它之后的一系列工作使得自然语言处理在过去三年里面有一个质的一个飞跃。
所以今天我们的任务是对BERT这篇文章进行精读,我们跟以前一样会给大家讲一下BERT的技术上的各种细节,在我们赞美BERT这个开创性工作的同时,我们也希望通过大家读论文来发现,BERT其实也是站在巨人的肩膀上面,我们希望大家能知道BERT哪些技术,哪些思想是来自于前人的工作,哪些是它自己独创的一些东西,这样我们就能对NLP这个领域里面,做出重要贡献的研究员以合理的认可。
BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
1.标题
接下来我们来看一下标题,标题的第一个词是BERT,就是说名字我已经给你想好了,后面的人就不需要帮我来想名字了。然后来看下一个词Pre-training,我们知道Pre-training的意思是,我在一个数据集上训练好一个模型,然后这个模型主要的目的是用在一个别的任务上面,所以别的任务如果叫做training的话,那么在大的数据集上训练我这个任务叫做 Pre-training,就是在training之前的那一个任务。后面几个词是Deep Bidirectional by directional transformers,Deep就好好理解,我帮你做的深一点,Bidirectional双向的,我们等会再来解释,好最后一个是for Language Understanding,我们知道transformer主要是用在于机器翻译这个小任务上面,这里用的是一个更广义的词,就是对一个语言的一个理解的上面,所以总结下来就是这篇文章是关于 BERT 的模型,它是一个深的双向的transformer是用来做预训练的,然后是针对的是一般的语言的理解任务。
2.摘要
第一段
在摘要的第一段的第一句话,它说我们介绍一个新的语言表示模型名字叫做 BERT,BERT 的名字来自于Bidirectional 的B
,Encoder的E
,representation的r
和transformer的t
,它的意思是transformer 这个模型双向的编码器表示,有意思的是这四个词跟你的标题是不一样的,所以我总觉得它 BERT 这个名字是凑出来的,因为它的想法基于一个重要的工作,叫做 ELMo,ELMo来自于芝麻街里面的人的名字,芝麻街是一个美国的老少皆智的一个少儿英语学习节目,然后所以它就凑了一个 BERT, BERT 是芝麻街里面另外一个主人公的名字,所以这篇文章和当之前的 ELMo开创了NLP 的芝麻街系列文章,基本上芝麻街里面重要的人物的名字都被用了一个遍,反正你都可以凑。
第二句话,跟最近的语言表示模型不一样,第一个引用的是ELMo这篇文章,第二个引用的是GPT这篇文章。BERT 是用来设计去训练深的双向的表示,然后使用没有标号的数据,然后再联合左右的上下文信息。
第三句话,因为我们的设计导致我们训练好的BERT,使得我们可以只用加一个额外的一个输出层,就可以得到一个不错的结果,在很多的 NLP 的任务上面,包括了问答、包括了语言推理,而且不需要对任务做很多任务特别的架构上的改动。
所以这两句话其实分别的是讲它跟ELMo和GPT的区别,我们知道GPT它其实考虑一个单向,它用左边的上下网信息去预测未来。所以BERT这里不一样,它用了右侧和左侧信息,所以它是个双向的。Bidirectional是一个非常重要的一个字。ELMo用的是一个基于RNN的架构,而BERT用的是transformer,所以ELMo在用到一些下游的任务的时候呢,它需要对架构做一点点调整。但是 BERT 的地方相对比较简单,只需要改最上层的就行了,这个跟GPT 其实是一样的。
所以它就是分别用两句话来讲清楚了,我跟GPT和ELMo的区别,这也是一种有意思的写法,就是说在你的摘要的第一段话讲我跟哪两个工作相关,而且我跟这两个工作的区别是什么,这也多多少少表示这份工作,其实也是在基于这两篇工作之上做了一些改动。
第二段
接下来摘要的第二段话就是卖一下BERT有什么好处了。这个模型在概念上更加简单,而且在实验上更加好,它在11个NLP的任务上得到了新的最好的结果,包括了GLUE、MultiNLI、SQuAD v1.1、SQuAD v2.0,然后它说对每个人我的绝对的精度是多少,然后呢跟之前最好的结果比是有7.7%的提升,同样道理话后面都是写的绝对的精度,然后说跟别人比它的差距在什么地方。
所以这个地方其实有两个东西是比较有意思的,首先它说我在11个任务上比较好,那真的就是我训练一个模型,然后在一大片的任务上能做的比较好。第二个我很推重的写法就是,当你说你的东西好的时候,你要讲清两个东西, 第一个是你的绝对的精度是多少,第二个是你跟别人比它的相对的好了是多少。
为什么这两个东西都重要呢,如果你没有后面那个,只有前面那个精度的话,别人不知道8%是什么意思,93是什么意思,它没有上下文关系,如果你只说跟别人好了7.7%,那么就是说那取决于你这个数是有多大了,就是大家无法理解的7.7什么意思,你是说在30%做到了37%呢,还是80%做到87%,当然这个东西都会有不一样的解释。所以你告诉大家说我的绝对精度是多少,我的相对精度跟别人比是多少,这样子大家就很清楚你这个结果跟别人比,在我甚至不知道这个数据集大概是什么样的情况下,也能得到你这个结果是非常显著的。
所以整体上它摘要两段话,一段话是说我跟另外两篇跟我相关的工作它的区别是什么,下面一段话是说我的结果特别好。这个其实也是一个非常标准的写法,如果你做一篇论文多多少少是基于别的工作上做些改进,当然你的结果得好一些,所以你先写我跟别人比我的改进在什么地方,另外一块是说我的结果比别人好在什么地方。
3.导言
第一段
导言第一段一般是交代这篇论文关注的研究方向的一些上下文关系。
第一句话,在语言模型里面,预训练可以用来提升很多自然语言的任务,第一篇是用词嵌入来做预训练,后面这些包括了 GPT 这篇文章。
第二句话,在这些自然语言任务里面包括两类,第一类是句子层面的任务,主要是用来建模这些句子之间的关系,比如说对个句子的情绪的一个识别,或者两个句子之间的关系。第二个任务是词源层面的任务,包括了一些实体命名的识别,就是说对每个词去识别它是不是一个实体命名,比如说它是不是一个人名、一个街道名字,这样的任务,你需要输出一些细腻度的词源层面的一些输出。
所以说预训练在NLP里面其实已经流行了一阵子了,这个在计算机视觉里面已经用了很多年了,同样的想法用到在自然语言处理,当然肯定不会很新的。有意思的是说, 今天我们再去介绍BERT的时候,很有可能会把说在NLP上做预训练这件事情归功于BERT,大家会认BERT之前,好像这个事情不能做,然后说有了BERT之后, 终于可以这么做了,然后你读论文的时候,你会发现这个想法当然很早之前就有了,BERT不是第一个提出来这么做的,而是说BERT让这个方法出圈了,让后面的研究者都跟着你来用这个方法做在自然语言处理的任务,这当然也值得大家思考,BERT是怎么样这个东西出圈。
第二段
导言的第二段和之后一般也就是摘要的第一段的一个扩充的版本。
在使用预训练模型做特征表示的时候,一般有两类策略,第一类策略是基于特征的,另外一个策略是基于微调的。
基于特征的的代表作是ELMo,对每一个下游的任务,构造一个跟这个任务相关的神经网络,它用的其实用的是RNN这个架构,然后在预训练好的这些表示,比如说你是一个词嵌入也好,什么别的也好,它作为一个额外的特征和你这个输入是一起输入进这个模型里面,我希望这些特征已经有了比较好的表示,所以导致你的模型训练比起来比较容易,这也是 NLP 里面使用这些预训练模型最常用的一个做法,就是把你学到这些特征跟你的输入一起放进去,作为一个很好的特征的表达。
第二类是基于微调的,比如说GPT,把预训练好的模型放在下游的任务的时候不需要改变太多,需要改一点就行了,然后这个模型它预训练好的参数会在你下游的数据上再进行微调一下,就是所有的权重在根据你的新的数据器进行微调一下。
你介绍别人的目的通常用来铺垫你自己的方法,就是说别人哪些地方你觉得做的不好,我的方法在这一块有改进。
这两个途径都是使用一个相同的目标函数,在预训练的时候,它都是使用一个语言模型,而且是一个单向的。
我们知道语言模型它就是一个单向的,就我给一些词,我预测我下一个词什么东西,就是我说一句话,然后你预测我这句话下面那个词是什么东西,你是属于一个预测模型,要预测未来,它当然是单向了。你不能说,我先告诉你我这句话是什么东西,下一句话什么东西,你来猜我中间要说什么,那当然就不叫语言模型了。
第三段
接下来在第三段的时候,讲一下主要的一个想法
现在这些技术会有局限性,特别是来做预训练的表征的时候,你的主要问题是,标准的语言模型是一个单向的,这样导致你在选架构的时候要有一些局限性,比如在 GPT 里面它用的是一个从左到右的架构,也就是说你在看一个句子的时候,你只能从左看到右,假设你的句子是从左读到右的话,然后它说这个东西不是很好,就是说它说我要如果要做句子层面的一些分析的话,比如说我要判断一个句子的情绪是不是对的话,我从左看到右和一次看所有就说从右看到左都是合法的。它另外说就算是词元上的一些任务,比如说 Q&A(问答) 的时候我其实也是能够看完整个句子让我去选答案,而不是真的要去一个一个往下走,它说如果我把两个方向的信息都放进来的话,应该是能提升这些任务的性能的。
第四段
在指出了相关工作的局限性和提出的想法之后,当然接下来要讲是怎么样解决这个问题的。
提出了BERT,BERT是用来减轻之前提到过的语言模型是一个单向的一个限制,它用到的是一个叫做带掩码的language model(masked language model),就是带掩码的一个语言模型,它是受一个叫Cloze任务的一个启发,然后引用的是1953年写的一篇论文。这个带掩码的语言模型每一次随机的选一些字元,然后把它盖住,然后你的目标函数是预测那些被盖住的那些字,就等价于我给你个句子然后挖这些空,然后让你填完型填空了,跟标准的语言模型从左看到右不一样的是,带掩码的语言模型当然是允许你看左右的信息,就是你做完形填空的时候,你不能只看完形填空的左边,你也得看看右边信息才行,不然的话你也不知道怎么去填它,这样的话它允许我们训练深的、双向的一个 transformer 模型。在带掩码的语言模型之外,还训练了一个别的任务,叫做下一个句子的预测,核心思想是我给你两个句子,你给我判断说这个两个句子在原文里面是不是相邻的,还是说我其实就是随机的采样了两个句子把你放在一起,这样子的话能让你的模型去学习句子层面的一些信息。
贡献
一共列了三点贡献。
- 展示了双向信息的重要性。GPT只用了单项, 之前有的工作只是很简单的把一个从左看到右的语言模型和一个从右看到左的语言模型简简单单的把它合并在一起,有点像双向的RNN模型,就是把它concat了在一起。而BERT这个模型在双向信息的应用上更好一点。
- 假设你有一个比较好的预训练的模型的话,你就不用对特定任务啊做些特定的模型的改动了,BERT是第一个基于微调的模型,在一系列的NLP任务上,包括了句子层面的和词元层面的任务上都取得了最好的成绩。
- 我的代码和模型全部放在这里面,大家可以随便用。
4.结论
结论比较短,第一句话,最近一些实验表明是使用非监督的预训练是非常好的,这样使得资源不多的任务(比如说我的训练样本比较少的任务)也能够享受深度神经网络,主要的工序就是把前人的结果拓展到深的双向的架构上面,使得同样的一个预训练的模型能够处理大量的不一样的自然语言的任务。
读到这里,我们基本上读了一页半的文章,就你可以看到它的故事是非常简单的东西,它说我有两个前面的工作一个叫ELMo,它用了双向的信息,但是它的网络架构呢用的比较老用的是RNN,另外一个是GPT,它用了一个新一点的transformer的架构,但是呢它只能处理单项的信息,我说我把ELMo双向的想法和GPT使用transformer的东西跟你合起来就成为了BERT,具体的改动是在做语言模型的时,我不是预测未来,而是变成完型填空。而且在写作上作者也是这么写的,它说我两个算法我把它拿过来拼在一起,而且它的主要贡献就是指出了你的双向是一个非常有用的信息。
这样它给大家很多信心, 就是很多时候我们的工作,很多时候就是a加b工作,就把两个东西给你缝在一起,或者把一个技术用来解决另外领域的问题,所以有时候说你也没必要自卑,说自己的想法特别小,不值得写出来,如果你的东西确实简单好用,别人愿意用就挺好的,你就是很朴实的把它写出来,一点问题都没有,而且说不定哪一天就出圈呢,成为了这个领域的经典之作呢。
5.相关工作
非监督的基于特征的一些工作,也就是之前我们提到过的ELMo,然后在ELMo之前是词嵌入,在之后也有很多工作,在这个地方做了一些简短的一个介绍。
非监督的基于微调的一些工作,代表作是 GPT,然后在之前的之后当然也有很多工作。
在有标号的数据上做迁移学习,在 NLP 里面有标号的数据而且比较大的数据,包括了比如说自然语言的一些推理和机器翻译,在两块都有比较大的数据集,然后你在这些有标号的数据集上训练好了模型,然后在别的任务上使用,在计算机视觉里面这一块是用的比较多了,大家经常在ImageNet上训练好模型,再去别的地方用,但是在NLP,似乎这一块不是那么的理想,它在 NLP 似乎这一块不是那么的理想,我觉得可能是一方面这两个任务确实跟别的任务差别还挺大的,第二个是说你的数据量其实还是远远的不够的,有意思的是说BERT 和它之后的一系列工作证明了在NLP上面需要没有标号的大量的数据集训练成模型,效果比你在有标号的相对说小点数据上训练模型效果更好。同样一个想法,现在也在慢慢的被计算机视觉采用,就是说我在大量的没有标号的图片上训练出了模型,也可能比你在ImageNet,这个100万数据集上训练的模型,可能效果还更好。
6.BERT模型
BERT里面有两个步骤,第一个叫做预训练,第二个叫做微调,接下来是对这两个概念的一个解释,在预训练里面这个模型是在一个没有标号的数据上训练的,在微调的时候我们同样适用一个BERT的模型,但是它的权重就是被初始化成我们在预训练中间得到的那个权重,然后所有的权重在微调的时候都会被参与训练,然后用的是有标号的数据,每一个下游的任务都会创建一个新的BERT模型,虽然它们都是用最早那个预训练好的BERT模型作为初始化,但是对每一个下游任务,都会根据自己的数据训练好自己的模型。
在预训练和微调不是BERT独创,在计算机视觉里面用的很多,但是作者还是在这里做了一个简单的介绍,这是一个非常好的习惯,比如说你在写论文的时候遇到一些技术,你的方法是需要它,而且你觉得可能应该所有人都知道,你就可能一笔带过了,这个不是特别好,就说你的论文需要是自洽的,就后面人过来读过来,可能我不知道什么是预训练,我不知道什么是微调,但是这又是了解你的方法不可缺少的一部分的话,那最好是你还是给大家做个简单的说明,不要让大家去说点一下这个参考文献进去看一下,让大家的阅读带来困难。
接下来我们跳到上面看下图一,图一画了两块,一块是预训练,一块是微调,在预训练的时候,我们的输入是一些没有标号的句子对,我们等会再来看具体它是什么情况,你这里看到的是大概是在没有标号的数据上,训练出一个BERT的模型,把它的权重训练好,对每一个下游的任务,包括一个两个三个,对每个任务我们创建一个同样的BERT模型,但是它的权重的初始化值来自于前面训练好的权重,对每一个任务,你会有自己的有标号的数据,然后我们对着BERT继续进行训练,这样子得到对这个任务而言我BERT的版本。
接下来我们看一下模型的架构,它架构写的非常简单,BERT模型就是一个多层的双向的transformer的编码器,而且它是直接基于原始的论文和它原始的代码,没有做什么改动,因为它没有做什么改动,所以在这里说我不给大家介绍了,你们直接去看一下原始论文,或者你去看一下这篇 Blog,就能知道这个模型是怎么回事。
你当然这么写是没问题,比如说你把人家东西直接拿过来,所以你在讲自己贡献的第三章不讲那一块是没关系的,但是我还是建议大家,你碰到这种情况的话,你基于别人的文章,你最好还是在第二章,就是在相关工作的时候,对那个文章做一定的介绍,至少你要讲清楚后面你要用到这些L、H是什么意思,不然的话你读到这里,如果你没有读过前面这篇transformer文章的话,那么你就可能读不下去了,你得会跳回去看,而且现在很多人可能第一时间是读BERT这篇文章,因为这个名字听的更多一点,那么你给这些人可能会带来阅读上的一点点不方便。
接下来就是这个模型架构的一些细节,我调了三个参数,第一个是L,就是你的 transformer块的个数,第二个是你的隐藏层大小,第三个是你在自注意力机制里面那个多头的头的个数。我们有两个模型,一个叫做BERT base,一个叫做BERT large, base用的是12层,宽度是768,头的个数是12,所以它总共的可以学习的参数是一个亿,BERT large的话呢就是把层数翻了一倍,然后你的宽度从768变成了1024,具体1024是怎么来的,是因为你BERT你的模型的复杂度跟你的层数是一个线性的关系,跟你的宽度是一个平方的关系,因为你的深度变成了以前的两倍,那么你在宽度上面也选择一个值使的这个增加的平方,大概是之前的两倍,它的头的个数变成了16,这是因为每个头它的维度都固定在了64,因为你的宽度增加了,所以头数也增加了大的模型,它的可学习的参数是3.4个亿。下面解释说,BERT base的选取使得我们跟GPT那个模型的数量大概差不多,可以做一个稍微也还公平的比较,BERT large当然是要去用来刷榜的。
接下来我们给大家介绍一下,你怎么样把那个超参数换算成你可学习参数的大小,也作为transformer架构的一个小回顾,我们知道这个模型里面的可学习参数,主要来自于两块,第一个是你的嵌入层,第二个就来自于你的transformer块。我们首先看一下嵌入层,嵌入层就是一个矩阵,它的输入是你字典的大小,我们字典大小这里是30,000就是30k,你的输出等于你的隐藏单元的个数就是h,它的输入会进入你的transforme块,transformer块里面有两个东西,一个是你的自注意力机制,一个是你后面的 MLP,自注意力机制本身是没有可学习参数的,但是对多头注意力的话,它会把你所有的进入的K(key)、V(value)、Q(query) 分别做一次投影,然后每一次投影它的维度是等于64的,然后因为你有各个头,头的个数 A 乘以64是等于H的,所以在这个地方其实对我们进来的话有一个key,有一个value,有一个 q,它们都会有自己的投影矩阵,这投影矩阵在每个头之间,你把它合并起来,它其实就是一个H乘以H的一个矩阵了,同样道理我拿到输出之后,我们还会做一次投影,同样道理它也是一个H乘H的东西,所以对于一个transformer模块,它的自注意力,它可学习参数是H的平方乘以4,然后再往上是你的 MLP, MLP里面需要两个全连接层,第一层的输入是H,但是它的输出是一个4乘以H的东西,另外一个权利阶层它的输入是4乘以H,但是它的输出是H,所以每一个矩阵它的大小是 H乘以4H,所以两个就是H的平方乘以8,这两个东西加起来是你一个transformer块里面的参数,然后你还要乘以 L,所以你总参数的个数应该就是30k乘以H就是你的嵌入层,再加上 L层乘以 H 的平方再乘以12, 你分别带进去啊假设你是 BERT base 的话,那么的 H 等于768,L等于12,你带进去之后, 你的总数算出来大概就是1.1个亿,后面对 BERT large 的话,你 H 代成1024,然后L代成24的话,你算出来就是3.4个亿,就大家可以去算一下,这个东西大概是不是对的。
讲完模型之后,让我们来看一下它的输入和输出,我们之前有讲过对下游任务的话,有些任务是处理一个句子,有些任务是处理两个句子,所以为了使得BERT模型能处理,所有这些任务的话,我的输入既可以是一个句子,也可以是一个句子对,具体来说,我一个句子的意思是一段连续的文字,不一定是真正上的语义上的一段句子,我的输入叫做一个序列,所谓的序列就是可以是一个句子,也可以是两个句子。
所以这个跟我们之前讲的transformer是有一点不一样的,transformer它训练的时候它的输入是一个序列对,因为它的编码器和解码器分别会输入一个序列,但是 BERT这个地方,我们只有一个编码器,所以我们为了使得能处理两个句子的情况,我们需要把两个句子变成一个序列。
接下来具体看一下,我们的序列是怎么构成的,它这个地方用的切词的方法是WordPiece。
WordPiece核心思想是,假设我按照空格切词的话,一个词作为一个token,因为我的数据量相对来说比较大,会导致我的词典大小特别大,可能是百万级别,那么根据我们之前算模型参数的方法,那么你如果是100万级别的话,就导致我的整个可学习参数都在我的嵌入层上面。WordPiece 的想法是说如果一个词在我整个里面出现了概率不大的话,那么我应该把它切开,看它的一个子序列,如果它的某一个子序列很有可能是一个词根,出现了概率比较大的话,那么就只保留这个子序列就行了,这样的话,我可以把一个相对来说比较长的词,切成很多一段一段的片段,这些片段而且是经常出现的,这样的话我可以用个相对来说比较小的,30,000的一个词典,就能够表示我一个比较大的文本了。
切好词之后,我们看一下怎么把两个句子放在一起。
这个序列的第一个词永远是一个特殊的一个记号是[cls],代表是 classification,这个词的作用是BERT最后的输出代表是整个序列的一个信息,比如说对一个整个句子层面的一个信息,因为 BERT使用的是 transformer 的编码器,所以它的自注意力层里面每一个词都会去看输入里面所有的词的关系,就算是这个词放在我的第一个的位置,它也是有办法能看到之后所有的词,所以它放在第一个是没关系的,不一定要放在最后。第二个是说他把两个句子合在一起,但是因为我要做句子层面的分类,所以我需要区分开来这两个句子,有两个办法来区分,第一个是在每个句子后面放一个特殊的词[SEP],表示的 separate,第二个是说它学一个嵌入层来表示这个句子到底是第一个句子还是第二个句子,然后它说在图一里面画了一下,它大概长什么样子。
让我们来看一下图一,首先可以看到这个是你输入的一个序列,然后这是你的第一个特殊的记号表示的用来分类,接下来是你的第一个句子,中间用一个separate的分开,这是你的第二个句子,然后每一个token进入BERT,得到这个token的embedding的表示,我们之后再来看这个 embedding 怎么算,对于 BERT 来讲那就是输入一个序列,得到它一个序列,最后一个transformer块它的输出,那么就表示的这个词元的BERT的表示,然后我们在后面再贴加额外的输出来得到我们要的结果。
最后一段话说对每一个词元进入BERT的那个向量表示,它是这个词元本身的embedding,加上它的在哪一个句子的embedding,再加上你的位置的embedding,然后在图二它有给大家展示一下是怎么做出来的。
图二演示的是BERT的嵌入层的做法,就是给一个词元的序列,然后得到一个向量的序列,这个向量的序列会进入你的transformer块,首先可以看到每一个方块是一个词元,我们这里有三个东西,第一个东西是一个词元的embedding层,那么就是说这是一个正常的embedding层对每一个词元的话,我们会输出一个它对应的向量。第二个是一个Segment Embeddings,它就表示是第一句话,还是第二句话,那就是a还是b,那么它的嵌入层,它的输入其实就是2,我每次给它的时候,是可以告诉你,这个词元到底是在句子 a 中间,还是在句子 b 中间,然后得到你相应的那一个向量,后面是一个位置的嵌入层,它的输入的大小是你这个序列最长有多长,比如说是1024,它的输入就是每一个词元在这个序列里面的位置信息,从0开始,01234,在得到我对应的位置的那个向量。所以最后的最后就是每一个词元本身的嵌入,加上你在是第几个句子的嵌入,再加上你这个序列中间位置的一个嵌入,记得在 transformer 里面我们有讲过,我们的位置信息是手动,构造出来的一个矩阵,但是在 BERT 里面,不管你是属于哪个句子,还是你的位置在哪里,它对应的向量的表示都是通过学习得来的,这样我们就介绍完了BERT对于预训练和微调都同样的部分。
接下来我们来讲一下,在预训练和微调之间不一样的部分。
我们知道在预训练的时候,主要有两个东西比较关键,一个是说你的目标函数,第二个是说你用来做预训练的数据。
在3.1节我们分别来看一下它们是什么,首先要介绍的就是掩码的语言模型, 前面其实就是讲一下我们之前说过的那一些东西,就是为什么双向好,带掩码的语言模型是什么样的东西。从这里就开始讲了一点新的东西在里面了,对一个输入的词元序列,如果一个词元是由WordPiece生成的话,那么它有15%的概率会随机替换成一个掩码,但是对于那些特殊的字源,就是第一个词元和中间的分割词元,那我们就不做替换了,那么如果我们的输入序列长度是1,000的话,那么就要预测150个词。接下来它说这个也有一点的问题,是因为我们在做掩码的时候,就会把词元替换成一个特殊的token,叫做[MASK],那么在训练的时候,你大概会看到15%的词元就是对应这个mask,但是在微调的时候是没有这个东西的,因为微调的时候我不用这个目标函数,所以没有mask的东西,导致在预训练的时候,微调的时候,看到的数据会有一点点不一样,这会带来一点问题,它的一个解决方法是对于这15%的被选中去掩码的词,有80%的概率我是真的把它替换成这个特殊的掩码符号,还有10%的概率我把它替换成一个随机的词元,还有10%的概率我什么都不干,我就把它存在那里,但是用它来做预测,具体来说这个80%、10%、10%是怎么选出来的,它说我们有一个ablation study,然后给大家说我们这个东西其实是在实验上跑了,发现这个东西还不错。
它在附录的里面有给大家讲几个例子,我们来看一下,这里是附录a.1它给了三个例子,假设我的输入是一个 my dog is hairy 的话,我把最后这个词圈中了,用来做掩码的话,那么有80%的概率我把最后那个词真的就换成了 mask这个符号,还有10%的概率我就在我的字典里面随机选一个词,比如说选中的 apple 把它换掉,还有10%的概率我就什么都不变,但是就标记下这个词我用来要做预测,所以看到需要中间这个情况,是给你加入了一些噪音,最后一个情况其实是用了,你和你真的在做微调的时候,你真实看到数据是没有变化的,所以你真实看到数应该就是它了。
接下来还是回到前面我们看一下,预训练中间第二个任务就是预测下一个句子。
在 QA 和在自然语言推理里面,它们都是一个句子对,所以如果能够让它学一些句子层面的信息是不错的,具体来说我们的一个输入序列里面有两个句子,有一个 A 有个 B,然后有50%的概率b是在原文中间,真的是在 a 之后,还有50%的概率b就是一个随机从一个别的地方选取出来的一个句子,那么意味着是50%的样本是正例,50%的样本是负例。在5.1的时候,有一些结果的比较,加入这个目标函数, 能够极大的提升Q&A和在语言推理的效果。
同样在附录的时候,它又给了一些例子我们来看一下附录,仍然是在附录 a.1,给了一个正例一个负例,正例是说这个人要去一个商店,然后他买了一加仑的牛奶,那么可以看到这个两个句子,应该就是在原文中间是一个相邻的关系,后面一个是说这个人去了商店,然后企鹅是一种不能飞的鸟,那么这两个东西当然是没有太多关系,所以这个是一个负例,而且注意到一点,这里有个
##, 其实是它是在语文中间是一个词,叫做 flightless 就是一个不能飞的鸟,但是这个词出现的概率不高,所以在 WordPiece 里面把它砍成了两个词,一个叫 flight,一个 less 它都是比较常见的词,
##表示的是后面那个词,其实应该是在原文中是跟着前面那个词的意思。
在模型预训练这一节,最后一小段讲的是它的训练的数据,它用了两个数据集,第一个是由8个亿词的一个书本构成的数据集,第二个是Wikipedia 英文的,有着25亿个词的一个数据集,Wikipedia数据集比较好下,大家这个直接就下下来就行了,然后它又加了一句说我们应该用文本层面的一些数据集,就是我的里面是一片一片的文章,而不是一些随机打乱的一些句子,这是因为 transformer确实能够处理比较长的序列,所以给一个整个文本序列过来,当然效果会比较好一点。
第3.2章就是用BERT做微调的一个一般化的介绍,它首先讲了一下,BERT跟一些基于编码器解码器的家伙有什么不一样,我们在之前讲的 transformer 是编码器解码器,因为我们把整个句子对都放在一起进去了,所以 self-attention能够在两端之间相互能够看,但是在编码器和解码器这个架构里面,编码器其实一般是看不到解码器的东西,所以 BERT在这一块会更好一点,但实际上来说你也付出了代价,就是说你不能像transformer一样的,可以做机器翻译了。接下来我在做下游任务的时候,我会根据我的任务设计我们任务相关的输入和输出,所以的好处是我的模型其实不怎么要变,主要是怎么样把我的输入改成我要的那一个句子对,如果你真的有两个句子的话,当然就是句子 A 和 B 了,如果你就只有一个句子的话,比如说我要做一个句子的分类的话,那我B就没有了,然后根据你下游的任务的要求,我要么是拿到第一个词元对应的输出做分类,或者是拿到对应那些词元的那些输出,做你要的那些输出,不管怎么样,我都是在最后加一个输出层,然后用一个softmax得到我要的那些标号,跟预训练比微调相对来说比较便宜,所有的结果都可以使用一个TPU,然后跑一个小时就行了,使用 gpu 的话,多跑几个小时也是可以的,然后具体对每一个下游任务是怎么样,构造输入输出的,会在第四节给大家详细来介绍。
7.实验
第四节讲的就是BERT怎么样用在各个下游任务上,特别是在摘要里面提到那几个BERT能赢下来的数据集上面。
第一个任务叫做GLUE,它里面也包含了多个数据集,它是一个句子层面的一个任务,所以BERT就是把第一个特殊词元,就是cls这个词元,它的最后的那个向量拿出来,然后学习一个输出层就是一个 W,把它放进去之后,用softmax就得到你的标号,然后就是一个很正常的多类分类问题了。
在表一就是在这个任务上的一些分类的结果,你可以看一下最后那个 Average,就是在所有数据集上的平均的值,当然是越高越好,这是个精度,可以看到BERT base,BERT large基本上比前面的都还要好一些,这是 GPT,然后这个是另外一个在 GPT 之前最好的结果,基本上看到是说 BERT,就算是在base跟 GPT 的可学习的参数差不多的情况下,也还是能够提升是比较大的。
接下来接下来是 SQuAD,就是斯坦福的一个 Q&A 的数据集,在 Q&A 这个任务里面,你的任务是我给你一段话,然后问你一个问题,需要你把我的答案找出来,这个答案已经在我给你的那一段话里面,你只需要把我这个答案对的那一个小的片段给我找出来就行了,就是这个片段的开始和结尾,所以这样子的话,它其实说白了就是对每个词元,我来判断一下你是不是答案的开头,是不是答案的结尾。具体来说它就是学两个向量S 和 E,分别对应的是这个词元,是这个答案开始的概率和答案最后的概率,具体来说它对每一个词元,也就是第二句话里面那个每一个词元,它跟它的相乘,然后再做 softmax,就会得到这个段里面每一个词元,它是答案开始那个概率,同样道理的话我会算出来它是尾的概率。然后我们就开始训练了,它这提到一点是我在做微调的时候,它用的是三个 epoch,就是扫数据三遍,然后学习利用的是5e-5,然后 bachsize 是 32,这个东西其实误导了很长一段时间,大家后来发现你用 BERT 做微调的时候,结果非常不稳定,就是同样的参数,我同样的数据集我给你训练十遍,你的Var(variance方差)特别大,最后大家发现是其实很简单,就是你这个东西不够,就是3这个东西太少了,你可能要多学习几遍会好一点,然后大家发现 BERT 用的优化器是一个 adam 的不完全版,这个东西在于你的BERT要训练很长时间的时候是没关系,但是你BERT的如果只训练那么小小的一段时间的话,那么这个东西会给你带来影响,你要把它换回到Adam的正常版会解决这个问题,表现的是BERT的它的结果,基本上可以看到是说,BERT基本上是比别人都要好很多的,还有一个是这个Q&A 数据集的2.0版本,我们就不给大家仔细介绍了。
最后一个是一个叫做SWAG的一个数据集,它用来判断两个句子之间的关系,同样跟之前的训练没有太多区别,而且结果上来说,BERT 也是比别人要好很多。
这样我们就讲完了BERT怎么做微调,基本上可以看到是说,对于还是挺不一样的这些数据集,BERT用起来还是挺方便的,你就基本上只要把它表示成你要的那一个一对句子的形式,最后的你拿到对应的输出,然后加一个输出层基本上就完事了,所以BERT对NLP整个领域的贡献还是非常大的,这样子有大量的任务可以用一个比较相对来说简单的架构,也不需要改太多东西,我就能做了。
第五节讲的是Ablation Studies,就是说我的 BERT 设计有那么多东西,然后看一下每一块最后对我的结果的贡献是什么样的。首先假设我去掉那一个下一个句子的预测会怎么样,以及如果是我就是看一个从左看到右的语言模型会怎么样,而不是用我带掩码的语言模型,从左看到右而且没有下一个句子预测,那这个是说,我可以在上面加一个双向的 LSTM,其实是从 ELMo 那边来的想法,然后看一下结果怎么样,基本可以看到是去掉任何一个,它的效果都会有打折,特别是说对这个任务,你的影响会比较大一点。
5.2讲的是模型大小的影响,我们知道,BERT base是一个亿的可学习的参数,BERT large是三个亿的参数,相对之前transformer,其实你的提升还是挺大的。它说我们这个也跟NLP其他大家的想法是一样的, 当你把模型变得越来越大的时候,效果会越来越好,然后它说我们认为这个是第一个工作展示了,你如果把模型变得特别特别大的时候,对于你的语言模型来说有比较大的提升。
从现在角度来看,BERT 并不大也就一个亿,而现在你 GPT3 都做到1千个亿的样子了,甚至大家在一万亿的那个程度在走,但是在三年前,确实 BERT 是开创性的工作,把一个模型能够推到那么大,引发了之后的模型大战,就是看谁的模型大。
最后一节5.3讲的是说,假设我不用BERT做微调的时候,而是把BERT的特征作为一个静态特征输进去会怎么样,当然它的结论是,效果确实没有微调那么好,所以也是这篇文章出来的一个卖点是,用 BERT 的话,你应该用微调。
8.评论
沐神评价
最后给大家总结一下 BERT 这篇文章,从写作上来说我觉得写的还行,中规中矩吧。先写了 BERT和ELMo 和 GPT 的一个区别,然后介绍 BERT 这个模型怎么回事,接下来是说你在各个实验上,你的设置是什么样子,最后是比的结果,但结果是非常好了。
在 BERT 这篇文章的结论里面,它认为它这篇文章最大的贡献是这个双向性,我也理解说你写一个文章是最好卖一个点啊,不要说我在文章这里也好那里也好,别人都记不住,但是我觉得选用双向性这个词呢,还是有待商榷了。今天来看,BERT的贡献不仅仅是你的双向性对吧,还有很多别的东西,就算你要写我的贡献是双向性的话,我觉得你从写作上来说,至少要说你选的双向性给你带来的不好是什么,你做一个选择会得到一些,也会失去一些,你跟 GPT 比你用的是编码器,GPT 用的是解码器,当然你得到了一些好处,但是你也失去了一些,比如说你做机器翻译就不那么好做了,给一个句子翻另外一个句子,你做文本的一个摘要也不那么好做了,就是说你做那些生成类的东西可能就没那么方便了。
当然了分类问题在 NLP 里面更加常见一点,所以BERT这么做了之后啊,NLP 的那些研究者其实更喜欢BERT一些,因为把BERT用在自己的问题上更加容易一点。
另外说你如果你从现在再回过去看 BERT 的话,其实你看到的是一个完整的一个解决问题的思路,符合了大家对一个深度学习模型的一个期望,这是我训练一个很深的很宽的一个模型,在一个很大的数据集上训练好,这个模型拿出来之后,可以用在很多小的问题上,能够通过微调来全面提升这些小数据上的性能,这个在计算机视觉里面,我们用了很多年了,在 NLP 的时候,BERT 才给大家展现的是说,我现在可以训练一个很大的,三个亿的一个参数的模型,使用的是几百GB 的一个数据集,然后给大家展示了你的模型,越大的时候你的效果越好,所以这个很好的满足了深度学习研究者的一个喜好,很简单很暴力效果很好。
但我一直其实是有疑惑的, 就是BERT是在这一系列工作中间的一篇,就是让更大的数据集训练更好的模型,比前面都要好,ELMo出来的时候其实也是这样子,GPT在 BERT 之前出来,其实也相对来说训练更大的模型,它也在很多数据集上取得了很好的成绩。BERT在另外方面讲,你也就是一个更大一点的模型、更大的数据集,在一些任务上取得很好的成绩,你的好结果的唯一的结局是被后面的工作所超越,所以为什么大家记住的是 BERT,如果你把BERT和 GPT 相比,它们大概是同期的一个工作,虽然BERT确实比 GPT 的结果好那么一些,但是它的思路是非常一样的,但是从利用率上来讲,BERT是 GPT 的10倍,意味着是说,它的影响力至少是你的十倍以上。
9.面试题
- BERT分为哪两种任务,各自的作用是什么?
- 在计算MLM预训练任务的损失函数的时候,参与计算的Tokens有哪些?是全部的15%的词汇还是15%词汇中真正被Mask的那些tokens?
- 在实现损失函数的时候,怎么确保没有被 Mask 的函数不参与到损失计算中去?
- BERT的三个Embedding为什么直接相加?
- BERT的优缺点分别是什么?
- 你知道有哪些针对BERT的缺点做优化的模型?
- BERT怎么用在生成模型中?