type
status
password
date
slug
summary
category
URL
tags
icon
背景
word2vec和GloVe都将相同的预训练向量分配给同一个词,而不考虑词的上下文(上下文无关表示)。考虑到自然语言中丰富的多义现象和复杂的语义,上下文无关表示具有明显的局限性。例如,在“a crane is flying”(一只鹤在飞)和“a crane driver came”(一名吊车司机来了)的上下文中,“crane”一词有完全不同的含义;因此,同一个词可以根据上下文被赋予不同的表示。
包含“上下文敏感”信息的词向量应运而生,例如TagLM(language-model-augmented sequence tagger,语言模型增强的序列标记器) [Peters et al., 2017b]、CoVe(Context Vectors,上下文向量) [McCann et al., 2017]和ELMo(Embeddings from Language Models,来自语言模型的嵌入) [Peters et al., 2018]。
ELMo、GPT与Bert的区别
- ELMo: 针对每一种自然语言处理任务,ELMo模型都有一种特定的网络结构。为每一个自然语言处理任务设计一个特定的架构大大削弱了ELMo模型的通用性
- GPT(Generative Pre Training,生成式预训练)模型为是一种通用的任务无关模型 [Radford et al., 2018]。在自然语言推断、问答、句子相似性和分类等12项任务中都取得了不错的结果。但是GPT采用 Transformer 的 Decoder,只能向前看(从左到右)。在“i went to the bank to deposit cash”(我去银行存现金)和“i went to the bank to sit down”(我去河岸边坐下)的上下文中,由于“bank”对其左边的上下文敏感,GPT将返回“bank”的相同表示(但两句话中的bank明确有不同含义)。
- Bert:ELMo对上下文进行双向编码,但使用特定于任务的架构;而GPT是任务无关的,但是从左到右编码上下文。BERT(来自Transformers的双向编码器表示)结合了这两个方面的优点。
BERT 模型的输入
BERT 的输入可以是一个句子或者是两个打包在一起的句子(比如 <Question, Answer>)。每个句子序列都会在开头位置添加特殊的分类标记符
[CLS]
作为开始。每个句子序列的结尾都添加特殊的标记符 [SEP]
作为结束。如果是两个打包在一起的句子,那么首先需要用标记符 [SEP]
分割,其次需要用 Segment Embeddings 表示两个不同的句子,如下所示。BERT 的输入是由三个 Embedding 相加而成的,分别是 Token Embeddings,Segment Embeddings,Position Embeddings。Bert模型的最终输入为
Embedding(batch_size, sentence_length, 768) = Token Embeddings (batch_size, sentence_length, 768) + Segment Embeddings(batch_size, sentence_length, 768) + Position Embeddings(batch_size, sentence_length, 768)
- Token Embeddings:把输入句子中每个字通过查询字向量表的方式转换为一维向量,作为模型的输入。在 Tokenization 之前,先把特殊标记符
[CLS]
和[SEP]
额外添加到句首和句尾。在 BERT 中,Tokenization 是用 WordPiece 来完成的 - 当输入为单个文本时,BERT输入序列是特殊类别词元“<cls>”、文本序列的标记、以及特殊分隔词元“<sep>”的连结。段索引为0,嵌入为
- 当输入为文本对时,BERT输入序列是“<cls>”、第一个文本序列的标记、“<sep>”、第二个文本序列标记、以及“<sep>”的连结。段索引为2,嵌入为
- Segment Embeddings:用于区分两个不同句子的,第一个句子是 0,第二个句子是 1。如果只有一个句子,那就都使用索引 0
- Position Embeddings:Position Embeddings 用于给模型提供序列顺序信息的。与 Transformer 中 Positional Encoding 不同,Positional Encoding 通过三角函数计算得到的,而 Position Embeddings 是通过模型训练学习得到的。BERT 使用 Position Embeddings 是因为 BERT 作为通用预训练模型,下游任务通常对词序特征要求比较高,所以选了 Postion Embeddings 这种因通过模型训练学习而潜能比较大的方式
代码-数据预处理
下面的
get_tokens_and_segments
将一个句子或两个句子作为输入,然后返回BERT输入序列的标记及其相应的段索引。- 当输入为单个文本时,BERT输入序列是特殊类别词元“<cls>”、文本序列的标记、以及特殊分隔词元“<sep>”的连结。段索引为0,嵌入为
- 当输入为文本对时,BERT输入序列是“<cls>”、第一个文本序列的标记、“<sep>”、第二个文本序列标记、以及“<sep>”的连结。段索引为2,嵌入为
数据集形式
NSP:正样本上下有顺序的句子对;负样本从整个数据集中随机选取一对句子对。
注意负样本是从整个数据集,不是从同一文档中选取
MLM:从一个句子中随机抽取15%的token,参与MLM任务。
mask的单词是从一个句子中选取的
BERT模型网络结构
BERT 是基于 Transformer 而来的。BERT 使用 个 Transformer Encoder 堆叠而成。至于 Transfomer 的原理在 Transformer模型详解 有介绍。在论文中的 BERT 模型有两种版本,一种是 ,共有 12层 Encoder;另一种是 ,共有 24 层 Encoder。
BERT模型参数如下所示,其中 为 Multi-Head Attention 个数; 为词向量长度,同时 Feed Forward 的隐藏层数量设置为 。由于 Encoder 中有 Add & Norm 层,所以虽然模型层数比较多,但不至于导致梯度消失。有时候模型层数不是越多越好的,有人认为低层模型偏向于语法特征学习,高层模型倾向于语义特征学习。
参数计算过程代码
代码
模型实例
BERT 模型预训练任务
接下来我们看看 BERT 的预训练过程。BERT 的预训练阶段有两个任务,分别是
Masked LM
和 Next Sentence Prediction (NSP)
。Masked LM:
预测句子中被掩盖的词
Next Sentence Prediction (NSP):
判断输入的两个句子是不是上下句。
Task 1: Masked Language Modeling(MLM)
为了双向编码上下文以表示每个词元,论文作者采用了一种随机 Masked 输入序列中的 Token 并预测这个 Token 的方法(可以理解为做完形填空题),这过程被称之为 Masked Language Modeling(MLM)。
在原始训练文本中, 随机的抽取 的token作为参与 Masked LM 任务。而在这 Masked Token 中, 的 Token 用
[MASK]
替换, 的 Token 用任意词替换,剩下的 则不变。最后取这 的 Token 对应的输出做分类来预测其真实值。参与 Masked LM 任务的数据中
- 80%时间为特殊的“<mask>“词元(例如,“this movie is great”变为“this movie is
[MASK]
”;
- 10%时间为随机词元(例如,“this movie is great”变为“this movie is drink”);
- 10%时间内为不变的标签词元(例如,“this movie is great”变为“this movie is great”)。
加上 10% 的随机词和 10% 的真实值是让模型知道,每个词都有意义,除了要学习上下文信息,还需要提防每个词,因为每个词都不一定是对的,对于 Bert 来说,每个词都需要很好的理解和预测。
Q:为什么选中的15%的 token 不能全部用 [MASK]代替,而要用 10% 的 random token 和 10% 的原 token
[MASK] 是以一种显式的方式告诉模型『这个词我不告诉你,你自己从上下文里猜』,从而防止信息泄露。如果 [MASK] 以外的部分全部都用原token,模型会学到『如果当前词是 [MASK],就根据其他词的信息推断这个词;如果当前词是一个正常的单词,就直接抄输入』。这样一来,在 finetune 阶段,所有词都是正常单词,模型就照抄所有词,不提取单词间的依赖关系了。
以一定的概率填入 random token,就是让模型时刻堤防着,在任意 token 的位置都需要把当前 token 的信息和上下文推断出的信息相结合。这样一来,在 finetune 阶段的正常句子上,模型也会同时提取这两方面的信息,因为它不知道它所看到的『正常单词』到底有没有被动过手脚的。
代码
我们创建
mlm实例
并对其进行了初始化。BERTEncoder
的输出encoded_X
表示2个BERT输入序列。我们将pred_positions
定义为在encoded_X
的任一输入序列中预测的3个token
。mlm
的输出表示encoded_X
的所有掩蔽位置pred_positions
处的预测结果mlm_Y_hat
。对于每个预测,结果的大小等于词表的大小。通过掩码下的预测词元
mlm_Y
的真实标签mlm_Y_hat
,我们可以计算在BERT预训练中的遮蔽语言模型任务的交叉熵损失。Task 2: Next Sentence Prediction (NSP)
BERT 模型在下游任务中不免地会遇到
QA(Quention-Answer)
和 NLI(Natural Language Inference)
等任务。为此,BERT 增加了第二个预训练任务 Next Sentence Prediction (简称 NSP,也就是预测下一个句子)。其目的是为了理解两个文本句子之间的联系。在 NSP 任务中,BERT 每次训练都会从语料库中随机选取两个句子 A 和 B,其中句子 A 和句子 B 有 的概率是正确相邻的句子,另外 的机率是随机抽取匹配的。- 所有参与任务训练的语句都被选中作为句子A.
- 其中50%的B是原始文本中真实跟随A的下一句话. (标记为IsNext, 代表正样本)
- 其中50%的B是原始文本中随机抽取的一句话. (标记为NotNext, 代表负样本)
在NSP任务中, BERT模型可以在测试集上取得97%-98%的准确率。除此之外,作者特意地说,语料的选取很关键,要选用
document-level
的而不是 sentence-level
的,这样可以具备抽象连续长序列特征的能力。代码
下面的
NextSentencePred
类使用单隐藏层的多层感知机来预测两个文本句子之间的联系。因为BERT输出的特殊词元[cls]
已经对输入的两个句子进行了编码,所以MLP隐藏层的输入是编码后的[cls]
词元。我们可以看到,
NextSentencePred
实例的前向推断返回每个BERT输入序列的二分类预测。还可以计算两个二元分类的交叉熵损失。
值得注意的是,上述两个预训练任务中的所有标签都可以从预训练语料库中获得,而无需人工标注。原始的BERT已经在图书语料库 [Zhu et al., 2015]和英文维基百科的连接上进行了预训练。这两个文本语料库非常庞大:它们分别有8亿个单词和25亿个单词。
整合代码
在预训练BERT时,最终的损失函数是 MLM 损失函数 和 NSP损失函数 相加。现在我们可以通过实例化三个类
BERTEncoder
、MaskLM
和NextSentencePred
来定义BERTModel
类。前向推断返回编码后的BERT表示encoded_X
、掩蔽语言模型预测mlm_Y_hat
和下一句预测nsp_Y_hat
。BERT的下游任务
在预训练好的BERT模型后面根据特定任务加上相应的网络,可以完成NLP的下游任务,比如文本分类、机器翻译等。不同的 NLP 任务,BERT 模型输出方式也不同。比如:
- 单文本分类任务:BERT 模型在文本前插入一个
[CLS]
符号,并将该符号对应的输出向量作为整篇文本的语义表示,用于文本分类。可以理解为:与文本中已有的其它字/词相比,这个无明显语义信息的符号会更“公平”地融合文本中各个字/词的语义信息
- 序列标注任务:该任务的实际应用场景包括:中文分词 & 新词发现(标注每个字是词的首字、中间字或末字)等。对于该任务,BERT模型利用文本中每个字对应的输出向量对该字进行分类
- 问答任务:BERT的输出为每个token所对应的encoding vector。假设vector的维度为D,那么整个输出序列为,其中N为整个序列的长度。因为答案由文本中连续的token组成,所以预测答案的过程本质上是确定答案开头和结尾token所在的位置的过程。因此,经过全连接层之后,得到 。其中代表全连接层,为每一个token分别作为答案开头和结尾的logit值,再经过Softmax层之后就得到了相应的概率值。经过数据后处理之后,便可得到预测答案。
- 语句对分类任务(同单文本分类):BERT 模型在文本前插入一个
[CLS]
符号,并将该符号对应的输出向量作为整篇文本的语义表示,用于文本分类。