Bert 自 Google 于 2018 年发表至今,一直给人们带来惊喜,期间也陆陆续续因为Bert出现的原因多了不少新的岗位,甚至公司 JD 上都明确表明必须懂 Bert。
它在 11 项自然语言处理任务中均表现出惊人的成绩:包括将 GLUE 基准推至 80.4%(绝对改进率7.6%),MultiNLI 精度达到 86.7%(绝对改进 5.6%)和 SQuAD v1.1 问题回答测试 F1 到 93.2。
谷歌团队的 Thang Luong 直接定义:BERT 模型开启了 NLP 的新时代!
Bert 能有这么好的效果,深入其原理本身,又究竟好在哪里?
Bert是什么?
BERT,全称是 Bidirectional Encoder Representation from Transformers,基于语义理解的深度双向预训练Transformer。
要理解 Bert,5 个关键词帮你理解其思想,分别是 Pre-training、Deep、Bidirectional、Transformer、Language Understanding。
Deep
Bert 与 Transformer 不同,Bert 的神经网络层更深,意味着它能更准确表达语义的理解,提取更多特征。
Bidirectional
BERT 被设计成一个深度双向模型,使得神经网络更有效地从第一层本身一直到最后一层捕获来自目标词的左右上下文的信息。
传统上,我们要么训练语言模型预测句子中的下一个单词( GPT 中使用的从右到左的上下文),要么训练语言模型预测从左到右的上下文。这使得我们的模型容易由于信息丢失而产生错误。
Transformer
Bert 是基于 Tranformer 的深度双向语言表征模型,也就是利用 Transformer 结构构造了一个多层双向的Encoder 网络。它的特点之一就是所有层都联合上下文语境进行预训练。
Bert 的目标是生成预训练语言模型,所以只需要 Encoder 机制。Transformer 的 Encoder 是一次性读取整个文本序列,而不是从左到右或者从右到左按顺序读取。
Pre-training
pre-training 的意思是,作者认为,确实存在通用的语言模型,先用大量数据预训练一个通用模型,然后再微调模型,使其适用于下游任务。为了区别于针对语言生成的 Language Model,作者给通用的语言模型,取了一个名字,叫语言表征模型 Language Representation Model。
深度学习就是表征学习,大多只在预训练表征微调的基础上加一个线性分类器作为输出就可以完成下游任务。
Language Understanding
Bert 是一个语言表征模型,能实现语言表征目标训练,通过深度双向 Transformer 模型达到语义理解的目的。
整合以上特点,我们就可以很直观的理解 Bert,Bert 是一个用 Transformer 作为特征抽取器的深度双向预训练语言理解模型。Bert 就是一个预训练模型,利用双向 Transformer,通过大量数据训练一个语言表征模型,这是一个通用模型,通过对其微调来适用下游任务,包括分类,回归,机器翻译,问答系统等等任务。
深入理解Bert工作原理
Bert 的模型结构如图左边第一个,Bert 采用了 Transformer Encoder,也就是每时每刻的 Attention 计算都能够得到全部时刻的输入。
OpenAI GPT 采用 Transformer 的 Decoder,每个时刻的 Attention 计算只能依赖于该时刻前的所有时刻的输入,因为 OpenAI GPT 是单向语言模型。
ELMO 则采用的是 LSTM,这个模型虽然可以学习到前后语义,但是输出依赖于前面的输入,决定了 EMLO 的网络层数不会太多,会造成大量耗时,这就决定了 ELMO 提取的特征有限。
Bert的输入有什么不同
Bert 的输入相较其它模型,采用了三个 Embedding 相加的方式,通过加入 Token Embeddings,Segment Embeddings,Position Embeddings 三个向量,以此达到预训练和预测下一句的目的。
如上图,Bert 的输入 Input 是两个句子:"my dog is cute","he likes playing"。首先会在第一句开头加上特殊Token [CLS] 用于标记句子开始,用 [SEP] 标记句子结束。
然后对每个 Token 进行 3 个 Embedding,词的 Embedding (Token Embeddings),位置 Embedding (Position Embeddings),句子 Embedding (Segment Embeddings)。最终将三个 Embedding 求和的方式输入到下一层。
下面详细介绍下三个 Embedding。
Token Embeddings
通过建立字向量表将每个字转换成一个一维向量,作为模型输入。特别的,英文词汇会做更细粒度的切分,比如playing 或切割成 play 和 ##ing,中文目前尚未对输入文本进行分词,直接对单子构成为本的输入单位。将词切割成更细粒度的 Word Piece 是为了解决未登录词的常见方法。
假如输入文本 ”I like dog“。下图则为 Token Embeddings 层实现过程。输入文本在送入 Token Embeddings 层之前要先进性 tokenization 处理,且两个特殊的 Token 会插入在文本开头 [CLS] 和结尾 [SEP]。
Bert 在处理英文文本时只需要 30522 个词,Token Embeddings 层会将每个词转换成 768 维向量,例子中 5 个Token 会被转换成一个 (6, 768) 的矩阵或 (1, 6, 768) 的张量。
Segment Embedding
Bert 能够处理句子对的分类任务,这类任务就是判断两个文本是否是语义相似的。句子对中的两个句子被简单的拼接在一起后送入模型中,Bert 如何区分一个句子对是两个句子呢?答案就是 Segment Embeddings。
Segement Embeddings 层有两种向量表示,前一个向量是把 0 赋值给第一个句子的各个 Token,后一个向量是把1赋值给各个 Token,问答系统等任务要预测下一句,因此输入是有关联的句子。而文本分类只有一个句子,那么 Segement embeddings 就全部是 0。
Position Embedding
由于出现在文本不同位置的字/词所携带的语义信息存在差异(如 ”你爱我“ 和 ”我爱你“),你和我虽然都和爱字很接近,但是位置不同,表示的含义不同。
在 RNN 中,第二个 ”I“ 和 第一个 ”I“ 表达的意义不一样,因为它们的隐状态不一样。对第二个 ”I“ 来说,隐状态经过 ”I think therefore“ 三个词,包含了前面三个词的信息,而第一个 ”I“ 只是一个初始值。因此,RNN 的隐状态保证在不同位置上相同的词有不同的输出向量表示。
RNN 能够让模型隐式的编码序列的顺序信息,相比之下,Transformer 的自注意力层 (Self-Attention) 对不同位置出现相同词给出的是同样的输出向量表示。尽管 Transformer 中两个 ”I“ 在不同的位置上,但是表示的向量是相同的。
Transformer 中通过植入关于 Token 的相对位置或者绝对位置信息来表示序列的顺序信息。作者测试用学习的方法来得到 Position Embeddings,最终发现固定位置和相对位置效果差不多,所以最后用的是固定位置的,而正弦可以处理更长的 Sequence,且可以用前面位置的值线性表示后面的位置。
偶数位置,使用正弦编码,奇数位置,使用余弦编码。
Bert 中处理的最长序列是 512 个 Token,长度超过 512 会被截取,Bert 在各个位置上学习一个向量来表示序列顺序的信息编码进来,这意味着 Position Embeddings 实际上是一个 (512, 768) 的 lookup 表,表第一行是代表第一个序列的每个位置,第二行代表序列第二个位置。
最后,Bert 模型将 Token Embeddings (1, n, 768) + Segment Embeddings(1, n, 768) + Position Embeddings(1, n, 768) 求和的方式得到一个 Embedding(1, n, 768) 作为模型的输入。
任务1:Masked Language Model
Maked LM 是为了解决单向信息问题,现有的语言模型的问题在于,没有同时利用双向信息,如 ELMO 号称是双向LM,但实际上是两个单向 RNN 构成的语言模型的拼接,由于时间序列的关系,RNN模型预测当前词只依赖前面出现过的词,对于后面的信息无从得知。
那么如何同时利用好前面的词和后面的词的语义呢?Bert 提出 Masked Language Model,也就是随机遮住句子中部分 Token,模型再去通过上下文语义去预测 Masked 的词,通过调整模型的参数使得模型预测正确率尽可能大。
怎么理解这一逻辑,Bert 预训练过程就是模仿我们学习语言的过程,要准确的理解一个句子或一段文本的语义,就要学习上下文关系,从上下文语义来推测空缺单词的含义。而 Bert 的做法模拟了英语中的完形填空,随机将一些单词遮住,让 Bert 模型去预测这个单词,以此达到学习整个文本语义的目的。
那么 Bert 如何做到”完形填空“的呢?
随机 mask 预料中 15% 的 Token,然后预测 [MASK] Token,与 masked token 对应的最终隐藏向量被输入到词汇表上的 softmax 层中。这虽然确实能训练一个双向预训练模型,但这种方法有个缺点,因为在预训练过程中随机 [MASK] Token 由于每次都是全部 mask,预训练期间会记住这些 MASK 信息,但是在fine-tune期间从未看到过 [MASK] Token,导致预训练和 fine-tune 信息不匹配。
而为了解决预训练和 fine-tune 信息不匹配,Bert 并不总是用实际的 [MASK] Token 替换 masked 词汇。
my dog is hairy → my dog is [MASK] 80%选中的词用[MASK]代替
my dog is hairy → my dog is apple 10%将选中的词用任意词代替
my dog is hairy → my dog is hairy 10%选中的词不发生变化
为什么 15% 的 Token 不完全 MASK?如果只有 MASK,这个预训练模型是有偏置的,也就是只能学到一种方式,用上下文去预测一个词,这导致 fine-tune 丢失一部分信息。
加上 10% 的随机词和 10% 的真实值是让模型知道,每个词都有意义,除了要学习上下文信息,还需要提防每个词,因为每个词都不一定是对的,对于 Bert 来说,每个词都需要很好的理解和预测。
有些人会疑惑,加了随机 Token,会让模型产生疑惑,从而不能学到真实的语义吗?对于人来说,完形填空都不一定能做对,而将文本中某些词随机替换,更是难以理解,从概率角度来说,随机 Token 占比只有 15% * 10% = 1.5%,预料足够的情况下,这并不会影响模型的性能。
因为 [MASK] Token 占比变小,且预测难度加大的原因,所以 MASK 会花更多时间。
任务2:Next Sentence Prediction
在许多下游任务中,如问答系统 QA 和自然语言推理 NLI,都是建立在理解两个文本句子之间的关系基础上,这不是语言模型能直接捕捉到的。
为了训练一个理解句子关系的模型,作者提出 Next Sentence Prediction,也即是预训练一个下一句预测的二分类任务,这个任务就是每次训练前都会从语料库中随机选择句子 A 和句子 B,50% 是正确的相邻的句子,50% 是随机选取的一个句子,这个任务在预训练中能达到 97%-98% 的准确率,并且能很显著的提高 QA 和 NLI 的效果。
Input = [CLS] the man went to [MASK] store [SEP]
he bought a gallon [MASK] milk [SEP]
Label = IsNext
Input = [CLS] the man [MASK] to the store [SEP]
penguin [MASK] are flight ##less birds [SEP]
Label = NotNext
模型通过对 Masked LM 任务和 Next Sentence Prediction 任务进行联合训练,使模型输出的每个字 / 词的向量表示都能尽可能全面、准确地刻画输入文本(单句或语句对)的整体信息,为后续的微调任务提供更好的模型参数初始值。
Bert如何实现fine-tune
fine-tune 就是指在已经训练好的语言模型基础上,使用有标签的数据对参数进行调整,使其更好的适用于下游任务。如对于分类问题在语言模型基础上加一层 softmax 网络,然后再新的预料上重新训练进行 fine-tune。
Bert的主要贡献
Bert 采用深度双向 Transformer 语言模型,通过 Mask LM 来达到训练深度双向预训练模型,较之前使用单向语言模型训练更准确,信息量更大,且语义理解更准确。
论文表明,预训练模型能省去特定工程需要修改体系架构的麻烦,Bert 是第一个基于 fine-tune 的语言模型,它在大量句子级和 Token 级任务上展现了很好的性能。
Bert 的成功,一个重要原因就是数据量大,计算资源丰富。BERT 训练数据采用了英文的开源语料 BooksCropus以及英文维基百科数据,一共有 33 亿个词。同时 BERT 模型的标准版本有 1 亿的参数量,与 GPT 持平,而 BERT的大号版本有 3 亿多参数量,这应该是目前自然语言处理中最大的预训练模型了。
当然,这么大的模型和这么多的数据,训练的代价也是不菲的。谷歌用了 16 个自己的 TPU 集群(一共 64 块 TPU)来训练大号版本的 BERT,一共花了 4 天的时间。
对于是否可以复现预训练,作者在 Reddit 上有一个大致的回复,指出 OpenAI 当时训练GPT用了将近 1 个月的时间,而如果用同等的硬件条件来训练 BERT 估计需要 1 年的时间。不过他们会将已经训练好的模型和代码开源,方便大家训练好的模型上进行后续任务。