1.BERT整体模型架构
-
基础架构 - TRM的Encoder部分
BERT的基础架构是transformer的encoder部分。为什么说是基础架构,因为bert是由多个encoder堆叠而成,其中bert-base使用的是12层的encoder,bert-large使用的是24层的encoder。
简单看一下,一共12层的encoder,这里想谈一个比较容易混淆的点;12个encoder堆叠在一起组成了bert,而不是12层的transformer堆叠在一起组成了bert(transformer在原论文中应该是6个encoder堆叠在一起成了编码端,6个decoder堆叠在一起变成了解码端)。
对于bert 的encoder部分 我们重点是关注他的输入部分。对于transformer来说他的输入部分包括input embedding 和 position encodeing;注意,在transformer中我们使用三角函数(正余弦函数)去代表他。但是在bert中,我们分为3个部分, 第一部分是token_emb 第二部分是segment emb 第三部分是position emb。注意:这里是position embedding 区别于 transformer的position encoding。
首先看input这一行,对于这一行我们重点关注两个部分:第一部分是正常词汇。第二部分是特殊词汇[cls] [sep] [sep] 是两种特殊符号,这两种特殊符号的存在都是因为bert的预训练任务中有一个任务是NSP任务,去判断两个句子之间的关系。因为处理的是两个句子 所以需要一个符号去告诉模型符号前后是两个不同的句子。我们要做的NSP任务又是一个二分类任务,如何去做二分类任务,作者在句子前面加入CLS的特殊符号,在训练的时候将CLS的输出向量接一个二分类器。
扩展:CLS的误解:很多人认为CLS的输出向量代表的是整个或者整两个句子的语义信息。个人理解:在预训练结束之后,CLS的输出向量并不能说代表了整个句子的语义信息。CLS这个向量用在NSP这个任务中是一个二分类任务,与编码的整个句子的语义信息差的远,所以大家都会发现一个问题:如果用CLS整个输出向量去做无监督文本相似度任务的时候效果特别差。
经验:bert pretrain模型直接拿出来做sentence embedding效果甚至不如word embedding,cls的embedding效果最差(也就是你说的pooled output)。把所有普通token embedding做pooling勉强能用(这个也是开源项目bert-as-service的默认做法),但也不会比word embedding更好。
token_embedding:就是对所有的input的词汇进行向量化表示。
segment_embedding:因为我们处理的是两个句子,所以我们需要对两个句子进行区分。在第一个句子我们全部用0来表示,后面的句子我们全部用1表示。代表两个句子
position_embeddings:代表的是bert的输入部分,与transformer的输入部分有一个很大的不同点,在transformer中,用的是正余弦函数,在bert使用的是随机初始化,然后让模型自己去学习。比如说第一个位置定位0 第二个位置定位1 直到511。让模型自己学习embedding的向量是什么样子的。
2.如何做预训练:MLM + NSP
预训练的bert主要涉及到两个任务 一个是MLM 另一个是NSP。
MLM:bert在预训练的时候使用的是大量的无标注预料,所以在预训练任务设计的时候一定是要考虑无监督来做。对于无监督的目标函数,有两组目标函数比较收到重视 AR模型(自回归模型:只能考虑到单侧信息,典型的就是GPT)、 AE模型(自编码模型);从损坏的输入数据中重建原始数据,可以使用到上下文的信息。BERT使用的就是AE。
AR模型有前后的依赖关系,顺序过来的,只用到了单侧的信息与之对应的,AE模型是对句子做一个mask,用面具掩盖句子中的某几个单词。mask这个模型,他的本质是在打破文本原有的信息。让模型预训练的时候做文本重建,在文本重建的时候,模型绞尽脑汁的从周围的文本中学习各种信息让自己训练出来的mask词汇无限的接近原本的词汇。模型在学习训练的时候,要让mask这个词出来的时候无限的接近或者就是原来的那个词。
mask模型的缺点:
我们看优化目标发现:优化目标认为吃和饭是相互独立的 也就是mask和mask相互独立。但是大多数的情况下mask与mask之间不是相互独立的。这就是mask模型的缺点。bert在预训练的时候第一个就是MLM 用到的就是mask策略,需要注意的是mask的概率问题,随机mask15%的单词。 15%个单词中10%替换成其他单词 10%原封不动,80%替换成真正的mask。
mask代码实践:
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)
NSP任务:最重要的点应该是理解样本构造模式
NSP样本如下:
1.从训练预料库中取出两个连续的段落作为正样本(两个连续的段落说明了来自同一个文档,一个文档一个主题,首先是同一个主题下,同一个主题下的两个连续的段落,说明顺序没有颠倒)
2.从不同文档中随机创建一堆段落作为负样本(不同的主题随便抽一个链接做负样本)
缺点:主题预测和连贯性预测合并为一个单项任务。由于主题任务是非常简单的,导致整个任务在做的时候就变得简单起来。这也是后续很多实验去预测NSP没有很好效果的主要原因,ALBERT直接抛弃掉了主题预测。ALBERT的正负样本都来自于同一个文档,正样本顺序,负样本颠倒。
3.如何微调BERT,提升BERT在下游任务中的效果
主要是分为4种,句子对的分类任务;单个句子的分类任务;问答;序列标注任务
对于序列标注:把所有的token输出做softmax看他属于实体中的哪一个。
对于单个样本:使用CLS的输出做微调(二分类或者多分类)
对于句子对:本质是文本匹配,把两个句子拼接起来看是否是同一个主题。
在下游任务重还是比较简单的
如何提升bert在下游任务中的表现
我们在实际应用的时候一般很少从头训练一个bert。一般是用已经训练好的,然后自己在自己的任务中做一个微调。一般的做法都是先获取谷歌中文bert ,然后基于自己的任务数据做一个微调。如果想要更好的性能,现在有很多trick可以做
四步骤
比如做微博文本情感分析
1.在大量通用语料上训练一个LM(Pretrain);——中文谷歌bert
2.在相同领域上继续训练LM(Domain transfer); ——在大量微博文本上继续训练这个bert
3.在任务相关的小数据上继续训练LM(task transfer) :—— 在微博情感文本上(有的文本不属于情感分析范畴)
4.在任务相关数据上做具体任务(Fine-tune)
先Domain transfer 再进行Task transfer 最后Fine-tune性能最好的
如何在相同领域数据中进行further pre-training
1.动态mask:就是每次epoch去训练的时候mask,而不是一直使用同一个(bert并不是一直使用同一个文件mask,为了避免这一点bert有做改进,有复制一些文本)
2.n-gram mask:其实比如ERINE 和SpanBert都是类似于做了实体词的mask(如果自己训练的时候没有实体词,可以做n-gram mask)
3.参数一定要设置的比较好 :
Batch_size : 16 32一般影响不打
Learning rate(Adam):5e-5 3e-5 2e-5 尽可能小一点避免灾难性遗忘
Number of epochs : 3 4
weighted decay修改后的adam,使用warmup,搭配线性衰减
在预训练的时候做数据增强,自蒸馏,外部数据的融入(加入实体词信息,使用知识图谱的信息等)
4.如何在脱敏数据中使用beert等预训练模型
1.如何在脱敏数据中使用BERT
2.基于此预料如何使用NSP任务
对于脱敏数据中使用bert,一般可以分为两种
第一种就是直接从零开始基于预料训练一个新的bert出来使用
第二种就是按照词频,把脱敏数字对照到中文或者其他语言[假设我们使用中文],使用中文bert做初始化,然后基于新的中文语料训练bert
很多人对预训练模型理解的不是很深刻,很疑惑为什么在脱敏数据中也可以训练bert等预训练模型
其实这一点也很容易理解
最开始bert用英文预料训练出来的,然后有人基于中文语料开源了中文的bert
那么我的脱敏数字就是类似与中文的一种另外的语言,你可以看成是【X】语言,我们当然可以基于【X】语言的预料去训练一个新的BERT或者其他的预训练模型
NSP任务如何去使用的问题:
很明显,在当前这个任务中是一个文本匹配的形式;
语料不是我们自己有主动的去获取的能力,所以构造一个NSP任务的格式比较困难
但是NSP任务仅仅是一种任务形式,我们完全可以基于训练语料构造一个是否匹配的任务,可以称之为NSP任务
基于此,测试数据是使用不了的,因为测试数据没有label
不过,对于测试数据可以使用MLM任务,训练数据使用MLM+类NSP任务。