目录 7.2、Next Sentence Prediction 八、BERT的Fine-tuning (下游任务改造 ) 九、GPT、BERT和Transformer的对比 https://wangguisen.blog.csdn.net/article/details/127065903 本文讲解思路阐述 本文 “保姆级讲解BERT” 的思路如下:
首先会先回顾:什么是预训练模型、语言模型的两种预训练方式、单向编码和双向编码 ; 然后会说一下:BERT原文中作者的摘要和导言 ,因为我觉得作者的这种写的思路非常值得学习; 然后会说一下:BERT公认的里程碑式的意义 、介绍一下BERT能干什么 ,让读者有一个大致的了解; 再然后就是比较重点的:BERT的预训练 和 Fine-tuning ,并贴出前向传播 流程图方便大家理解; 接着会:把BERT和其它语言模型做一下对比 ,加深理解; 我还会放出torch复现的代码来,大家可以看下MLM数据怎么处理的,以及多任务的损失部分是怎么来算的,当然实际主要还是用Huggingface的; 最后简单说一下BERT的一些变种模型 ,作为本文的结尾,扩充一下知识面。 预训练模型回顾 定义:
首先使用大量无监督语料进行语言模型的预训练(Pre-training),再使用有标注的语料进行微调(Fine-tuning),来完成具体的NLP任务,比如分类、序列标注、句间关系判断、机器阅读理解等。 优势:
语言模型两种预训练方式回顾 在NLP中,利用语言模型去做预训练,能显著提升许多自然语言处理任务的效果,自然语言处理任务主要包括两种:
sentence-level tasks(句子级别) :指学习多个句子之间的关系,例如 natural language inference(自然语言推理)。token-level tasks(词级别) :指模型要生成 细粒度(fine-grained)的输出,fine-grained指的是类内细分,与之相对于的是 粗粒度(coarse-grained)。例如在分类任务中,coarse-grained只需要分辨是猫还是狗,而fine-grained则 需要在每个类别中分辨出具体的品种,比如是拉布拉多还是罗威纳。 语言模型(Language Model)来辅助NLP任务已经得到了学术界较为广泛的探讨,通常有两种方式:
Feature-based 基于特征的(Feature-based)预训练方式,典型的代表就是ELMo。
Feature-based 指的是利用预训练好的语言模型的结果,也就是得到预训练好的词向量,将其作为额外的特征融入到下游任务中,参与下游任务的训练 。
通常 Feature-based 的预训练方法包括两步:
首先是在大量的语料A上无监督的训练好语言模型,得到预训练好的语言模型; 然后搭建下游任务所使用的模型(task specific model),采用有标记的训练语料B来训练 task specific model,将预训练好的语言模型的参数固定 ,训练语料B先经过语言模型得到预训练好的词向量;得到词向量后可以直接作为 task specific model 的输入参与训练;也可以和原始特征Embedding进行拼接,作为额外特征参与后面的训练。 需要强调的是:基于特征的预训练方法,我们只需要拿到预训练模型的词向量即可,预训练模型的参数不参与 下游任务的梯度更新。
Fine-tuning 基于微调的(Fine-tuning)预训练方式,典型的代表就是GPT。
Fine-tuning 指的是利用预训练好的语言模型,在此基础上加入少量的 task-specific parameters ,例如对于分类问题,就可以在语言模型的基础上加一层FC或直接Softmax进行分类 ,然后在新的训练语料上进行微调。
通常 Fine-tuning 的预训练方法包括两步:
构建语言模型,采用大量的语料A无监督的训练语言模型; 在预训练好的语言模型的基础上,用下游任务的训练语料B进行有监督的训练;在语言模型之后可以直接加一层FC或者直接加Softmax来进行分类;或者常用的做法就是,在语言模型之后再接其它的模型,比如接一个TextCNN、DPCNN等,可以理解为它们学习的就是预训练模型提取的高级语义 。 需要强调的是,利用预训练语言模型在下游任务微调,模型的参数 不是固定的 ,它仍然是 trainable variables。
BERT采用的就是基于微调 的预训练方式。
单向编码和双向编码回顾 我们来回顾下单向编码和双向编码的差异,比如这个例子:“今天天气很{},我们不得不取消户外运动 ”。
我们分别从单向编码 和双向编码 的角度去考虑 {} 中应该填什么词:
单向编码只会考虑 “今天天气很 ”,以人类的经验,大概率会从 “好”、“不错”、“差”、“糟糕” 等,这几个词中选择,很明显这些词的语义可以被划分为截然不同的两类。 这样理解:因为没有下文 ,所以这里就随便填,可以填“好”、“不错” ,也可以填“差”、“糟糕” 。 双向编码会同时考虑上下文的信息 ,即除了会考虑 “今天天气很” 这五个字,还会考虑 “我们不得不取消户外运动” ,来帮助模型判断,则大概率会从 “差”、“糟糕” 这一类词中选择。 一、BERT原文的摘要和导言 接下来就正式进入BERT的讲解了。
我们先看它的标题:
Pre-training(预训练)的意思也重复好多遍了:在一个数据集上训练好一个模型,然后这个模型的主要目的是用在别的任务上; Deep Bidirectional Transformers:预训练的深的双向的 Transformer; for Language Understanding:针对的是一般的语言的理解任务。 我们来看下摘要的第一段话:
第一个高亮说了BERT名字的由来,意思是Transformer这个模型双向的编码器表示,后面说是跟最近的语言模型表示不一样。第一个引用的是ELMo的文章,第二个引用的是GPT的文章。 第二个高亮说的是,BERT是用来去设计深的双向的 表示,使用没有标记的数据 ,再联合左右的上下文信息 。 第三个高亮说的是,BERT只需要加一个额外的输出层就可以得到一个不错的结果 ,在很多的NLP任务上面。 这两句话其实就讲明白了其和ELMo、GPT的区别:
GPT 因为掩码注意力的限制考虑的是单向的信息,即用左边的信息去预测未来;BERT这里不一样是因为它用了左侧和右侧,所以它是个双向的。ELMo 是一个基于RNN的架构,而BERT用的是Transformer,所以ELMo在用到一些下游的任务的时候,它需要对架构做出调整,BERT就比较简单只需要改最上层就行了,和GPT是一样的。我感觉这样写就很好,在摘要的第一段话里说和那两个工作相关,然后再说和这两个工作的区别是什么。
我们再来看一段导言:
导言的第一段一般是交代了这篇论文关注的一个上下文关系。
说这些NLP任务包括两类,第一类是用于建模句子之间的关系,第二类是词元(token)的任务。比如对每个词去进行实体识别(NER)、比如它是不是一个人名、街道名等。(本文最上面有给出回顾)
导言的第二段和最后,是摘要的第一段的扩充版本。
它说在使用预训练模型做特征表示的时候一般有两类策略:基于特征的、基于微调的 。(本文一开始回顾的)
第一类是基于特征 的,代表是ELMo,对每一个下游的任务,先构造一个跟这个任务相关的网络,然后基于大量语料进行预训练,得到预训练好的词向量,预训练好的词向量表示可以作为一个额外的特征,和输入一起喂到模型里面,因为特征有了一个比较好的表示,所以导致模型训练起来比较容易。这也是NLP里面使用预训练一个常用的做法,就是把学到的特征和输入一起放进去,作为一个很好的特征的表达。
第二类是基于微调 的,代表是GPT,把预训练好的模型放在下游的任务的时候不需要改太多,模型预训练好的参数会在下游数据的任务上进行微调,即所有的权重会根据新的数据进行微调。
读到这是不是感觉我的上一篇、包括本篇的前面的回顾都不是废话了吧!
二、BERT里程碑式的意义 如果把过去几年NLP领域里的论文做一个排序的话,BERT当之无愧的是状元 的位置。在计算机视觉任务里,很早的时候就可以在一个很大的数据集,比如ImageNet训练好一个卷积网络,这个模型可以帮助一大片的cv任务来提升它们的性能。但是在NLP里,在BERT之前,一直没有一个深 的神经网络,使得训练好之后能够帮助一大片的NLP任务。
也就是说在BERT之前,还是对于每个任务构造自己的神经网络,在自己的任务上做训练。BERT的出现使得我们可以在一个比较大的数据集上预训练好一个比较深的神经网络,然后应用在很多NLP任务上面。既简化了NLP任务的训练,又提升了它们的性能。所以BERT和它之后的一系列工作使得NLP在过去几年里有了一个质的飞跃。
作者的原话是:从大量无标记数据集中预训练得到的深度模型,通过下游任务微调 的方式,可以显著提高各项NLP任务的准确率。
但是如果说创新的话,它其实并没有什么本质的创新,很大的创新应该是Attention 和 Transformer,BERT其实是一个集大成者 :
借鉴了 GPT 用 Transformer 作为特征提取器的思路 。 采用了 Word2Vec 所使用的 CBOW 方法。 还可以这样说:预训练的方法其实很早就有了,只不过是BERT让它出圈了。
三、BERT能干什么 上面我们提到过,作为一个预训练的语言模型,BERT能够以微调的方式 帮助很多NLP的下游任务。所以BERT的输出也肯定不单单是一种,如上图所示,bert具有两种输出:
一个是 pooler output ,对应的 [CLS] 的输出; 一个是 sequence output ,对应的是序列中的所有字的最后一层 hidden 的输出 ; 先提取强调一下,在预训练中的MLM任务里,损失计算用的是被 mask 的输出,大家往后看就明白了。 所以BERT主要可以处理两种任务:
一种任务是分类/回归任务(使用的是pooler output);
单句分类任务(Single Sentence Classification tasks); 句子对分类任务(Sentence Pair Classification tasks); 回归任务其实是分类任务的一种特殊形式,最后的输出是一个数值而不是具体的某个类别的概率。例如:文本相似度,可以判断两个句子是不是类似的,得到具体的分数; 一种是序列任务(sequence output);
Cloze task(完形填空),这其实这就是bert预训练的一种任务; 我们以单句分类任务(Single Sentence Classification tasks)为例简单看下,示例图如下:
四、BERT的整体架构 了解Transformer、ELMo、GPT的同学其实一看图bert也就明白了。
BERT的模型结构就是 Transformer Encoder 的堆叠 ,因为Transformer拥有比较强的特征提取能力 ,所以通过堆叠block能够提取到多层 的信息。
因为使用 Transformer Encoder 作为block的原因,BERT是双向 的语言模型,也就是说每个时刻的Attention计算都能够得到全部时刻的输入 ,而GPT由于Decoder的掩码注意力的限制,每个时刻的Attention计算只能依赖于该时刻前的输入,所以这里也是为什么说GPT是单向语言模型的原因。
注意这里不要混淆,BERT是对 Transformer Encoder 的堆叠,不是堆叠的Transformer。
上面重点强调了BERT就是 Transformer Encoder 的堆叠,那么我们可以将BERT结构简单的归纳为三个部分:
输入层,输入层和Transformer不同,图中也可以看出,它是Token Embedding、Segment Embedding和Position Embedding 相加 得到的 ; 中间层,中间层就是 Encoder block 的堆叠; 输出层,为了更好的融入不同的下游任务,输出又可以分为两块,一是 [CLS] 的输出,二是 Sequence 的输出。 五、文中给出的两种结构 在模型参数选择上,论文中给出了两套大小不一致的模型:
数据量大,模型也大,就不容易过拟合,能够学到更到的东西。
BERT_base:L=12,H=768,A=12,总参数量为1.1亿
BERT_large:L=24,H=1024,A=16,总参数量为3.4亿
L:代表 Transformer block 的层数; H:代表特征向量的维数,此处默认 Feed Forward 层中的中间隐层维数为4H; 使用这三个参数基本可以定义BERT的量级,训练过程也是很花费计算资源和时间的,总之表示膜拜,普通人即便有idea没有算力也只能跪着。印象中听过一个视频,里面的大神说训练下来是几百万美金...,所以我们训练不了,只能拿人家训练好的来进行微调。
之前讲的ELMo,它用的双向双层LSTM得到了单词特征、句法特征、语义特征,这些特征它可能是人为定义出来的,但人和机器相比总是狭隘的,正因为机器的强大,它能够发现更多的特征,是人类无法用语言表达的高级语义特征 。
对于BERT_large,用了24层block,也就是编码了24层不同的特征信息,越高层的信息越无法用语言表达,但是机器认为它是可取的,对目标有帮助,随着训练数据的越来越大,它能够学到更多的语义逻辑关系 。
其实这也是和神经网络被称为黑盒一个道理,通过BERT堆叠多层Encoder block,最终能够得到一个很好的特征向量,去准确的表达一个词、一个句子。
六、BERT的输入 BERT的本质其实就是通过在海量的语料进行自监督学习,为每个 token 学习得到一个比较好的向量表征 ,我们可以直接使用 BERT 学习到的表征作为下游任务的嵌入特征。
所谓的自监督学习是指在没有人工标注的数据集上运行的监督学习。
所以为了使 BERT 能够适应不同的下游任务,BERT 的输入可以是单句 或者句对 ,即:
[CLS] + 句子A + [SEP] + 句子B + [SEP] 对于每一个输入 token,它的 Embedding 表征由:对应的词表征(Token Embedding )、段表征(Segment Embedding )、位置表征(Position Embedding )相加 产生。
我们来看一个比较形象的图:
注意啊,找的这个图没有画位置编码,我懒得画了,大家自行想象一下即可。
6.1、Input 首先来看 input,对于 input,我们需要重点关注两部分:
第一个是正常的词汇,如 'my'、 'dog'、 'is'、 'cute '、'he'、'likes'、'play'; 第二个是特殊词汇 ,如 '[CLS]'、'[SEP]'; [CLS] [CLS] 表示输入句子的起始token (特殊token),对应最终的 hidden state,也就是 Encoder block 的输出,训练结束后可以表征整个句子 ,[CLS]其实是Classification的意思,[CLS] 输出的特征向量(pooler output)可以用于下游 的分类任务。
我们前面提到过,预训练好的 BERT 可以用于多种nlp的下游任务,对于分类任务,就用 [CLS] 的输出特征向量 。
如下图所示,与文本中已有的其它字词相比,这个无明显语义信息的符号感觉会更“公平”的融合文本中各个词的语义信息。
这时,句子B就为空,输入就变成了:[CLS] + 句子A
注意 [CLS] 输入的是 [CLS] 本身这个词的 Embedding。
[SEP] [SEP] 就是 分隔符 的意思,这个不难理解,如果输入为两个句子的话,需要一个特殊符号 [SEP] 将两个句子分开,就是告诉模型在 [SEP] 之前的是一个句子,之后的是一个句子。
6.2、Token Embedding Token Embedding 就是正常的 Embedding,比如随机初始化,或加载其它预训练好的词向量。
对于英文任务,作者使用了Wordpiece模型来产生Subword,从而减小词表规模。
其中,Wordpiece是指将单词划分成一组有限的公共子词单元,能在单词的有效性和字符的灵活性之间获得一个折中的平衡。
例如图中的 “playing” 就被拆分成了 “play” 和 “ing”。
对于中文任务,作者直接训练基于字的模型,上面也提到了,模型输入需要附加一个起始token,记为 [CLS],训练结束后用它表征整个句子,参与下游的分类任务。
6.3、Segment Embedding BERT为了更好的融入下游任务,所以输入可能会有句子对,Segment Embedding 是句子分段的嵌入张量,它的作用就是为了区分两个句子 。区分的方式也很简单:第一个句子全部用0表示,后面的句子全部用1来表示(包括 [CLS] 和 [SEP]在内) ,即图中的 。
例如我们现在有一个输入句子对:
那么其对应的 Segment Embedding 为:
BERT的预训练里有个叫 NSP 的任务,即要做以两个句子为输入的分类任务。
那么对于单句输入,其 Segment Embedding 只有一种;对于句对输入,其 Segment Embedding 就会有两种。
6.4、Position Embedding BERT里的位置编码和 Transformer 是不同的,Transformer 的位置编码是通过正余弦函数 实现的,而BERT是随机初始化 的,让模型自己去学。在BERT中,它假设句子最大长度为512。
七、BERT的预训练 上图为原文中给出的预训练和微调示意图,和GPT一样,BERT也采用两阶段式训练 方法:
第一阶段 :使用易获取的大规模无标签语料来训练语言模型(LM),为了能够同时利于 token-level task 和 sentence-level task ,作者采用如下两种 预训练任务来建立语言模型(多任务训练目标),从而更好的支持下游任务:掩码语言模型 :Masked Language Model(MLM) ,目的是提高模型的语义理解能力 ;下句预测 :Next Sentence Prediction(NSP) ,目的是训练句子之间的理解能力 ;第二阶段 :利用下游任务的有标签训练语料,进行微调 训练。不同于GPT等标准语言模型使用 为目标函数进行训练,能看到全局信息的BERT使用 为目标函数进行训练。
7.1、Masked LM 先来解释一下为什么需要 语言掩码模型(MLM) :
BERT作者认为,使用自左向右编码 和自右向左编码 的单向编码器拼接而成的双向编码器,在性能、参数规模和效率等方面,都不如直接使用深的双向编码器强大 ,所以BERT使用 Transformer Encoder 作为特征提取器。 如果使用预训练的语言模型处理其它任务,大家想要的肯定就不止某个词的左边的信息了(上文信息),而是左右两边的信息 。 因为BERT采用 Transformer Encoder 作为特征提取器,所以它就无法使用标准语言模型的训练模式了。换句话说,因为 Encoder block 是双向的,它是无法使用完形填空思想 的,或者说它无法采用预测下一个词的方式 了,因为模型会看到要预测的值 。 Encoder编码器,就是给它一个句子,它已经把中间词的信息提前拿到了,再让它去预测不是搞笑吗。
为了解决这个问题,BERT 借鉴了 CBOW 思想(完形填空任务),使用语言掩码模型(MLM )方法训练模型 ,具体做法为:
随机 mask 掉语料库中 15% 的词,在输入的时候再去 预测 被 mask 的词是什么。 注意只有训练阶段才会这样做,测试阶段不会mask。
被 mask 的词称为掩码词 ,这样实际上已经不是传统的神经网络语言模型了,而是单纯作为分类问题,根据这个时刻的 hidden state 来预测这个时刻的 token 应该是什么,而不是预测下一个时刻的词的概率分布了。
利用 mask 打破文本的原有语义信息,让模型不知道。在预训练的时候,让其对有mask的文本进行重建,绞尽脑汁的从周围的上下文中学习各种信息,来让自己预测的mask的词汇更接近原本的词汇。
我们还要再强调的是,BERT的本质是提取特征向量(高级语义),通过 mask 方法只是为了让BERT提取的特征向量更准确,主要目的不是预测被 mask 的token,主要目的是提取特征向量 。
为什么说它借鉴完形填空思想呢?对于某一个被 mask 的 token 的上下文而言,模型不知道token是什么,再去预测它,就类似于完形填空了。
但是这样设计 MLM 的训练方法会引入一个弊端:在模型微调阶段或推理阶段(测试阶段),输入的文本中没有 mask,这样会出现由训练和预测数据偏差导致的性能损失 。
作者考虑到这个弊端,并没有总用 mask 替换掩码词,而是按照一定比例选取替换词,在随机的15%的词作为掩码词后 ,这些掩码词有三类替换选项 :
80%训练样本中:将选择的词用【mask】来代替 ,例如:“地球是【mask】八大行星之一”
10%的训练样本中,选中的词不发生变化,该做法是为了缓解训练文本和预测文本的偏差带来的性能损失 ,例如:“地球是太阳系八大行星之一”
10%的训练样本中:将选中的词用任意的词来代替,该做法是为了让BERT学会根据上下文信息自动纠错 ,例如:“地球是月球八大行星之一”
按照存在弊端的做法,如果对这15%的词全部用 mask 来替换的话,那么模型的全部精力 就会聚焦在 mask 上,因为其它的词,比如 “地球”,它一定是正确的。
现在的做法,mask 是随机的,模型不知道那个token是 mask 的,甚至 “地球” 也可能是要被 mask 的,只不过它被错误的词代替了。
也就是说用了这个方法,所以的词都有可能是要被 mask 的词,这样模型就有了把精力放在 mask 和其它词的能力,也就是测试阶段也会去考虑每一个词 了。
mask的作用就是让模型对特征向量有更好的语义理解 ,所以mask不能去掉,但是要考虑训练阶段和测试阶段的间隙,即上述所说的弊端。
作者在论文中提到这样做的好处是,编码器不知道哪些词是需要训练的、哪些词是错误的,因此被迫需要学习每一个token的表示向量 。
另外作者也表示双向编码器比单向编码器训练要慢,进而导致BERT的训练效率低了很多,但是实验也证明MLM训练方法可以让 BERT 获得超出同期所有预训练语言模型的语义理解能力 ,牺牲训练效率是值得的。
15%的mask token中,三种方案代码示例如下:
for index in mask_indices: # 80% of the time, replace with [MASK] if random.random() < 0.8 : masked_token = '[MASK]' else : # 10% of the time, keep original if random.random() < 0.5 : masked_token = tokens[index] # 10% of the time, replace with random word else : masked_token = random.choice(vocab_list)
MLM提供的这种无监督的预训练方法,就是通过 mask token,让模型去预测被 mask 的 token,从而提高模型对token的理解,即提高表征能力 。所以我们总结一下,理解这两点就够了:
第一就是借鉴了CBOW完形填空的思想,去预测被mask掉的token; 第二就是训练阶段和测试阶段的间隙问题,通过MLM这个方法缓解了这个间隙。 7.2、Next Sentence Prediction 在很多NLP的下游任务中,如问答(QA)和自然语言推断(NLI),都基于两个句子做逻辑推理,而语言模型并不具备直接捕获句子之间的语义联系的能力 ,或者可以说成单词预测粒度的训练到不了解句子关系这个层级,为了学会捕捉句子之间的语义联系 ,使得模型能够很好的理解两个句子之间的关系 ,BERT 采用了下句预测(NSP ) 作为无监督预训练的一部分,即第二个预训练任务。
NSP 的具体做法是:BERT 输入的语句将由两个句子构成,即句子对(A,B)作为输入,让模型来预测句子B是不是句子A的真实的下一句话 。所有参与训练的语句都可以被选中句子A:
其中50%的B,是原始文本中真实跟随A的下一句话,label为 Isnext,代表 正样本 ;作者的原话翻译:50% 的概率将语义连贯的两个连续句子作为训练文本; 其实50%的B,是原始文本中随机抽取的一句话,label为 Notnext,代表 负样本 ;作者的原话翻译:另外 50% 的概率将完全随机抽取两个句子作为训练文本。 连续句对一般选自篇章级别的语料,以此确保前后语句的语义强相关。
示例如下:
连续句对:[CLS]今天天气很糟糕[SEP]下午的体育课取消了[SEP],label为1
随机句对:[CLS]今天天气很糟糕[SEP]鱼快被烤焦了[SEP],label为0
通过训练 [CLS] 编码后的输出标签,BERT 可以学会捕捉两个输入句对的文本语义 ,在连续句对的预测任务中,BERT 的正确率可以达到 97%-98%。
我们还是举例来理解,[CLS] 和 NSP 任务的一个流程关系:
1、首先我们拿到属于上下文的一个句子对,按照Input的方式在连续的句子里加一些特殊token:[CLS] 上一句话 [SEP] 下一句话 [SEP] ; 我们假设这个句子对是:[CLS] 我的狗很可爱 [SEP] 它喜欢玩耍 [SEP] ; 2、同样需要准备格式相同的两句话,但它们不是上下文关系,即准备负样本 : 假设为:[CLS] 我的狗很可爱 [SEP] 企鹅不擅长飞行 [SEP] ; 实际训练中,正负样本比也是均衡的1:1 ,也就是一半的时间输出的文本属于上下文关系,一半不是。 3、Input准备好后,我们需要初始化一个 Segment Embedding,作用是让模型分开上句和下句; [CLS] 我的狗很可爱 [SEP] 它喜欢玩耍 [SEP] ---> [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]; [CLS] 我的狗很可爱 [SEP] 企鹅不擅长飞行 [SEP] ---> [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]; 注意我没写padding和位置编码,大家看个意思就好了。 4、随后就是 Encoder block 的计算,其实就是多头注意力,让每句话中的每一个字对应的词向量,都融入这句话中所有 token 的信息。训练结束后我们在最终隐层的计算结果里,取出 [CLS] 所对应的一条向量即可,里面就含有整个句子的信息,假设最后一个encoder block的输出维度为:[batch_size, seq_len, emb_dim],我们要取出 [CLS] 对应的词向量 ,即seq_len维度的第0个位置: 7.3、前向传播示意图 假设我们原始输入为单句:我想吃火锅 ,[CLS] 对应的索引为1; 将15%的token按照8:1:1进行mask,这里大家看个大概意思就好了,假设 “吃” 这个 token 被 mask,mask 对应的索引为4; 然后经过三个Embedding的相加,每个Embedding的维度是512,得到 Input Embedding; 然后经过 Encoder block 进行特征提取,最后得到的每个 token 的词向量维度也是512; [CLS] 对应的特征向量用作NSP二分类任务,将 [CLS] 的输出接一个linear层映射到2维; MLM任务要预测被 mask 掉的词,我们将被 mask 的词也接一个linear层,不过它要映射到词表大小 ,然后再去 Softmax,取概率最大的词计算损失 ,以此来无监督的提高表征学习 。 八、BERT的Fine-tuning(下游任务改造) BERT 根据NLP下游任务的输入和输出的形式,将微调训练支持的任务分为四类,分别是句对分类、单句分类、文本问答和单句标注 ,接下来我们重点介绍下 BERT 如何通过微调训练句对分类、单句分类的任务 。
对于 文本问答 和 单句标注 任务本文不做详细介绍,可粗略理解为:
文本问答任务:给定一个问句和一个蕴含答案的句子,找出答案在后句的位置,称为文本问答,例如给定一个问题(句子 A),在给定的段落(句子 B)中标注答案的起始位置和终止位置。 单句标注任务:给定一个句子,标注每个词的标签,称为单句标注。例如给定一个句子,标注句子中的人名、地名和机构名。其实就是把所有的token输出,做一个softmax,看它属于实体中的哪一个。 8.1、句对分类 句对分类任务:
其实本质上是一个文本匹配的任务,例如判断句对是否相似、判断后者是否为前者的答案。 也是用 [CLS] 的输出 ,0代表不相似,1代表相似。 针对句对分类任务,BERT 在预训练过程中就使用了 NSP 训练方法获得了直接捕获句对语义关系的能力。 如上图所示,句对用 [SEP] 分隔符拼接成文本序列,在句首加入标签 [CLS],将句首标签所对应的输出值作为分类标签,计算预测分类标签与真实分类标签的交叉熵,将其作为优化目标,在下游任务数据上进行微调训练。
针对二分类任务,BERT 不需要对输入数据和输出数据的结构做任何改动,直接使用与 NSP 训练方法一样的输入和输出结构就行,因为NSP本身就是一个二分类任务。 针对多分类任务,需要在句首标签 [CLS] 的输出特征向量后接一个全连接层和 Softmax 层,保证输出维数与类别数目一致,最后通过 argmax 操作(取最大值时对应的索引序号)得到相对应的类别结果。 下面给出句对分相似性任务的实例:
任务:判断句子 '我很喜欢你' 和句子 '我很中意你' 是否相似 输入改写:'[CLS]我很喜欢你[SEP]我很中意你' 取 '[CLS]' 标签对应输出:[0.02 , 0.98 ] 通过 argmax 操作得到相似类别为 1 (类别索引从 0 开始),即两个句子相似
8.2、单句分类 单句分类任务:
给定一个句子,判断该句子的类别 ,统称为单句分类,例如判断情感类别、判断是否为语义连贯的句子。单个文本分类也是使用 [CLS] 的输出进行微调。 针对单句二分类任务,也无须对 BERT 的输入数据和输出数据的结构做任何改动。 如下图所示,单句分类在句首加入标签 [CLS],将句首标签所对应的输出值作为分类标签,计算预测分类标签与真实分类标签的交叉熵,将其作为优化目标,在任务数据上进行微调训练。
同样,针对多分类任务,需要在句首标签 [CLS] 的输出特征向量后接一个全连接层和 Softmax 层,保证输出维数与类别数目一致,最后通过 argmax 操作得到相对应的类别结果。
下面给出情感类别判断任务的实例:
任务:判断句子 '这家火锅店太好吃了,性价比很高' 是否为积极情绪 输入改写为:'[CLS]这家火锅店太好吃了,性价比很高' 取 '[CLS]' 标签对应输出:[0.1 , 0.9 ] 通过 argmax 得到类别为 1 ,即这个句子是积极的情绪
下面给出语义连贯性判断任务的实例:
任务:判断句子 '海大球星饭茶吃' 是否为一句话 输入改写:'[CLS]海大球星饭茶吃' 取 '[CLS]' 标签对应输出:[0.99 , 0.01 ] 通过 argmax 操作得到相似类别为 0 ,即这个句子不是一个语义连贯的句子
九、GPT、BERT和Transformer的对比 其实这个没什么好对比的,GPT和BERT都可以说是一个集大成者,它们都是在Transformer的基础上搭建起来的,一个用了Transformer的Encoder,一个用了Transformer的Decoder。为什么这里要单独做成一个标题,主要是想分享一下我对Encoder和Decoder的理解。
我们不止一遍的提到过,编码器是做特征提取 的,也就是单词经过它会得到单词向量、图片经过它会得到图片向量,视频经过它会得到视频向量。这里我感觉它是BERT起源 的一个点,既然什么东西经过Encoder都能得到其对应的向量,那么在这个过程中,Encoder是能够认识 这个单词、这张图片、这个视频的,就是通过这种方式让计算机、让模型准确的认识客观世界的“万事万物”,从而可以得到人类无法描述和理解的高级信息(语义特别丰富) ,然后可以通过BERT的下游任务进行改造 来发掘物与物之间的联系。
而解码器其实是在做一个生成的事情,因为它里面有掩码多头注意力 ,所以就注定不能发掘 后面的信息。
可能会有同学不理解,我们说的再直白一些。编码器因为多头注意力的存在可以挖掘上下文的关系,而解码器的多头注意力有掩码,所以不能发掘后面的信息,它靠的是上文,既然靠的是上文,那么就可以用上文信息去预测下文,这就叫做生成式 。所以解码器有了这样的能力,它就能干BERT无法干的事情。这也就是GPT和BERT的本质区别 ,BERT无法做生成式任务。
当然我们站在后来人的角度上来思考这些的时候,是无法准确的去评判BERT和GPT到底那个好那个坏,因为它们的目的是不一样的,一个是为了更好的编码得到高级表征信息,一个是做一些生成式的任务 。
我猜这个时候肯定会有人问:GPT就不能认识客观的世界吗?肯定能,只不过它是通过上文去认识的,它认识的不如BERT那也详细和准确。
十、ELMo、GPT、BERT的对比 回顾双向的意思:单词考虑了上下文的语境。
ELMO 使用自左向右和自右向左编码的两个 LSTM 网络,分别以 和 为目标函数独立训练(它是上文一个,下文一个,而不是同时获得上下文),将训练得到的特征向量以拼接的形式实现双向编码,本质上还是单向编码 ,只不过是两个方向上的单向编码的拼接而成的伪双向编码 。为什么说是伪双向编码?因为ELMO用的是LSTM: 比如对于 ,虽然能够获取它的上文和下文,它将获取到的上文和下文是拼成一个双向编码,但是它受限于LSTM,所以是伪双向编码,也就是说在某一时间段,当它获取上文的时候它获取不到下文信息,当获取下文信息的时候它看不到上文信息 。 GPT 使用 Transformer 的 Decoder 作为 block,以 为目标函数进行训练,用 block 取代 LSTM 作为特征提取器,实现了单向编码,是一个标准的预训练语言模型,即使用 Fine-tuning 模式解决下游任务。BERT也是一个标准的预训练语言模型,使用 Transformer 的 Encoder 作为block,它以 为目标函数进行训练,BERT使用的 Encoder 编码器属于双向编码器。 BERT用的是 Transformer 的 Self-Attention,Self-attention 是可以直接看到前后文 的,所以说BERT的双向编码是真正意义的双向编码 ,因为它考虑了上下文。这也是Self-Attention解决LSTM无法利用上下文信息的问题。 BERT和ELMO的区别 在于使用 Transformer block 作为特征提取器,加强了语义特征提取的能力。BERT和GPT的区别 在于使用 Transformer Encoder 作为block,并将GPT的单向编码改为双向编码,也就是说BERT舍弃了 文本生成能力,换来了更强的语义理解能力 。BERT基于 Encoder 做的是特征提取得到特征向量,用于下游任务。GPT基于 Decoder 做的是生成式任务。 从任务属性的角度来讲BERT和ELMO更相似,目的都是获取特征向量 。 预训练语言模型总结 首先我们还是回顾一下预训练这个过程本质上是在做什么事情,本质上预训练是通过设计好一个网络结构来做语言模型任务,然后把大量甚至是无穷尽的无标注的 自然语言文本利用起来 ,预训练任务把大量语言学知识抽取出来编码到网络结构中,当手头任务带有标注信息的数据有限时,这些先验的语言学特征当然会对手头任务有极大的特征补充 作用,因为当数据有限的时候,很多语言学现象是覆盖不到的,泛化能力就弱,集成尽量通用的语言学知识自然会加强模型的泛化能力。如何引入先验的语言学知识其实一直是NLP尤其是深度学习场景下的NLP的主要目标之一,不过一直没有太好的解决办法,而ELMO/GPT/Bert的这种两阶段模式看起来无疑是解决这个问题自然又简洁的方法,这也是这些方法的主要价值所在。
最后我们可以再梳理下几个模型之间的演进关系,从上图可见,Bert 其实和 ELMO 及 GPT 存在千丝万缕的关系:
比如如果我们把 GPT 预训练阶段换成双向语言模型,那么就得到了Bert; 而如果我们把 ELMO 的特征抽取器换成 Transformer,那么我们也会得到Bert。 所以简单总结下Bert最关键的两点:
借鉴CBOW的思想进行语言模型的预训练,因为采用的是 Transformer Encoder作为特征提取器,所以它是双向的语言模型; 此外为了更好的适应不同的下游任务,预训练的时候额外引入一个NSP任务。 torch复现BERT示例代码 还是要强调一下,复现只是为了更好的理解模型,实际还是用人家预训练好的,代码放到博客里了: https://wangguisen.blog.csdn.net/article/details/127319881
另外 Transformer 和 BERT 的面经可以看下: https://wangguisen.blog.csdn.net/article/details/127303056
BERT的变种 也是后续打算写的文章(应该会写的吧?... ),这里说一下是扩充大家的知识面,同时也作为本文的结尾段落。
RoBERTa (Robustly Optimized BERT Pre-training Approach),其对BERT预训练做了一个优化,主要贡献如下:
在MLM任务中,使用dynamic mask,而不是 static mask; 更大的词表,使用byte-level(BBPE)作为tokenizer。 ALBERT (A Lite BERT),它是BERT的一个微缩版,主要贡献如下:
跨层参数共享(Cross-layer Parameter Sharing); 词向量因式分解(Factorized Embedding Layer of Parameterization); 句子顺序预测(Sentence Order Prediction); DistillBERT (A Distilled Version of BERT),它是BERT的一个蒸馏版本,主要贡献如下:
student model(即DistillBERT)的基本结构是一个6层的BERT模型,同时去掉了标记类型向量(token-type embedding)和池化模块(pooler); 训练与BERT基本一致,只是在计算loss时有区别(triple loss 三重损失); DistillBERT采用了MLM进行预训练,丢弃了NSP。 knowledge distillation 知识蒸馏概述:
一种常用的知识迁移方法,通常由教师模型和学生模型构成; 该技术能够将较大的模型压缩到一个较小的模型,同时基本保持原模型的效果。 References https:///abs/1810.04805v2
https://zhuanlan.zhihu.com/p/49271699
https:///index.php/archives/1456/
https://blog.csdn.net/Decennie/article/details/119597271
https://blog.csdn.net/qq_22795223/article/details/105930871
https://blog.csdn.net/jiaowoshouzi/article/details/89073944
https://blog.csdn.net/weixin_35795792/article/details/112230542
https://blog.csdn.net/weixin_45169380/article/details/124471472