BERT模型出自该论文:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
在论文中,作者对BERT的名称做出这样的解释——Bidirectional Encoder Representations from Transformers. 那么顾名思义,BERT可能是由来自Transformer模型中的双向Encoder编码器并组成。事实上,确实是这样的。
BERT采取的运行模式
与Transformer直接用数据进行训练不同的是,BERT模型采用了预训练(Pre-training)和微调(Fine-tuning)的方式。
论文中简单介绍了三种一般预训练语言表示:
- Unsupervised Feature-based Approaches
(基于无监督特征的方法) - Unsupervised Fine-tuning Approaches
(无监督微调方法) - Transfer Learning from Supervised Data
(从监督数据迁移学习)
监督和无监督是机器学习中的一个概念,如果数据集中是带有Label标签的那么则称为监督;如果不带任何标签则是无监督。
BERT采取的是第二种无监督微调方法。
具体方法是将整体的模型先在大量无标签的文本数据中进行训练,得到预训练模型。
之后针对下游任务(downstream task),此时由于每个任务都是不同的(比如:单句分类、问答任务…),再进行有监督的数据训练,从而进行参数的微调。
这样做的优点是,很少有参数需要从头训练。节省时间的同时也提高了准确率。
原文:
The advantage of these approaches is that few parameters need to be learned from scratch. At least partly due to this advantage, OpenAI GPT achieved previously state-of-the-art results on many sentencelevel tasks from the GLUE benchmark.
BERT模型
BERT模型主要分为三个部分:(这里根据的是BERT的代码实现,在论文中没有提及)
- Embedding
- Encoder
- Pooler(应该是可选的,在论文中没找到…)
其中,Encoder部分正如作者说的一样,同Attention is all you need这篇文章中的Encoder部分完全相同,不过采用了很多个Encoder进行堆叠。
Embedding
从模型图中看出,词嵌入的数据不那么简单,是三个embedding的相加:
- WordPiece Embeddings
- Segment Embeddings
- Position Embeddings
WordPiece
由于BERT的设计是用来处理各种下游任务的,在输入上(token sequence)需要能够明确表示每个句子(sentence)。不过,这里的sentence不同于自然语言中的句子,不单单是一句话。由于支持了上下文长序列,这里的sentence可以是多条句子的组合,也可以是文章中的一段或几段。
但是!前面不是提过需要预训练大量的数据吗?也就是意味着token sequence中有着海量的词,由于下文相关的其他embedding是进行学习的,如果这样,那就会导致整个模型要学习的参数大部分集中在数据预处理部分,这显然是不利的。因而需要提出全新的分词方法。
WordPiece将单词划分成一组有限的公共集合(词汇表),其中最小的token并不是单个英文词本身,而是更细粒度的切分。这个词汇表的限制大小为30000,所以只是尽可能添加最常用的单词和子单词。但是这个词汇表这样就可以表示出所有的词了吗?它是如何做到的?
词汇表中包含的元素有以下几种:
- 单词本身
整个单词足够常见,会直接出现在词典中。 - 被切分的子单词(单词开头)和独立单词
切分:按照一定规则对单个单词进行切分,并加以开头与否的区分。比如:tradition被划分为 tra、##di、##tion三个token。(##表示非完整单词,而是某个单词的非开头部分) - 被切分的子单词(以##开头)
- 单个字符
那这种方法为什么可以表示出每个单词呢?
具体的切分规则如下:
- 分词器对整个单词进行检测,是否在词汇表中。
- 如果没有,尝试将单词分解为词汇表中包含的尽可能多的子单词。
- 最后将单词分解为单个字符。
这样下来,几乎可以表示每一个单词。
值得一提的是,词汇表中没有NLP中常用的标记符<UNK>等,对于词汇表中没有的词,将其分解为子单词,利用子单词生成嵌入向量,就可以获得原始单词的近似向量。
当然,论文提到了几个新的token。
<CLS>:作为每个sentense的开始,通常标识着分类任务,如果不是分类任务可以去掉。
<SEP>:作为sentense内部的分隔符。
Segment
这里就是分割的意思,表明token是来自哪一个句子的。
Position
token在句子中的位置。
不同于Transformer中的正弦函数表示,而这里的Position是一个固定值。
另外,Transformer中这个编码是通过手动构造矩阵获得的,而BERT中是通过学习获得的。
Pre-training
BERT的名称中第一个字母的含义就是Bidirectional,那么肯定是在双向上做了一些改动的。
既然BERT不采用传统从左到右或者从右到左的模型来训练,是不是意味着每一个数据可以看到它前方和后方的数据,这样不就可以轻松通过直接间接的方式来预测目标单词了吗?那在这样透明的环境中用什么方法去训练呢?
论文中提及的方式是完形填空(Cloze task)类的方法。
训练方式
- Masked LM(MLM)
- Next Sentence Prediction (NSP)
MLM
将输入单词(input token)按照一定比例,随机进行隐藏(masked),将选中的单词替换成特殊的token [MASK]。随后处理后的序列送入模型,将最后得到的隐藏层向量(其实就是输出结果)经过softmax后与词汇表进行对比。
这样其实就进行了一个完形填空方式的预测单词任务。
论文中使用的比例是15%。
当然,仅仅这么做是不够的。
如果这么做,预训练部分和微调部分其实是不匹配的。因为[MASK]在微调的过程中是不出现的。为了解决这个问题,论文作者用了以下的办法:
- 还是按比例选择token,比例为15%。
- 如果选中了token:
- 80%概率 用[MASK]进行替代。
- 10%概率 在词汇表中选择一个随机的token。
- 10%概率 保留原token。
可以看到论文中对此做出的例子解释:
个人认为,随机替换的目的是为了抑制过拟合。并且作者在论文中提到,这样的随机替换只占到了所有内容的1.5%,并不会对模型的语言理解能力造成较大的影响。
NSP
当BERT运用在QA问题和语义推断上的问题时,就需要理解两个句子之间的关系。
而已有的MLM并不能获取句子与句子之间的关系,所以采用NSP进行预训练。
具体做法如下:
对于每一个训练样例,在语料库中挑出句子A和B来组成。
- 50%的时候,挑选连贯的句子即B就是A的下一句(标注为IsNext)
- 50%的时候,句子B为语料库中的随机句子(标注为NotNext)
由于[CLS]是每个句子的开头,所以[CLS]中学习了句子与句子之间的关系,在Figure1中是C。这样就解决了句子层次间的问题。
Fine-tuning
微调任务是直接的,因为Transformer中的自注意机制允许BERT对许多下游任务进行建模,无论这些任务设计单个文本还是文本对,只需要通过更改适当的输入和输出。
这是论文中给出的在不同下游任务的说明。