文章目录
BERT学习笔记
论文题目:BERT:Pre-training of Deep Bidirectional Transformers for Language Understanding
从论文的题目可以看出他又一下几个概念:Pre-training
、Deep Bidirectional Transformers
、Language Understanding
-
Pre-training:预训练,可以看出使用的是迁移学医。
-
Deep Bidirectional Transformer:深度双向transformer,深度指的是使用多层transformer(目的是为了:像多层卷积神经网络那样,每个层学习到不同的东西,层数越深对数据的理解程度越深),双向性指的是一个词的相关性计算是与这个词的左面的所有词与右面的所有词有关的。
-
Language Understanding:语言理解,BERT模型可以用于理解一段语言。也就是可以表征这个文本。
语言表征模型
作者开篇点题说BERT是一个语言表征模型 language representation model
语言表征:语言信息在大脑(计算机)中的存在方式。例如:一句话(中的词)可以经过语言表征模型encoder成一个向量,这个向量就代表这句话。
语言加工:大脑(计算机)对输入的语言进行编码、转换、存储、提取的过程。
也就是说通过BERT可以将一段sequence
(由token
组成的)经过语言加工,得到可以表示这句话的一个表示。
这是我以前想过的一个问题
每个词都写出来也可以表示一句话,那为什么还要encoder成另一种形式呢?
因为每个词都是独立的token,我们理解一句话也是知道了每个token然后经过大脑对其处理成一种可以理解的抽象印象\
这个印象才是我们理解的事物。\
所以需要将所有词进行encoder,得到的张量就是这句话的抽象印象。才能代表这句话!
作者认为BERT是一个普适性通用预训练语言表征模型。
为什么说它具有普适性呢?
- 因为BERT使用深度双向transformer,每一层都会从token的左面与右面预测这个token的表征,这样学习的到的特征就会非常多。
- 并且BERT使用的数据集非常大(使用集群TPU都要训练四个小时),所以可以适应市面上的大多数数据。
这两点使得BERT作为预训练模型,插入到下游任务(downstream task)中只需要简单的fine-tuning就可以适用那个指定的task(甚至不需要fine-tuning)。
Transformer也是一个语言表征模型(BERT就是Transformer的威力加强版)
迁移学习
由于现在模型越来越复杂,模型的参数越来越多。训练时间会非常长。如果我们有一个模型可以作为我们模型的上游模型,这个模型是已经训练好的,可以直接拿来用就好了。
那个已经训练好的模型就叫Pre-training Model,使用预训练模型的是Downstream Task Model。
迁移学习遇到的问题:
- 预训练模型的数据集不一定匹配下游任务的数据集,这样就会造成模型之间适配度不够(例如你训练一只小狗吃饭,那再想训练它在指定地方大便,这明显是不可能从之前的学习上进步的。但是如果你先训练它在指定地方小便,那么你不需要很多的时间就可以再让它学会在指定地方大便)
- 预训练模型所做的任务不一定匹配下游任务。也会造成模型之间适配度不够
那么我们想要一个通用型模型怎么办?那就用最广的数据集,做所有的任务进行训练。BERT就做了这些,所以它叫普适性语言表征模型。
迁移学习预训练模型与下游模型之间怎么进行交互的呢?
实现迁移学习的两种策略:
- feature-based:基于特征
- fine-tuning:精调
Feature-Based Approach
在 Featue-Based 这种策略中,预训练模型与下游任务模型是完全分离的,预训练模型之将输出的特征扔给下游模型作为输入。
例如:假设在BERT中使用 Featue-Based 的方法,如果下游任务需要词向量,BERT就只会将输出的 Feature(词向量)传到下游任务模型中。
但是 Feature-Based 这种方法缺点也很明显。过于硬直,使用这种方法并不能很好的适应下游任务。
Fine-Tuning Approach
Fine-Tuning将预训练模型放入下游任务模型中,跟着一起训练。但是 learn rate 必须小,对预训练模型进行精而小的调整。
Fine-Tuning分为两种:
-
冻结下半部分参数:将预训练模型的下半部分层冻结。
# 例如在pytorch中: model = Model() i = 0 for child in model.children(): for param in child.parameters(): param.requires_grad = False i += 1 # 然后再在优化器中过滤一下 filter = optimizer.SGD(filter(lambda p: p.requires_grad, model.parameters()), lr = 1e3)
-
完全不冻结参数:直接将整个预训练模型放到
__init__
和forward()
中。参与下游任务的训练。
将所有层的参数都冻结了就和 Feature-Bases 一样了!
精调对于迁移学习来说可以缓解迁移过程中的不匹配。
BERT
BERT的使用由两部分构成:Pre-training,Fine-tuning
BERT的使用由两部分构成,但是BERT的训练只是Pre-training。也就是BERT的本体就是左边那个,Fine-Tuning只是为了让BERT更好的适应下游任务。
从左边的图也可以看出BERT的特色:
-
MaskLM:
- 使用
[mask]
随机覆盖句子中15%的词。这些词中80%用[mask]
覆盖,10%使用原词表示,10%随机从词库中取一个词替代。 - 经过模型后预测这些掩藏后的token是否正确(损失方法)。
- 使用
-
word embedding:
- 输入BERT中的词由三部分组成:
token emdedding
+segment embedding
+position emdedding
- 输入BERT中的词由三部分组成:
-
NSP:
- next sentence predict。预测下一个词是否正确(损失方法)。
-
双向Transformer:
- 使用Self Attention得到token两边的信息(因为Attention可以无视距离获取token间的相关性,但是这样只获得到了两边的信息,但是信息的前后组织关系并没有获得)
- 因此加上
position embedding
就可以获取到位置信息了。(这样就获得序列性了)
Pre-training
Pre-training阶段使用的是无监督训练。(可以加一个伪字)
因为除了在NSP、MaskLM阶段使用了标签进行损失计算,但是这里的标签也是程序可以生成的(下一句是与不是可以用if进行判断,单词是不是可以到词表中找id),其他地方并没有使用到标签。
而这不需要人工标注,所以还是无监督训练。
Mask LM(创新)
Mask Language Model
是由最早的Cloze task
延申而来的。类似于完形填空(根据上下文知道这个词应该填什么)
由于传统的语言模型只能是left to right
或者right to left
的特性(循环神经网络就是这种特性),这样会导致双向训练会发生see itself
的情况。这样并不好!
但是使用Mask LM
就会缓解这种情况,因为mask掉的词本就不知道是啥,看见自己又能怎样。
MaskLM的操作过程:(例如一句话:my dog is hairy)
-
80%使用
[mask]
替代随机掩盖的15%的词中的词。e.g.,my dog is hairy
→my dog is [mask]
-
10%使用原词不变在随机掩盖的15%的词中的词中。e.g.,
my dog is hairy
→my dog is hairy
-
10%使用词库中的随机词替代随机掩盖的15%的词中的词。e.g.,
my dog is hairy
→my dog is apple
这样的操作是为了,在transformer进行encoder时,并不知道这个词是什么,让被掩藏的词被迫通过前后的词来预测。
作者说这种操作似乎不会损害模型的语言理解能力!
虽然只随机掩藏15%的词(说明一个句子每次只预测15%的词),这样效率并不高,但是与这样模型具有的超高经验来说这些都是值得的。
为什么不能全部使用Mask?
因为BERT的作用还是作为预训练模型迁移到其他任务中使用。少量的使用原词或随机词,可以增加文本匹配度,使得词的预测更符合语境。
毕竟完形填空没有选项的话可以有很多个词替代的。
MaskLM训练过程:
将掩藏后的tokens输入到网络中,经过网络计算会得到所有词的表示。在训练中我们只关注掩藏后的词的预测是否正确。
所以将经过计算的[Mask]
输入到一个分类器中(这里的图只是BERT模型的图,训练BERT过程中上面还会加一个分类器作为判断计算),判断掩词是否是原文中的词。使用交叉熵损失进行损失计算。
句长问题:
从上面可以看出BERT会限制输入的数量的(因为Transformer不是序列性的,所以不存在时间步的分步输入)。例如:BERT限制最大序列的长度为512个token。如果输入序列超过512个token就切掉多余的token;如果输入序列少于512个token使用[PAD]
进行填充。
所以Mask LM的作用:
- 缓解
see itself
效应 - 迫使词两侧的词对掩藏词的经验性正确预测
Word Embedding
在BERT中一个句子被额外添加两个词
[CLS]
:这个词放在句子头。用于NSP中的分类任务,使用这个词代表一句话(因为使用transformer)。[SEP]
:这个词放在句子尾。用于分割对句(BERT在做问答等任务的时候输入的就是对句)。
这两个位置跟着模型一起训练(就是模型中由这两个词位的权重W)。
将输入的词分为三部分进行构造:
Token Embedding
:先对词进行WordPiece
(例如这里的playing
分为了play
/##ing
,将词跟与时态分开)。然后对WordPiece
后的词进行初始化(这里的初始化可以使用ont hot
也可以使用word2vec
生成的词表)。最后就得到了Token Embedding
。SegmentEmbedding
:区分词所在的句。BERT会给第一个句子中的每个token添加一个可训练的 A嵌入(EA),给第二个句子中的每个token添加一个可训练的 B嵌入(EB)。例如:EA都是0,EB都是1。Position Embedding
:为了得到每个词的位置信息(因为transformer没有序列性,所以只有添加上位置信息才能有双向性)。按照以下公式对每个词的位置进行嵌入。
但现在的BERT代码中似乎换成和随机生成词向量一样,通过训练得到位置嵌入,这也是一般获得位置嵌入的方法。
双向Transformer
在BERT中的Transformer是《Attention is all you need》论文中的本体。可以在tensor2tensor
库中找到这个模型。
Transformer是一个没有序列性的、可以使词间无视距离的一种表征模型。
- 无视距离是它的一个优点,因为循环神经网络因为距离过长多多少少都会遗忘一些信息。这并不好!
- 没有序列性是它的一个缺点,因为句子必须有序列性才能读通。但这点在
position embedding
与MaskLM
中克服了。 - Transformer中有两部分:encoder-decoder
- encoder:会无视距离编码成一个表征。
- decoder:decoder是单向性的,将表征解码为序列。
BERT中使用的是encoder部分所以可以实现双向性。
OpenAIGPT中使用的是decoder部分所以它还是单向性的。
NSP(创新)
Next Sentence Predict
大部分任务都是基于句子理解的任务。例如:问答、自然语言推测等。
但是因为使用BERT作为预训练模型,下游任务不需要是特别多的参数特别复杂的模型。简而言之,下游任务模特别简单,并不能直接获得句子的表示。所以我们将句子的表示放在了预训练过程中!
在预训练过程中使用预测下一句子是否正确,就可以获得句子的表示。
训练过程:
将句子输入模型中(训练的时候是对句,使用的时候可以把一个句子拆成对句输入)。最终可以得到头词[CLS]
的表示,因为BERT中间使用了深度双向Transformer,所以计算过后的[CLS]
的向量(就是图里的C)可以表达整个句子。
NSP任务是一个二值化任务。就是判断B句是否是A句的下一句。(是或不是,二分类器实现)
Sentence A
是上句,Sentence B
是下句。Sentence B 50% 的几率是 A 的下一句(标签为IsNext),50%的几率是随机选的一个句子(标签为NotNext)。
将得到的[CLS]
代表整个对句输入到二分类器中。使用交叉熵计算损失。然后再进行梯度下降。
这个训练与MaskLM不是同时进行的,可以先训练MaskLM再训练NSP(或者换一下)
Fine-tuning
将预训练好的模型直接放到下游任务中用就好了。
例如这里下游任务是问答,输入是:问题、答案所在的句子。
经过BERT计算后将 Paragraph 中的输出的词向量输入到问答的下游任务中(图中没有画出来),任务的目的是找到 Paragraph 中问题所在的答案的开始、结束位置。
比较BERT、ELMo、Word2Vec
从前面可以看出BERT可以用于产生词向量。
ELMo与BERT产生词向量的方式与word2vec不同。ELMo、BERT是模型式产生词向量的,同一个词在不同的上下文中可以有不同的词向量(也就是词义不同)。但是word2vec却不同,它是根据DNN训练出每个词的,每个词只有一个词向量。
Word2Vec
word2vec的模型只是简单的DNN模型,它并没有序列性(与上下文无关性)。将将词库中的词作为参数参加训练。
负采样:
在经过隐层计算后,将接上一个二分类器。二分类器的任务是:从词库中随机抽取k个词作为一个集合(负例),然后将下一个词(正例)也加入集合中。模型从中找出正确的词。
BERT也采用了负采样的思想!
预训练encoding(上下文相关) | 模型 | 预测目标 | 下游具体任务 | 负采样 | 级别 |
---|---|---|---|---|---|
No | CBOW/Skip-Gram | next word | 需要encoding | Yes | word-level |
可以看出,Word2Vec并没有将下游任务放在模型里训练,这样迁移性也不好。
ELMo
ELMo实现了上下文相关,使用了BiLSTM得到序列性。
但是它分开使用了两个BiLSTM进行训练,这样的双向性并不彻底。
并且LSTM层数多了就会发生see itself
的缺陷。
ELMo模型将context的encoding操作从下游具体NLP任务转换到了预训练词向量这里,但在具体应用时要做出一些调整。当bilstm有多层时,由于每层会学到不同的特征,而这些特征在具体应用中侧重点不同,每层的关注度也不同。ELMo给原始词向量层和每个RNN隐层都设置了一个可训练参数,通过softmax层归一化后乘到相应的层上并求和起到了加权作用。
比如,原本论文中设定了两个隐层,第一隐层可以学到对词性、句法等信息,对此有明显需求的任务可以对第一隐层参数学到比较大的值;第二隐层更适合对词义消歧有需求的任务,从而分配更高权重。
所以ELMo的缺点:
- 双向性不彻底
see itself
预训练encoding(上下文相关) | 模型 | 预测目标 | 下游具体任务 | 负采样 | 级别 |
---|---|---|---|---|---|
Yes | BiLSTM | next word | 需要设置每层的参数 | No | word-level |
BERT
BERT因为有掩码的操作,所以并不会发生see itself
(你连自己都不知道是谁怎么能知道自己长什么样子呢?)
BERT使用Transformer + Position Embedding,所以可以实现真正的双线性。(Transformer无视距离双向操作,Position Embedding提供位置信息)
BERT采用句子级负采样。类似于Word2Vec
预训练encoding(上下文相关) | 模型 | 预测目标 | 下游具体任务 | 负采样 | 级别 |
---|---|---|---|---|---|
Yes | Transformer | masked word/next sentence | 简单MLP | Yes | sentence-level |