1. 标题+作者
BERT:Pre-training of Deep BIdirectional Transformers for Language Understaning
Pre-training:在一个数据集上训练好一个模型,然后这个模型在用到别的任务上,别的任务称为training,这个称为Pre-training
Deep:深一点
Bidirectional:双向的
2. 摘要
第一段:
一个新的语言表示模型:BERT。BERT来源:Bidirectional Encoder Representations from Transformers。transformer模型的双向编码器表示。
和最近的语言模型【不一样】,BERT是用来设计去预训练深的双向的表示,使用的是没有标号的数据,再联合左右的上下文信息。
由于巧妙地设计,使得我们可以只用加一个额外的输出层,就在很多NLP任务上可以得到一个不错的结果,包括问答、语言推理等,且不需要针对任务做特别的架构上的改动。
BERT和GPT、ELMo不一样的地方:
GPT考虑单向的,左边的上下文信息去预测未来,BERT使用的双向的
ELMo是基于RNN的架构,BERT用的是transformer,ELMo在应用到下游(downstream)的任务时候,需要对架构做一点点调整,BERT比较简单。
【评】:在摘要的第一句话说了和那些工作相关,和哪些工作有什么区别,其实也表示了本篇文章的工作也是基于这两篇工作上做了一些改动。
第二段:
BERT模型在概念上更加简单,而且实验上更加好,它在11个NLP任务上得到了新的最好的结果。包括GLUE,SQuADV1.1等等。
【评】:①在11个任务上计较好,那真的就是训练一个模型,能在一大片任务都效果好;② 也是比较推崇的写法:当你说你的东西好的时候,那必须讲清楚两个东西:a. 绝对的精度是多少,b. 和别人比相对的提示有多少。如果只有a没有b,那么别人无法理解,80%意味着什么;如果只有b又无法让人知道相比的基础是什么。
常用写法:比别人改进了什么地方,我的结果比别人好在什么地方
3. 导言
第一段:
[导言第一段一般是交代文章所关注研究方向的一些上下文关系 ]
语言模型中,预训练可以用来提升很多自然语言的任务。这些主要包含两类:第一类是句子层面的任务,主要是来建模句子之间的关系,比如句子情绪的识别,两个句子之间的关系;第二类是词层面的任务,比如命名实体识别,这类任务需要细粒度(fine-grained)的词层面的输出。
【评】:NLP中的预训练很早就开始了,并不是从BERT开始的,但是BERT开始NLP的预训练才开始“出圈”了,那么BERT是怎么出圈的呢?
第二段:
[导言的第二段和之后,一般是摘要的第一段的一个扩充的版本]
使用预训练模型做特征表征的时候一般有两类策略(strategy):基于特征的(feature-based)和基于微调的(fine-tuing)。基于特征的方法代表是ELMo,对于下游任务构造一个跟任务相关的神经网络(ELMo用的是RNN),然后在预训练后的表示,比如词嵌入等,作为一个额外的特征,和模型的输入一起输入模型中,我希望这些特征已经有了比较好的表示,从而使得模型训练起来比较容易。这也是NLP使用预训练模型常用的方法:把学到的特征跟输入一起放进去,作为一个很好的特征的表达。第二类是微调的:比如GPT,预训练好的模型放在下游任务的时候,不需要改变太多,只需要改一点点就可以了,模型预训练好的参数会在下游的数据赛再进行微调一下,就是说所有的权重根据新的数据进行微调。[评:介绍别人的目的,就是说别人哪些地方做得不好,我的方法在这一块有改进],这两种途径是在预训练时候使用相同的目标函数,使用一个单向的语言模型去学习通用的语言表示,[当然,语言模型确实都是单向的,基于前文预测后文]。
第三段:
现有的技术会有局限性,特别是来做预训练表征时候。主要问题是标准的语言模型是单向的,导致选架构的时候受到了一定的限制,比如GPT用的是一个从左到右的架构,即看一个句子只能从左看到右,这种方法不是很好,如果要做句子层面的分析的话,比如判断句子的情绪,我从左看到右和一次性看所有以及从右看到左都是合法的,另外就算是词层面(token-level)上的任务比如问答,也是需要能够看完整个句子再选答案,而不是真的要一个一个往下走。故,把两个方向的信息都放进来的话,应该是能提升这些任务的性能的。
第四段:
[在指出相关工作的问题和局限性以及作者的想法后,接下来讲怎么解决问题。]
提出了BERT,可以减轻之前提到过的语言模型是单向的限制,采用的是一个称为带掩码的语言模型(Masked language model,MLM),此模型是受一个叫Cloze任务的启发。带掩码的语言模型是怎么工作的呢?每一次随机选一些词元(token)将其盖住,然后目标函数是预测那些被盖住的那些词,类似于完形填空。与标准语言模型从左看到右不一样的是,带掩码语言模型是可以同时看左和右的信息,允许我们训练一个深的双向的transformer模型。在带掩码语言模型之外,还训练了一个别的任务:“下一个句子预测”(next sentence prediction),核心思想给你两个句子,判断这两个句子在原文中是相邻的,还是说其实就是随机的采样了两个句子放在一起。这样的任务可以让模型学习到句子层面的一些信息。接下来讲文章的三点贡献:
①展示了双向信息的重要性。之前有个工作是把从左看到右的模型和从右看到左的模型简简单单的合并在一起,有点像双向RNN直接concate一起。BERT在双向信息的应用上更好。
②假设有一个训练比较好的预训练模型,就不用对特定任务做特定的改动。BERT是第一个基于微调的模型,在一系列句子/词元层面的任务上取得了很好的效果。
③代码和模型全部放在github,随便用~
4.结论
最近一些实验表明,使用非监督的预训练效果是非常好的,使得资源不多(比如少样本)的任务也可以享用神经网络。主要工作是把前人的结果拓展到深的双向的架构上,使得同样的预训练模型能够处理大量的NLP语言任务。
【评】:故事非常简单,两个前置的工作,一个是ELMo,使用了双向信息,但是用的是比较老的架构RNN;另一个是GPT,使用了新的网络架构transformer,但是只能处理单向的信息。
于是,采用新的架构transformer,且使用双向信息,就成为了BERT,具体做法是在做语言模型的时候,不再预测未来,而是变成了完形填空。而且在写作上作者也是这么写的,说把两个算法把它拿过来拼在一起,而且他的主要贡献就是指出了双向是一个非常有用的信息。
这给了大家信息,很多时候我们的工作就是A+B工作, 是把两个东西缝在一起,或者把一个技术领域的方法拿到另一个领域去解决问题,所以有时候也没必要自卑,说自己的想法特别小,不值得写出来,如果你的东西确实简单好用,别人愿意用也是很好的,你就是很朴实的写出来,而且说不定哪一天就出圈了呢,称为领域经典之作。
5. 相关工作(chapter 2. Related Work)
2.1 讲的是非监督的基于特征的一些工作。……->词嵌入->ELMo->……
2.2 非监督的,基于微调的方法。......-> GPT->......
2.3 在有标号的数据(Superivised Data)上做迁移学习。
比如自然语言推理和机器翻译领域有着带标号的大规模数据集,在这些数据集上训练好了模型后再在别的任务上使用。CV领域这种方法用的更加广泛。但是NLP这种方法不是很理想。
【评】BERT之后的工作证明没有标号的大量数据集训练出模型比在有标号的相对较小的数据集上训练效果更好。同样的方法在CV上也开始被使用。
6. BERT模型(chapter 3. BERT)
第一段:
BERT里有两个步骤:预训练&微调。预训练时,模型是在一个没有标号的数据赛训练的;微调时候同样使用一个BERT模型,但是他的权重就是被初始化成我们在预训练时候得到的权重,所有的权重都会在微调时候参与训练,使用的数据是下游任务的带标号数据。每一个下游任务都新创建了一个BERT模型,虽然他们都是用最早的那个预训练好的BERT模型作为初始化,但是下游任务会根据自己的数据训练好的自己的模型。这些会在图1做展示。
【评】预训练和微调不是BERT独创,在CV中用的很多,但是作者还是在这里做了一个简单的介绍,这是一个非常好的习惯。比如在写论文的时候需要用到一些技术,而且你觉得可能应该所有人都知道,于是就一笔带过了,这个不是特别好,论文是需要自洽的,后来读论文者可能不知道什么是微调/预训练,但是这又是了解你的方法不可或缺的一部分,最好还是做一个简单说明,不要简单列个参考文献,让大家点进去看一下,这会给阅读带来困难。
跳到图一看一下:
左侧是预训练,可以看到输入时没有标号的句子对 ,大致可以看到建立一个BERT模型,将其权重训练好。右侧是微调,对于每个下游任务,每个任务创建一个同样的BERT模型, 但是其权重初始化值是预训练好的权重值。
BERT 模型架构
BERT的架构是多层的双向transformer编码器,且直接基于原始的论文和代码。【评】 [因为它没有做什么改动,所以直接在这里说不给大家介绍了, 直接看原始论文或者博客,就可以知道怎么回事。当然这样写是没问题的,比如你把人家东西直接拿来用,所以在讲自己贡献的第三章不讲那一块是没关系的,但是还是建议大家,碰到这种情况(基于别人的文章)时,还是应该在第二章(相关工作)对那个文章做一定的介绍,至少讲清楚后面字母变量的含义等等。]
BERT架构的细节:
调了三个 参数:①transformer块的个数(),②隐藏层大小(),③自注意力机制多头的头的个数()。有两个模型:BERT-base(,所以其可学习的参数时110M,即1.1亿Q:如何计算?【1】Bert/Transformer模型的参数大小计算_Lisen’s blog-CSDN博客_transformer参数量 【2】小白bert参数计算_persistinlife的专栏-CSDN博客_bert参数计算
),BERT-large(,总参数个数:340M)。【解释:从Base到Large】large是把层数翻了一倍,然后宽度从768变成了1024,1024是怎么来的?是因为BERT模型的复杂度跟层数是线性的关系,跟宽度是平方的关系,因为深度变成了以前的两倍,那么在宽度上也选择一个值使得增加的平方大概是之前的两倍,头的个数变成了16,这是因为每个头的维度都固定在了64,因为宽度增加了,所有头数也增加了。
BERT-base的选择是为了和GPT做比较,二者参数量相近。BERT-large当然是用来刷榜的。
贴心的沐神讲解如何从超参数换算得到可训练参数总和~
接下来我们讲解下如何由超参数大小得到可训练模型参数总数的大小,顺便作为transformer的复习。
模型的可学习参数量主要来自两块,第一块是嵌入层,第二块是transformer块。
①嵌入层:嵌入层就是一个句子,其输入是字典的大小(30522),输出等于隐藏单元的个数
②transformer块
Q,K,V每个参数是H × H,concate之后再投影,H×H,
之后是FFN,FFN参数量:H×H×8
(5)一文懂“NLP Bert-base” 模型参数量计算_良师,益友-CSDN博客
小白bert参数计算_persistinlife的专栏-CSDN博客_bert参数计算
Bert/Transformer模型的参数大小计算_Lisen’s blog-CSDN博客_transformer参数量
BERT 的输入输出
前文提过,下游任务的输入又可能是一个句子也有可能是句子对,为了使BERT可以处理所有这些任务,BERT的输入既可以是一个句子,也可以是句子对。具体来讲:一个句子可以是一段连续的文字,不一定是真正语义上的一个句子。BERT的输入叫做一个序列,所谓序列,可以是一个句子,也可以是两个句子。这里和transformer是不一样的,transformer训练的时候的输入是一个序列对,因为他的编码器和解码器分别会输入一个序列。但是BERT只有一个编码器。为了能处理两个句子的情况,我们需要把两个句子变成一个序列。
序列是怎么构成的?
BERT使用的切词方法是Word Piece,核心思想:假如我按照空格切词的话,一个词作为一个token,因为我的数据量相对来说比较大,会导致词典特别大,可能是百万级别。根据之前模型参数计算方法,如果是百万级别的话,就导致模型的可学习参数都集中在嵌入层上;而Word Piece的思路,如果一个词在整个词库里出现的概率不大的话,那么应该把它切开,看他的子序列,如果其中一个子序列(可能是一个词根)的出现概率比较大,那么只保留这个子序列即可,这样可以将一个比较长的词切成很多一段一段的片段,而且这些片段是经常出现的,于是就可以用一个相对来说比较小的(3万)词典就能表示比较大的文本库了。
切好词后,如何把两个句子放在一起?
序列的第一个词永远是一个特殊的记号[CLS],代表是classification。这个词的作用是,BERT希望它最后的输出代表是整个序列的信息,比如说对整个句子层面的信息。因为BERT使用的是transformer的编码器,所以它的自注意力层里面每一个词都会去看,输入里面所有词的关系,就算这个[CLS]放在了第一个位置,他也是有办法能看到之后所有的词,所以[CLS]放在第一个是没有关系的,不一定要放在最后。另外要把两个句子合并在一起,但是又要做句子层面的分类,所有需要区分开来这两个句子,有两个办法来区分:①每个句子后面放一个特殊的词[SEP],代表是separate,②学习一个嵌入层,来表示这个句子是第一个句子还是第二个句子。
图一中:可以看到输入的组成,[CLS],[SEP];每个token进入BERT,得到token的embedding表示。对于BERT而言,就是输入一个序列,输出一个序列,最后一个transformer的输出就是词元的BERT表示,再在后面贴加额外的输出层来得到我们要的结果。
对于一个词元,他进入BERT后的embedding表示有三部分:token本身的embedding,位置的embedding,区分在哪一个句子的embedding。如图二。
给一个词元序列,得到一个向量序列,最后进入transformer块。
token embed: 词元的embedding
segment embed:输入大小是2,标识token是属于a句子还是b句子
position embed:位置的嵌入层,输入大小是token的最大长度。每个词元的位置信息。(transformer的位置向量是手动构建的,但是Bert中,不管token所属哪个句子的表示还是位置的向量的表示,都是通过学习得来的)
最终Bert的输入就是如上三个嵌入层相加。
以上是预训练和微调过程中相同的地方。接下来介绍不同的地方。
预训练时候主要有两个东西比较关键:①训练的目标函数②预训练所用的数据。
chapter 3.1 Pre-training BERT
预训练主要有两个任务:带掩码的语言模型(MLM)和NSP(下一句预测)
- Masked LM。
一部分时讲了为什么双向好,带掩码的好处是什么(前文都已经提到过)。
另一部分是新的:对于一个输入的词元序列,如果一个token是Word Piece生成的话,那么其有15%的概率会随机替换成一个掩码,特殊的词元如句子的第一个词元和中间的分割词元不再做替换。那么假如输入长度1000,那么要预测150个token;但这样也存在问题,在做掩码时候,会把词元替换成一个特殊的token[MASK],那么在训练的时候大概会看到15%的词元是[MASK]但是在微调时候是没有这个[MASK]存在的。这就导致预训练和微调时候看到的数据会不一样,这是有问题的;提出的解决办法是:对于这15%被选中的要被替换为[MASK]的词,其中80%是真的替换为[MASK],还有10%概率被替换为随机的一个词元,还有10%保持原样,但是还是用它来做预测。为什么是80,10,10%呢,作者讲有实验证明这个比例是比较好的。 -
Next Sentence Prediction(NSP)
NSP预测下一个句子用在QA和自然语言推理(NLI)里面,输入时句子对。那么需要Bert学到一些句子层面的信息,具体讲:一个输入序列里有两个句子a,b。50%概率,b是真的在a之前,还有50%概率b是随机选出来的句子,和b没有关系。
:原词是flightless,但是它出现概率不高,所以Word Piece将其看成了两个词flight和less,##表示后面的词在原问中是跟着前面那个词。
最后讲了用到的数据集:BooksCorpus和Wikipedia。强调应该使用文本层面的数据集,即数据集中式一篇一篇的文章而非随机打乱的句子,这是因为transformer确实能处理较长的序列,所以给一整个大的文本序列来当然效果会好一些。
3.2 Fine-tuning BERT
Bert和编码器-解码器架构的模型有什么不一样?
因为Bert把整个句子对都放进去了,所以self-attention能够在两端之间相互能够看。但是你在编码器-解码器架构中,编码器一般是看不懂解码器的东西。Bert这个具有优势的,但是也付出了代价:不能像transformer一样做机器翻译。
做下游任务的时候,会根据任务,设计任务相关的输入和输出。好处是Bert模型不需要变,主要是怎么把输入改成我要的那个句子对,如果确实有两个句子,那就对于句子a和b,但是如果只有一个句子,比如分类的话,那么b就没有了。根据下游任务的要求,要么是拿到第一个词元[CLS]所对应的输出去做分类,或者是拿到所有词元对应的输出;拿到Bert的输出后再加一层任务相关的输出层,比如softmax获得最终的标号(分类)。
7. 实验(chapter 4 Experiments)
- GLUE。
把[CLS]对应的输出拿出来,学习一个输出层,放进softmax,就解决了一个多分类问题。 - SQuAD。斯坦福分QA数据集。
任务:给出一个问题和一段话,答案是在这段话的某个片段,找出这个片段的开始和结尾。理解为:对每个词元,判断是不是答案的开头/结尾。具体讲就是学习两个向量S和E,分别对应词元是答案开始/结尾概率,对每个词元(第二句话里的每个词元)相乘,最后做softmax,就可以得到这段话每个词元它是答案开始/结尾的概率。
文中用的是3个epoch,学习率5e-5,batch_size是32,但是后来大家应用发现,Bert微调时,结果非常不稳定,同样的参数和数据结果会差很多,所以3个epoch时不够的。并且Bert用的是一个不完全的Adam优化器,这在Bert训练很长时间时没有问题,但是Bert如果只训练那么小小的一段时间的话,那么不完全的Adam就会造成较大的影响,所以此时应该换回正常的Adam。 - SQuADv2.0 4. SWAG
略
chapter 5 Ablation Studies
介绍Bert中的设计对于模型的贡献是怎样的。
5.2 NLP模型确实是越大效果越好。
5.3 假设不用Bert做微调的,而是把Bert的输出作为一个静态特征用在模型中会怎么?结论:效果确实没有微调那么好
8. 评论总结
先写了Bert和ELMo、GPT的区别,然后介绍Bert是怎么回事,接下来是在实验上设置时什么样子,最后时比结果。
文章认为其最大贡献是双向性,这有待商榷,其他还有更多贡献更大的点。