1. bert模型架构
基础架构——transformer的encoder部分(如下图)
transformer 是多层encoder-多层decoder结构。input = word_embedding + positional_encoding(word_embedding 词向量,可以是随机初始化,也可以使用 word2vec;positional_embedding用正余弦表示位置特征)
而bert是多层encoder 结构。
1. 输入部分
input = token_embedding + segment_embedding + positional_embedding
token_embedding ——词向量,可以是随机初始化,也可以使用 word2vec
segment_embedding ——用于对两个句子进行区分,CLS到到中间SEP,全部都是(整个句子都是一样的),也可以全部用0;SEP到SEP,全部用,全部用1.
positional_embedding ——区别于transformer中的 positional_encoding,使用随机初始化(但为什么用随机初始化,而不用正余弦函数,没有找到很好地解释)
input 中的特殊字符:CLS,SEP
(bert有两个训练任务,一个是NSP,next sentence prediction,用来判断两个句子之间的关系,因此,需要有一个字符告诉计算机句子与句子之间的分割,所以有了SEP字符。同时,NSP也是句子之间的二分类任务,因此,作者在句子前接了一个CLS字符,训练的时候,将CLS输出后面接一个二分类器,这就是CLS的作用。但是,有一个问题,很多人认为CLS代表整个句子的语义信息,但是原文并没有这种说法,所以可以作为一个思考点。B站UP主做过一个测试,他也提供了一些其他证据,bert预训练模型直接拿来做sentence embedding,效果甚至不如word embedding,CLS效果最差。)
2. 多头注意力部分
3. 前馈神经网络
区分:encoder与 decoder
2. 如何做预训练
2.1 MLM 掩码语言模型
bert使用大量无监督预料进行预训练。无监督模型有两种目标函数,比较受重视:
举例:原始预料“我爱吃饭”
1. AR(auto-regressive 自回归模型):只考虑单侧信息,典型的GPT
P(我爱吃饭) =P(我)P(爱|我)P(吃|我爱)P(饭|我爱吃)
AR的优化目标是:“我爱吃饭”的概率 = “我”出现的概率 “我”出现的条件下,“爱” 出现的概率...
这个优化目标,是有一个前后依赖关系的。所以说,AR只用到了单侧信息
2. AE(auto-encoding 自编码模型):从损坏的输入数据中预测重建原始数据,可以使用上下文信息
对句子进行 mask,原句编程 “我爱mask饭”
P(我爱吃饭|我爱mask饭)=P(mask=吃|我爱饭)
AE的优化目标:“我爱吃饭”的概率 = “我爱饭”出现的条件下,mask=吃的概率
本质:打破文本原有的信息,让模型训练的时候,进行文本重建
模型缺点:
P(我爱吃饭|我爱mask mask)=P(吃|我爱)P(饭|我爱)
mask之后,吃 和 饭 之间被看做是独立的,但是本身是有关系的。
mask策略:
随机mask 15%的单词,这 15%中,0.1被替换成其他单词(有可能选到这个单词本身),0.1原封不动,0.8替换成其他(这个比例问题没有知道到解释),如下图:
2.2 NSP
NSP样本如下:
1. 从训练语料库汇总取出两个连续的段落作为正样本(说明两个段落来自于同一个文档,同一个主题,且顺序没有颠倒)
2. 从不同文档中随机创建一对段落作为负样本(不同的主题)
缺点:主题预测 和 连贯性预测 合并为一个单项任务
3. 如何微调bert,提升下游任务中的效果
四个常见的下游任务:
a. 句子对分类任务——本质是文本匹配,把两个句子拼接起来,判断是否相似。CLS接二分类器,输出 0-相似,1-不相似
b. 单个句子分类任务——CLS输出,接一个分类器,进行分类
c. 问答任务
d. 序列标注任务——把所有的token 输出,然后接softmax,进行标注(比如词性标注,命名实体识别)
如何提升bert下游任务表现?即微调策略。
基本步骤:1-获取一个训练好的bert,比如谷歌中文BERT;2-基于任务数据进行微调。
比如,做微博情感分析:
1-获取通用的预训练模型,比如谷歌中文BERT
2-在相同领域上继续做模型训练,比如在微博文本上进行训练(Domain tansfer 领域自适应,或 领域迁移)
3-在任务相关的小数据上继续训练,在微博情感任务文本上进行训练(task transfer 任务自适应,或 任务迁移)
4-在任务相关数据上做具体的任务 (fine-tune 微调)
在上面 领域迁移 步骤中,还可以进行 further pre-traning,比如:
1-动态mask:每次epoch去训练的时候mask
2-n-gram mask:ERNIE 和 SpanBERT类似于做了实体词 的 mask
可以对参数进行优化,从而提升模型效果:
batch size:16,32---128,影响不大,主要看及其效果
learning rate(Adam):5e-5, 3e-5, 2e-5 尽可能小一点,避免灾难性遗忘
number of epochs: 3,4
weighted decay:修改后的Adam,使用warmup, 搭配线性衰减
其他:
数据增强、自蒸馏、外部知识融入
比如 ERNIE ,融入了知识图谱,加入了实体信息
4. 如何在脱敏数据中心使用BERT等预训练模型
如果本身语料很大,可以从0开始训练一个bert
否则,按照词频,把脱敏数字对找到中文(假如是中文语料),使用中文做bert初始化,然后基于新的中文语料训练bert
5. 代码解读
代码最核心的一点,MLM损失函数的计算:
15%的词汇被mask,8:1:1的比例进行了不同的处理,那么损失函数究竟计算的是哪一部分?
最下面一行 是原始字符对应的索引。
mask:把第三个字符 从索引 13 替换成了 4 对应的字符。
接下来,经过三个embedding,然后拼接,组合成input
然后,经过 encoder 层(N层)
得到每个token的embedding:
第一个对应的是 CLS,可以接linear层,做二分类任务
被mask的位置,接linear层,在 词表大小的维度(bert的此表大小是22128)接 softmax,挑选最有可能的词汇,做损失函数
1. 参数部分
maxlen- 如果是document类很长的文本,一般会使用一些策略,使最大长度控制在256以内。
max_pred -为了限制一个句子最大可以预测多少个token
n_layers - 选择多少个 encoder,base一般选12,large一般选24
n_head - 多少个头(多头注意力机制)
d_ff -前馈神经网络的维度,不用特意写成768*4,没什么意义,直接写成3072
d_q = d_v Q-K-V向量的维度,一般K和V要一致
n_segments NSP做二分类任务,有两个SEP(区分出两个segments)
text 是 输入样本,正常任务中肯定是从文本文件中读取
sentences 是数据预处理的代码,去除掉原始文本中一些没用的字符
加下来,创建一个词表,其中 【PAD】编码为0、【CLS】-1、【SEP】-2、【MASK】-3等特殊字符进行编码,而文本中的字符,从3以后取。
token_list 是把文本转化成数字
为什么补0?