多Transformer的双向编码器表示法
Bidirectional Encoder Representations from Transformers,即Bert;
第2章 了解Bert模型(掩码语言模型构建和下句预测)
文本嵌入模型Bert,在许多自然语言处理任务上表现优秀,本节主要包括:
- 了解Bert,及与其他嵌入模型的区别;
- 分析Bert工作原理和基础配置;
- 两个任务场景:掩码语言模型构建和下句预测;
- Bert训练过程;
Bert基本理念
问答任务、文本生成、句子分类的自然语言处理任务重都有良好表现,其成功在于它是基于上下文的嵌入模型(有别于通过建立词在语言空间中的向量映射的模型);
有上下文嵌入模型 vs 无上下文嵌入模型
- 对于一词多义,同一个词的词嵌入,在不同场景(这里指上下文)中应不同;
- 如果是无上下文嵌入的话,那么词嵌入就会相同;
而Bert就是一个基于上下文的模型,先理解语境(Bert会将词与句子中的所有单词联系起来),然后根据上下文生成该词的嵌入值;
BERT是基于Transformer模型的,可以把BERT看做是只有编码器的Transformer;
我们已经知道Transformer编码器会输出句子中每个词的特征值,且Transformer编码器是双向的,它可以从两个方向读取一个句子;
一个句子送入编码器,编码器就会利用多头注意力层来理解每个单词在句中的上下文,并输出特征值;每个单词的特征向量大小是前馈网络层的大小(即隐藏神经元的个数),假设是768,那么每个单词的特征向量大小也是768
,这个768同样也应该是用于生成QKV矩阵的权重矩阵中权重向量的维度;
Bert的两种标准配置:
- BERT-base
- BERT-large
BERT-base:
- L:由12层编码器叠加;
- A:
每层编码器都使用12个注意力头
; - H:隐藏神经元数量768;
- 其总参数量达到1.1亿个;
BERT-large:
- L:24
- A:16
- H:1024
- 网络参数量达3.4亿个;
其他的一些小型配置:
- BERT-tiny:L=2、H=128
- BERT-mini:L=4、H=256
- BERT-small:L=4、H=512
- BERT-medium:L=8、H=512
更小配置的BERT可以适配到更有限的资源,但标准BERT可以得到更准确的结果;
BERT 模型预训练
对模型m使用一个大型数据集针对某个具体任务进行训练,并保存训练模型;再对一个新任务,使用已经训练过的模型权重来初始化m(不使用随机初始化从头训练),并根据新任务调整(微调)其权重;
BERT模型在一个巨大的语料库上针对两个特定任务进行预训练:掩码语言模型构建、下句预测;训练得到的模型,在一个新任务中,比如问答任务,载入预训练参数,微调权重即可;
WordPiece:
一种特殊的词元分析器,遵循子词词元化规律;
示例:Let us start pretraining the model,使用WordPiece标记的结果为:
tokens = [let, us, start, pre, ##train, ##ing, the, model]
具体过程:
- 检查该词是否存在于词表中,在则作为一个标记;
- 不在,则继续分成子词,检查子词是否在词表中,在则作为一个标记;
- 不在则继续分割子词;
- 通过这种方式不断进行拆分,检查子词是否在词表中,直到字母级别(无法再分),这在处理未登录词时是有效的;
BERT词表有3万个标记,本例中词表中没有pretraining这个词,因此将该词进行拆分,##
表示该词是一个子词,其前面还有其他的词;
对输入数据的处理:
在将数据输入BERT前,需要使用3个嵌入层将输入转换为嵌入:标记嵌入层、分段嵌入层、位置嵌入层;
标记嵌入层:
- 对于一个文本段落,添加
[CLS]
标记到第一个分词并掩码
后的句子开头(只在第一个开头加); - 添加
[SEP]
标记到每个分词并掩码
后的句子末尾(每一句结尾都要加); [CLS]
用于分类任务,[SEP]
表示每个句子结束;- 使用标记嵌入层将标记后的文本转换为嵌入,注意标记嵌入的值将通过训练学习获得的;
分段嵌入层:
- 用来区分两个给定的句子;
- 分段嵌入层只输出EA或EB,即如果输入的标记属于句子A,那么该标记将被映射到嵌入EA;
如果只有一个句子,句子的所有标记都将被映射到嵌入EA;
位置嵌入层:
- 用来获得句子中每个标记的位置嵌入的;
输入 | [CLS] | Pairs | is | a | city | [SEP] | I | love | it | [SEP] |
---|---|---|---|---|---|---|---|---|---|---|
+标记嵌入 | E[CLS] | EPairs | Eis | Ea | Ecity | E[SEP] | EI | Elove | Eit | E[SEP] |
+分段嵌入 | EA | EA | EA | EA | EA | EA | EB | EB | EB | EB |
+位置嵌入 | E0 | E1 | E2 | E3 | E4 | E5 | E6 | E7 | E8 | E9 |
现在就可以将结果送入BERT了;
预训练策略
BERT模型在一个巨大的语料库上针对两个特定任务进行预训练:
- 掩码语言模型构建
- 下句预测
语言模型构建:
语言模型构建任务是指通过训练模型来预测一连串单词的下一个单词,可以把语言模型分两类:
- 自动回归式语言模型
- 自动编码式语言模型
自动回归式语言模型有以下两种方法:
- 正向(从左到右)预测;
- 反向(从右到左)预测;
举例:Pairs is a city. I love it.
- 将city用空白代替,如果使用正向预测,那么模型就会从左到右读取所有单词,直到空白处,然后进行预测;
- 若使用反向预测,那么模型就会从右到左完成这个过程;
自动回归式语言模型本质上是单向的,它只能沿着一个方向阅读句子;
自动编码式语言模型:自动编码式语言模型是双向的,可以同时利用正向预测和反向预测的优势,预测时同时从两个方向阅读句子;这样能更清晰地理解句子,能输出更好的结果;
掩码语言模型构建:
BERT是自动编码式语言模型,在掩码语言模型构建任务中,给定一个输入句,随机掩盖其中15%的单词,并训练模型来预测被掩盖的单词;
工作原理:
- 依旧用之前例句,使用
[MASK]
替换了city,就可以训练BERT模型来预测被掩盖的词;
这里会引入一个小问题:
- BERT预训练模型是通过预测
[MASK]
来训练BERT,但在下游任务微调时,输入中不会有任何[MASK]
标记,这将导致BERT的预训练方式和用于微调的方式不匹配;
解决办法是使用80-10-10规则:
- 对于随机覆盖的15%标记,再做如下处理;
- 在80%的情况下,使用
[MASK]
标记来替换实际词标记; - 10%的数据,使用一个随机标记(随机词)来替换实际词标记;
- 剩余10%的数据,不做任何改变;
在分词和掩码
后,将标记列表送入标记嵌入层、分段嵌入层和位置嵌入层,得到嵌入向量;再将嵌入向量送入BERT,BERT将输出每个标记的特征向量
输入 | [CLS] | Pairs | is | a | city | [SEP] | I | love | it | [SEP] |
---|---|---|---|---|---|---|---|---|---|---|
输入 | [CLS] | Pairs | is | a | [MASK] | [SEP] | I | love | it | [SEP] |
+标记嵌入 | E[CLS] | EPairs | Eis | Ea | Ecity | E[SEP] | EI | Elove | Eit | E[SEP] |
+分段嵌入 | EA | EA | EA | EA | EA | EA | EB | EB | EB | EB |
+位置嵌入 | E0 | E1 | E2 | E3 | E4 | E5 | E6 | E7 | E8 | E9 |
输出标记 | R[CLS] | RPairs | Ris | Ra | Rcity | R[SEP] | RI | Rlove | Rit | R[SEP] |
若使用BERT-base配置,12层编码器、12个注意力头、768个隐藏神经元,输出的每个标记的特征向量大小也是768;
那么如何使用这些特征向量来预测被掩盖的词呢?
- 将BERT计算的 被掩盖的词的特征向量
R[MASK]
送入使用softmax激活函数的前馈网络层,输出词表中所有单词为掩盖单词的概率; - 通过多次训练迭代,更新优化BERT的前馈网络层和编码器层权重,使之最优,这样模型才能返回正确的概率;
掩码语言模型构建任务也被称为完形填空任务
;除了对输入标记进行掩码处理,还可以使用另一种方法,即全词掩码
;
全词掩码:
- 在全词掩盖中,如果子词被掩盖,那么该子词对应的单词也将被掩盖;
- 如果掩码率超过15%,那么可以忽略掩盖其他词;
下句预测:
下句预测(next sentence prediction)是一个用于训练BERT模型的策略,它是一个二分类任务;
在该任务重,我们想BERT模型提供两个句子,预测第二个句子是否是第一个句子的下一句;通过执行下句预测任务,BERT模型可以理解两个句子之间的关系;
可以从任何一个单一语言语料库中生成数据集:
- 从一个文档抽取任意两个连续句子,标为isNext;
- 从一个文档中抽取一个句子,并从一个随机文档中抽取另一个句子,标为notNext;
- 注意,要保证isNext类别与notNext类别数据各占50%;
输入句子对,预测标签;
具体过程:
- 使用wordpiece添加标记:
[CLS]
第一个句子分词[SEP]
第二个句子分词[SEP]
; - 然后将标记送入 标记嵌入层、分段嵌入层和位置嵌入层,得到嵌入值;
- 再将嵌入值送入BERT模型,得到每个标记的特征值;
- 根据特征值进行分类,只需
将[CLS]标记的特征值通过softmax激活函数将其送入前馈网络层
,然后返回句子对分别是isNext和notNext的概率;
这里之所以只需要取
[CLS]
标记的嵌入,是因为该标记基本上汇总了所有标记的特征,所以它可以表示句子的总特征;
同样需要通过多次训练迭代,更新优化BERT的前馈网络层和编码器层权重,使之最优,这样模型才能返回正确的概率;
预训练过程
BERT使用多伦多图书语料库(Toronto BookCorpus)和维基百科数据集进行预训练,我们已将了解了两种训练任务:掩码语言模型(完形填空)和下句预测任务,现在需要准本数据集;
- 从语料库中抽取两个句子A和B;
- 句子的标记数之和 应小于或等于
512
; - 对两个句子进行采样时,需保证句子B作为句子A的下一句和非下一句比例为1比1;
- 使用wordpiece添加标记,将
[CLS]
标记在第一句开头,将[SEP]
标记在每句结尾; - 根据80-10-10规则,随机掩盖15%的标记;
- 然后将得到嵌入的标记送入BERT模型,并训练BERT模型预测被掩盖的标记,
同时
对句子B是否是句子A的下一句进行分类;
BERT使用256个序列批量(Batchsize=256)进行100w步的训练,使用Adam优化器,lr=1e-4、β1=0.9、β2=0.999,预热步骤设置为1w;
关于预热步骤:我们知道在训练的初始阶段可以设置较高学习率,使最初迭代更快接近最优点,后续迭代中,则调低学习率使结果更加准确,因为在最初,权重值远离收敛值,较大幅度的lr变化是可接受的,但后续如果已经接近收敛值,仍采样相同的变化幅度,就容易错过收敛值,这就是学习率的调整策略;而预热步骤,是通过1w次迭代,将学习率由0线性的提高到1e-4,1w之后的迭代,再随着误差接近收敛,线性地降低学习率;
在训练中还对所有层使用了随机节点关闭(dropout),每层关闭节点概率为0.1,激活函数使用GeLU(即高斯误差线性单元Gaussian Error Linear Unit);
看着有点像relu,但是在小于0的一部分x轴的y值是较小的负值,实际曲线像一个对号;
预训练后,BERT模型就可以应用于各种任务;
几种子词词元化算法
子词词元化算法:BERT所使用的,在处理未登录词方面非常有效;
假设有一个训练数据集,更具它我们创建一张词表(分词 添加):
- 一般的词表(vocablary)由许多单词(标记)组成;
- 在对句子进行标记时,如遇到非词表单词,可使用标识未知单词的标记
<UNK>
代替; - 为避免词表过大,可以通过子词词元化,将单词分成子词,将子词加入词表,子词在标识时需要在前面添加两个
#
号,表示和前面的词相关联; - 子词词元化算法 可以决定哪些单词需要拆分,哪些不需要;
常见的三种子词词元化算法:
- 字节对编码 BPE
- 字节级字节对编码 BBPE,每个Unicode字节都被转换为1字节,一个字符可以有1~4个字节,然后使用字节对编码算法,使用字节级频繁对构建词表;在对语言环境下很有用;
- WordPiece,与字节对编码稍有不同:
- 字节对编码 从给定数据集中提取带有计数的单词,然后将其拆分成字符序列,再将具有高频率的符号进行合并;不断迭代具有高频率的符号对,直到满足词表的大小要求;
- 而在WordPiece中,不根据频率合并符号对,而是根据相似度合并符号对,合并具有高相似度的符号对,其相似度由在给定的数据集上训练的语言模型提供;
对于出现最频繁的符号对(合并),检查每个符号对的语言模型(在给定的训练集上训练)的相似度,合并相似度最大的符号对;
算法步骤:
- 从给定的数据集中提取单词并计算它们出现的次数;
- 确定词表大小;
- 将单词拆分成一个字符序列;
- 将字符序列中的所有非重复字符添加到词表中;
- 在给定的数据集(训练集)上构建语言模型;
- 选择合并具有最大相似度(基于上一步中的语言模型)的符号对;
- 重复上一步,直到达到所设定的词表大小;
词表构建后,就可以用来做文本标记。