【论文阅读】BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

前言

BERT 是 Google 于 2018 年提出的 NLP 预训练技术,全称是 Bidirectional Encoder Representations from Transformers,直译可以理解为双向 Transformer 的 Enocder。你可能听说过 BERT ,也知道它有多么神奇,本文主要通过对论文原文以及其他的一些资料,来帮助大家更全面的认识 BERT。

As a result, the pre-trained BERT model can be finetuned with just one additional output layer to create state-of-the-art models for a wide range of tasks, such as question answering and language inference, without substantial taskspecific architecture modifications.

Overview

首先我们对 BERT 有一个整体的认识,之后再探讨各种细节。在使用预训练语言模型进行 NLP 的任务中,主要有两种策略:

  • feature-based:针对具体的任务场景模型的结构也需要修改,预训练模型作为一个补充。例如ELMo
  • fine-tuning:模型参数与具体任务基本无关,通过简单地微调预训练的模型在下游任务上进行训练。例如OpenAI GPT

Differences in pre-training model architectures

BERT 同样是基于fine-tuning的,但是在上面的两种方法中,使用的都是单向的语言模型,基于同样的损失函数。例如 GPT ,在对一个单词进行表示的时候,只能关注这个单词前面的 token。

本文作者通过使用新的训练目标 Masked Language Model 来解决单向语言模型的局限性,通过融合左右两个方向的语境,来训练一个双向、深度的 Transformer 模型

对比 ELMo,虽然都是“双向”,但是目标函数其实是不同的。ELMo 分别以 P(wiw1,,wi1)P(w_i \vert w_1, \cdots, w_{i-1})P(wiwi,,wn)P(w_i \vert w_i, \cdots, w_n) 作为目标函数,独立训练然后进行拼接,而 BERT 则是以 P(wiw1,,wi1,wi+1,,wn)P(w_i \vert w_1, \cdots, w_{i-1}, w_{i+1}, \cdots, w_n) 作为目标函数进行训练。

BERT

BERT 模型的训练共有两个步骤,即pre-trainingfine-tuning。在预训练期间,模型在不同的预训练任务的无标记数据上进行训练。对于微调,BERT 模型首先用预训练的参数进行初始化,然后用下游任务的有标记数据对所有的参数进行微调。

Overall pre-training and fine-tuning procedures for BERT

模型架构

BERT 模型是一个双向多层的 Transformer 的 Encoder 模型,关于 Transformer 的部分这里不进行介绍,可以参考 Google 的论文 Attention Is All You Need。作者提出两种规模的 BERT 模型:

BERTBASE(L=12,H=768,A=12,Total Parameters=110M)\text{BERT}_{\text{BASE}}(L=12,H=768,A=12,\text{Total Parameters=110M})

BERTLARGE(L=24,H=1024,A=16,Total Parameters=340M)\text{BERT}_{\text{LARGE}}(L=24,H=1024,A=16,\text{Total Parameters=340M})

其中L,H,AL,H,A分别表示 Transformer block 的数量、隐藏层维度和 multi-head attention 头数。

Embedding

BERT 模型既可以以单个句子为输入,也可以处理类似问答系统这样的两个句子的情形。对于一个 token,它的输入表示为所有对应的 token,segment,position 的 embedding 之和,如下图:

BERT input representation

其中[CLS][SEP]为特殊的 embedding,分别表示首字符和分隔符。

  • Token Embedding:词向量
  • Segment Embedding:用来区别两种句子,因为预训练不光做 LM 还要做以两个句子为输入的分类任务
  • Position Embedding:和 Transformer 不一样,不是三角函数而是学习出来的

Pre-training BERT

在 BERT 中,作者提出了两个无监督的任务对 BERT 进行预训练:

  • Masked LM

    Masked LM 的任务描述为:给定一句话,随机抹去这句话中的一个或几个词,要求根据剩余词汇预测被抹去的几个词分别是什么。

  • Next Sentence Prediction

    Next Sentence Prediction 的任务描述为:给定一篇文章中的两句话,判断第二句话在文本中是否紧跟在第一句话之后。

实际上,BERT 的预训练阶段就是将上面两个任务结合起来,同时进行,然后将所有 Loss 相加。

Masked LM

就像之前提到的,作者认为深度双向的模型要比单向的语言模型或者说浅层的语言模型要好。在之前的模型中,每个词只使用其左边(或右边)的词来确定上下文,即从左到右训练或者从右到左训练。

举个例子:I made a bank deposit

其中,对bank进行单向表示时,其含义只取决于I made a,并不包括deposit

而 BERT 则可以同时根据单词两侧的上下文,即通过I made a ... deposit来表示bank

为了训练一个双向的语言模型,BERT 使用了一种不同的方法。类似于完形填空,简单来说,就是随机遮盖或替换一句话里面的任意字或词,然后让模型通过上下文预测那一个被遮盖或替换的部分,最终的损失函数只计算被 mask 掉那个部分

具体来说,BERT 会随机遮住15%的词来预测这些词,所以是一种 Masked LM,需要预测的只是被 mask 的位置上的词,而不需要重现整个句子。实际操作如下:

  1. 随机把一句话中 15% 的 token(字或词)替换成以下内容:
    1. 80% 的几率被替换成[MASK],例如 my dog is hairy → my dog is [MASK]
    2. 10% 的几率被替换成任意一个其它的 token,例如 my dog is hairy → my dog is apple
    3. 10% 的几率原封不动,例如 my dog is hairy → my dog is hairy
  2. 通过模型预测和还原被遮盖掉或替换掉的部分,计算损失时,只计算被随机遮盖或替换的部分。
1
2
Input: the man went to the [MASK1] . he bought a [MASK2] of milk.
Labels: [MASK1] = store; [MASK2] = gallon

对于 Mask 如何进行也是有技巧的:

  1. 如果句子中的某个 token 100%都会被 mask 掉,但是在实际 fine-tuning 的时候并没有这个 token,这就导致预训练和 fine-tunining 不一致。
  2. 加入随机 token 的原因是因为 Transformer 要保持对每个输入 token 的分布式表征。在上面的例子中,它看到的apple可能是被替换的词。这样强迫模型在编码当前时刻词的时候不能太依赖于当前的词,而要考虑它的上下文,甚至对其上下文进行”纠错”。比如上面的例子中,模型在编码 apple 时,根据上下文 my dog is,应该把 apple 编码成 hairy 的语义而不是 apple 的语义。
  3. 另一方面,至于随机替换造成的负面影响,因为一个单词被随机替换的概率只有15%×10%=1.5%15\%\times10\%=1.5\%,这个负面影响是可以忽略不计的。

实际上,作者也对不同的 Mask 情况进行了消融实验:

Ablation over different masking strategies

由于每个 batch 只有 15%的 token 被训练了,模型收敛起来会比传统语言模型更慢。不过相对于 BERT 对于精度的提升,这点效率损失也可以接受,预训练通常也不需要经常进行。

Next Sentence Prediction (NSP)

许多基于对话问答(QA)或者自然语言推理(NLI)的任务都是基于两句话关系的理解基础上的,而上面的语言模型显然并不能很好的捕捉这种特性。为了训练一个能够理解句子关系的模型,作者引入了一个二分类问题,预测下一个句子。

具体来说,在为每个预训练例子选择句子 A 和 B 时,50%的情况 B 是紧随 A 的实际下一句(标记为IsNext),50%的情况是语料库中的随机句子(标记为NotNext)。

1
2
3
4
5
6
7
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

Fine-tuning BERT

Google 利用预训练好的 BERT 模型一共在 11 个任务通过 fine-tuning 获得了 SOTA 的表现。这 11 个任务可以被归为 4 种类型,如下图所示。在这些任务中,(a)和(b)是 sequence-level 任务,而(c)和(d)是 token-level 任务。。

Illustrations of Fine-tuning BERT on Different Tasks

sequence-level

sentence pair 和 sentence single 分别对应(a)和(b),此时只需要对第一个位置的 [CLS] 添加一个 Linear Classifier 即可进行分类任务,其中新添加的全连接层的参数需要从头开始学习,而 BERT 中的参数微调就可以了。

token-level

将句子中各个字对应位置的 output 分别送入不同的 Linear,预测出该字的标签。其实这本质上还是个分类问题,只不过是对每个字都要预测一个类别。

实验

具体的实验部分就不介绍了,自然就是最优的效果。

GLUE Test results

PyTorch 实现

EmoryHuang/nlp-tutorial

总结

Word2vec 作为里程碑式的进步,对 NLP 的发展产生了巨大的影响,但 Word2vec 本身是一种浅层结构,而且其训练的词向量所“学习”到的语义信息受制于窗口大小;ELMo 的出现在一定程度上解决了这个问题,ELMo 是一种双层双向的 LSTM 结构,其训练的语言模型可以学习到句子左右两边的上下文信息(并不是真正意义上的上下文);OpenAI 的 GPT 是利用了 Transform 的编码器作为语言模型进行预训练的,之后特定的自然语言处理任务在其基础上进行微调即可。

就像最开始提到的,ELMo 和 GPT 最大的问题就是传统的语言模型是单向的,根据之前的历史来预测当前词,但是我们不能利用后面的信息。BERT 的出现,似乎融合了它们所有的优点,因此才可以在诸多后续特定任务上取得最优的效果。

最后对 BERT 做个总结吧:

  • Masked Language Model(MLM)预训练任务,能够获取上下文相关的双向特征表示;
  • Next Sentence Prediction(NSP)预训练任务,擅长处理句子或段落的匹配任务;
  • 特征抽取机制 Transformer;
  • 大规模、高质量的文本数据;

当然 BERT 还是有几点问题:

  • 预训练与微调的模式不匹配问题。事实上,MLM 的提出就有缓解这个问题的目的,但不能从根本上解决;
  • BERT 句子中的所有[MASK]都是独立的

最后,针对这些问题,Google,Google 提出了 XLNet,在各个方面均超过了 BERT,下次再看这个吧。

参考资料