零、前言:
📕欢迎访问:
Bilibili:space.bilibili.com/57089326
🍬观前小剧场:
Q:2023年了,如果还没看过BERT,或者是刚入门的小白,该怎么办?
A:没关系,那就现在开始看吧,只有先开始才能走向新的开始!
Q:我觉得你说的有道理,不过话说回来,正确的阅读姿势是什么?
A:我想,你需要准备一杯咖啡或者茶,安静地坐下来,以及30~45分钟的时间,慢下来阅读,这可能会比急急忙忙地看一遍的收获更大,印象更深。
Q:如果我只是想了解个大概,并不想知道具体的细节怎么办,我现在就在通勤的地铁上,我也没有位置坐,很急!
A:没关系,笔者考虑到了这种情况,只需要阅读前两节或者前三节的内容即可。
A:事不宜迟,如果没有其他问题的话我们就开始吧!
一、BERT简介:
BERT开源仓库:github.com/google-rese…
一句话解释BERT:
是一个预训练的语言表征模型,是一个深度的、双向的Transformer,用来做预训练,针对一般的语言理解任务。
Q:预训练是什么?
A:预训练就是,事先在一个较大的数据集上面训练好一个模型的这次训练过程(在这之后,再把这个模型拿到别的任务上面继续训练)。
BERT出现的目的和意义:
BERT的目的是通过在所有层中对左右上下文进行联合条件反射,从未标记的文本中预训练深度双向表示。
普适性:预训练的BERT模型可以通过一个额外的输出层进行微调,从而应用于更多样的任务,如问答和语言推理,并且无需对特定的任务架构进行大量的修改。
BERT出现的意义:让我们能够从一个大数据集上训练好一个比较深的神经网络,用于很多不同的NLP任务上面,既简化了训练,又提升了性能。
Q:BERT名字的由来?
A:B=Bidirectional, E=Encoder, R=Representations, T=Transformers
乍一看,怎么BERT这个名字和他的含义不是特别沾边啊?
事实上,这是因为BERT基于了ELMo的一些工作,而这两个名字都是美国的一个老少皆宜的少儿英语学习节目——芝麻街(笑)。
芝麻街中文官网:www.sesamestreetchina.com.cn/
于是乎,一发不可收拾,NLP领域的文章就把芝麻街中人物的名字用了个遍。
二、BERT与GPT、ELMo的关系
由于BERT是基于GPT和ELMo这两个工作的,我们就先来看一下他们之间有什么区别。
BERT与GPT的区别:
在2018年,GPT是单向的,基于左边的上下文信息,来生成新的文段。(2023年有待考究);
而BERT不仅用了左侧的信息,还用了右侧的信息,所以说是一个双向的操作,即Bidirectional。
BERT与ELMo的区别:
ELMo用的是基于RNN的架构,在用于一些特定的下游任务时,需要对架构做一定的调整;
BERT用的是Transformer,只需要改最上层的就行了,这里指的是BERT的那个额外的输出层。
之后文章列举了自己在11个NLP任务上面的更好的表现,并且科学地给出了绝对值和相对值,这是非常优秀的一种写文细节,我们在写论文的时候也可以借鉴这种写法。
三、BERT预训练
预训练在NLP的应用:
采用预训练的两大类自然语言任务:
- 句子层面的任务:通过整体分析来预测句子之间的关系,如对句子情绪的识别,或者两个句子之间的关系 ;
- 词语层面的任务:标记级任务,如命名实体识别和问答(比如”张三”这个词是不是人名?还是一种食物?)。
事实上,预训练在计算机视觉领域已经用了很多年了,BERT也不是自然语言处理领域第一个用预训练的,只是说,因为BERT的效果太好了,以至于**”抢了风头”**。
预训练的表示,减少了对许多高度工程化的任务的特定架构的需求。
BERT是第一个基于调优的表示模型,在大量句子级和记号级的任务上实现了最先进的性能,甚至优于很多特定于某种任务的架构。
NLP中两种预训练的策略:
语言模型预训练应用于下游任务,或者说特征表示,有两种现成的策略:基于特征、基于微调。
- 基于特征:如ELMo——使用特定于任务的RNN架构,把学到的特征和输入一起作为特征的表达。
- 基于微调:如GPT——引入了最小的任务特定参数,并通过简单的一点点微调所有预训练参数,来对下游任务进行训练。
在当时,这些的技术限制了预训练表征的力量,主要是因为标准语言模型是单向的。
传统的单向语言模型,或者是浅层拼接两个单向语言模型,只能获取单方向的上下文信息。
如果要获取句子层面,或者说句子之间的信息的话,就显得有些吃力了。
那么,BERT是如何来解决这一问题的呢?
MLM——来自远古的秘宝:
BERT的使用Transformer的双向编码器表示来改进基于微调的方法;
使用带掩码的语言模型MLM, Masked Language Model对双向的Transformer进行预训练,缓解了单向性约束,生成深度的双向语言表征。
MLM使得能融合左右上下文,所以才能预训练深度双向Transformer,收到了Cloze的启发(这个竟然是1953年的论文)。
那么MLM在做什么呢?其实就是每一次随机选择一些字,然后**”盖住”,目标函数显而易见了,就是预测那些被盖住的字**。
有没有**”完形填空”**的感觉?事实上就是这样。
有了这个概念,我们就方便理解了,做完形填空的时候,就是不仅要看左边的文段,还要看右边的文段才行。
MLM除了完形填空之外,对于**”句子匹配”**也是有帮助的(怎么都是英语考试的题型)。
句子匹配的意思是说,随机的选两个句子,目标函数是要判断这两个句子在原文中是不是相邻的。
(有点像句子匹配的题目吧?虽然我也不知道那个题目具体叫什么名字)
有标注的数据集一定更好吗:
事实上,BERT和它之后的一些工作证明了,有些时候,在NLP任务上,通过没有标号的、大量的数据集训练的模型,效果比有标注的、规模小一些的数据集上,效果更好。
这样的想法也在被计算机视觉等其他领域所采用。
BERT预训练与微调:
预训练Pre-Training:
让我们再次回顾一下预训练在干什么,事实上,就是在一个没有标注的数据集上训练BERT模型。
有两个关键点:目标函数、预训练数据。
微调Fine-Tuning:
同样是用的BERT模型,但是初始化权重为预训练过程中得到的权重。
所有的权重,在微调的过程中都会参与训练,并且用的是有标号的数据。
基于预训练得到的模型,在每个后续的下游任务中都会训练出适用于当前任务的模型。
以下是预训练和微调的图示:(后续还会介绍其细节,先大致浏览一下即可)
论文的这一部分对这些前置的技术做了简要的介绍,这是非常好的习惯。倘若是对于大家可能熟知的任务一笔带过,会显得整个文章不那么得体,不那么自洽。
四、深入BERT模型
BERT模型架构:
简而言之,就是一个多层的、双向的Transformer编码器。
BERT调整了Transformer的三个参数:L(Transformer块的个数)、H(隐藏层维数)、A(自注意力机制的多头的头的数目)。
BERTBASEBERT_{BASE}:L=12,H=768,A=12,Total Parameters=110ML = 12, H = 768, A = 12, Total Parameters=110M
BERTLARGEBERT_{LARGE}:L=24,H=1024,A=16,Total Parameters=340ML = 24, H = 1024, A = 16, Total Parameters=340M
如何把超参数换算成可学习参数的大小呢?
BERT模型可学习参数计算:
可学习参数主要分为两块:嵌入层、Transformer块,过程有图简述之:
自注意力机制本身并没有可学习参数,但是对于多头注意力,会把进入的所有K(Key)、V(Value)、Q(Query)都做一次投影,每次投影的维度为64,而且有A×64=HA × 64 = H;
如上,前面的自注意力块有4H24H^2个参数,MLP有H×4H×2=8H2H×4H×2=8H^2个参数,则每个Transformer块中有12H212H^2个参数;
最后再乘LL和字典大小,得到Sum=30000H+12LH2Sum = 30000H+12LH^2,代入BERTBASEBERT_{BASE}可以获得确实是大约110M110M个参数。
BERT输入输出:
输入:既可以是”句子”,也可以是”句子对”,不过事实上,这里的句子和句子对都不是狭义上的概念,事实上是一个**”序列”**。
因为BERT不同于一般的Transformer,BERT只有编码器,而Transformer有编码器和解码器,所以为了能够处理两个句子的情况,需要把两个句子变成一个序列。
切词方法:WordPiece
如果说按照空格来切词的话,也就是一个词对应一个Token,那样会导致字典大小特别大,根据上文的可学习参数计算方法,这会导致参数太多而且集中在嵌入层上面。
而WordPiece的想法是:如果一个词在整个文段里出现的概率不大的话,应该切开,看其子序列(有可能是一个词根)。
如果一个词根出现的概率比较大的话,就只保留词根就好了,这样的话会让字典大小缩小不少。
合并句子的方法:
每一个序列的第一个词都是[cls]
,代表classification,因为最后的输出要代表整个序列的一个信息,这是因为BERT使用了Transformer的编码器,所以自注意力层里的每一个词都会看输入的所有词的关系,所以可以放在第一个位置。
需要对句子进行一定的区分,以从句子层面进行分类,这里有两个办法:
- 为每个句子的末尾添加了一个
[SEP]
表示separate; - 学习一个嵌入层,来知道某个句子是第一个还是第二个。
这个时候我们再回去看一下上面给出过的图,就很好理解了:
如上图,每一个Token进入BERT,得到这个Token的Embedding表示。
整体上的效果就是:输入一个序列,得到一个序列,最后再添加额外的输出层来得到想要的结果。
对于每一个词元,进入BERT时的向量表示的是:
这个词元本身的Embedding + 它在哪个位置的Embedding + 位置的Embedding,如下图所示:
- Token Embedding:词元的Embedding层,对于每一个词元,输出一个对应的向量;
- Segment Embedding:A表示第一句话,B表示第二句话;
- Position Embedding:位置的嵌入层,输入的大小是序列最长有多长,输入的是每一个词元的位置信息(0base)
这些都是通过学习得来的,而不同于Transformer的手动构造。
MLM预训练的细节:
知道了切词的方法,我们再来看所谓的”英语题”。
完形填空:
对一个输入的词元序列,如果一个词元是由WordPiece生成的话(也就是切开来的),那就会有15%的概率随机替换成一个掩码[MASK]
。
(当然,对于[CLS]
和[SEP]
就不做替换了,这属于功能性的)
整体来看,如果输入序列是1000的话,那就要预测大约150个词。
当然,这一过程是MLM预训练独有的,在微调的过程中是看不到这一个过程的,更看不到[MASK]
。
但是,这个替换的过程,也不是100%替换成[MASK]
的,具体来说是这样的方案:
- 80%:替换为
[MASK]
- 10%:替换为一个随机的词元
- 10%:什么都不做,但是用来做预测
附录的一个例子:
句子匹配:
无论是在QA还是语言推理层面,都是一个句子对,所以最好让其学习一些句子层面的信息。
对于句子A和B,有可能是相邻,也有可能不是,那么相邻的就是正例,不相邻的就是负例,各占50%。
附录的一个例子,简单读一下Input和Label就明白了:
值得一提的是,可以看到有一个##
,这个意思是说,flightless这个词在原文中不常见,所以砍成了两个词,##
的作用是表明这原本是一个词。
两种不同策略的预训练精度结果(MLM和LR):
再看微调:
由于选择了单一个编码器的架构,而不是像传统的Transformer一样有编码器和解码器,所以会有一些缺点:
与GPT比,BERT用的是编码器,GPT用的是解码器。BERT做机器翻译、文本的摘要,也就是生成类的任务并不好做。
不过事实上,分类问题在NLP中更常见。
如下图是BERT在分类任务上的表现情况:
五、结论与尾声:
在本文发布的当时,语言模型迁移学习的经验改进表明,丰富的无监督预训练是许多语言理解系统的组成部分,这个效果是非常好的。
使得训练集比较少的任务也能够使用深度神经网络,获得比较好的效果。
特别是,这些结果使低资源任务也能从深度单向架构中受益。BERT的主要贡献是进一步将这些发现推广到深度双向架构,允许相同的预训练模型成功地处理广泛的NLP任务。