文章目录
引言
本节将按照思维导图逐步了解BERT语言模型(基于transformer的网络结构)。
BERT带来了新的NLP范式。
一、预训练语言模型
大规模的预训练语言模型的模型参数量呈几何倍数的增长趋势。下面我们了解BERT预训练的原理。
1.为什么要进行预训练?
基于词向量为基础的模型并不是在BERT中首次出现。在Word2vec中,词向量表示是有局限性的。这是因为词向量表达固定,无法表达上下文。2017年之前,NLP模型的普遍形态为词向量+encoder。
利用LSTM或者Transformer模型通过训练集来学习如何提取上下文信息,最终输出分类标签。这里的训练集指的是具有分类或者NER的标注信息。这种模式有如下缺陷:
- Encoder部分随机初始化,没有经过预训练,对优化器压力大;
- 数据集要足够好,足够大,encoder才能学会如何提取上下文信息
这两个缺陷导致2017年以前的模型非常具有局限性。
基于上述的缺陷,我们现在想要有效的预训练embedding+编码器。有标签的文本数据获取成本大,相比来说,获取大量无标签的文本数据代价很小。所以如何进行预训练呢?这就涉及到在大量无标签的文本上进行自监督学习(self-supervised training)。
2. BERT预训练-掩码预测任务
掩码预测任务与Word2vec中CBOW非常神似,但略有不同。
- 一方面,CBOW语言模型输入的是一个时间窗口,并不一定是一个句子;掩码预测任务输入的是一个句子(sentence)
- 另一方面,CBOW当中被预测的词是不做输入的;但掩码预测任务会遮掩某个词(用”[MASK]"字符替换原词)然后用于输入
- CBOW中是一句话预测一个词,掩码预测任务一句话可以遮掩多个字
掩码预测任务中,输入是句子当中遮掩掉几个词(用”[MASK]"字符替换原词),经过BERT网络输出每个字的向量表征。在遮掩的这个字的位置上要经过一个线性层来预测这个位置是哪个字的概率。掩码预测任务存在的问题:在下游任务中,eg:预测一个句子的情感是不会有[Mask]的。因此,在实现掩码任务时,需要遮掩的字只占语料全体的字数的一小部分(15%);在这15%里面:
- 一部分(80%)被遮掩,也就是替换为[MASK];
- 一部分(10%)随机替换为其他词(仍然需要预测此处的token应该是什么);
- —部分(10%)保留原词(仍然需要预测此处的token);
这样做的目的是使得模型能够区分这个词放在这里是否合理,使得模型具有判断这句话是否合理的能力,加深了模型对语言的理解。BERT模型在经过预训练之后天生就能够做一些下游任务,比如:纠错任务。
3.BERT预训练-下句预测任务
下句预测指的是:判断句子B是否是句子A的下文。此时,BERT句子的输入会是[CLS] A sent [SEP] B sent [SEP]
的格式。
我们经常通过句首的第一个token来表示句子整体的语义关系,在上下句预测的任务当中,上下句的关系是保存在输入的[CLS]
符号的当中,在预测时使用BertPooler提取整个输入序列的表征:
这里要注意:不是直接拿[CLS]的向量表征;而是要经过BertPooler这个模块,其中包含MLP,tanh操作得到一个向量表示,再输入到2分类层,BertPooler也是参与预训练的,预训练会更新整个bert的模型参数,微调时候其实是可以更新部分参数,后面会有介绍。
用了 Masked LM 和 Next Sentence Prediction 两种方法分别捕捉词语和句子级别的 representation
二、BERT的文本处理—subword tokenizer
1. 为什么要学习subword tokenizer?
subword tokenizer就是将长的复杂的单词分成成短的简单的单词,eg:句子” play the song little robin redbreast”在输入模型时变为”[CLS] play the song red ##bre ##ast [SEP]”
。Tokenizer是预训练时候用了哪个,后面微调就要用哪个。使用subword tokenizer的原因是:
- 传统词表示方法:是先对各个句子进行分词,然后再统计并选出频数最高的前N个词组成词表·词表一般较大,一是尾部词训练不充分,二是显存占用较大
- 传统词表示方法无法很好的处理未知或罕见的词汇(OOV问题)
- 传统词tokenization方法不利于模型学习词缀之间的关系
E.g.模型学到的“old”, “older”,and “oldest”之间的关系无法泛化到“smart”, “smarter”,and“smartest”。 - Character embedding作为OOV的解决方法粒度太细,且推理时候显存开销较大:
一个原本长度为12的句子,采用character embedding,则输入的大小变为12*16 (假设设置词字母长度最
大为16) - Subword粒度在词与字符之间,能够较好的平衡OOV问题
常见的subword模型:Byte Pair Encoding (BPE), WordPiece
2. 词表生成与分词
BPE算法生成词表过程如下:
- 准备一个语料库;确定期望的Subword词表大小;
- 将单词拆分为成最小单元。比如英文中26个字母加上各种符号,这些作为初始词表;
- 在语料上统计单词内相邻单元对的频数,选取频类最高的单元对合并成新的Subword单元;
- 重复第3步直到:达到第1步设定的Subword词表大小或下一轮迭代中token最高频数为1
假设现在语料库中有如下词汇(及其频率):
观察词汇表大小在每一步如何变化
BPE算法编码:得到Subword词表后,针对每一个单词,我们可以采用如下的方式来进行编码:
- 将词典中的所有子词按照长度由大到小进行排序;
- 对于要进行分词的单词w,依次遍历排好序的词典。查看当前子词是否是该单词的子字符串(贪婪的最长匹配),如果是,则输出当前子词,并对剩余单词字符串继续匹配;
- 如果遍历完字典后,仍不匹配,则将剩余字符串替换为特殊符号输出,如””;
WordPiece算法生成词表的步骤与BPE类似;加上”##”前缀表示token不作为一个完整单词的开始部分。与BPE的最大区别在于,如何选择两个子词进行合并∶BPE选择频数最高的相邻子词合并,而WordPiece选择能够提升语言模型概率最大的相邻子词加入词表;
- 假设句子 S = ( t 1 , t 2 , t n ) S =(t_1, t_2,t_n) S=(t1,t2,tn)由n个子词组成,表示子词,且假设各个子词之间是独立存在的
- 句子的语言模型似然值等价于所有子词概率的乘积: l o g P ( S ) = ∑ i = 1 n P ( t i ) logP(S)= \sum_{i=1}^nP(t_i) logP(S)=∑i=1nP(ti)
- 假设把相邻位置的
i
i
i和
j
j
j两个子词进行合并,合并后产生的子词记为
z
z
z,此时句子似然值的变化可表示为:
l o g P ( t z ) − ( l o g P ( t x ) + l o g P ( t y ) ) = l o g P ( t z ) P ( t x ) P ( t y ) logP(t_z)- (logP(t_x)+ logP(t_y)) =log\frac{P(t_z)}{P(t_x)P(t_y)} logP(tz)−(logP(tx)+logP(ty))=logP(tx)P(ty)P(tz)
很容易发现,似然值的变化就是两个子词之间的互信息。
简而言之,WordPiece每次选择合并的两个子词,他们具有最大的互信息值,也就是两子词在语言模型上具有较强的关联性,它们经常在语料中以相邻方式同时出现。
Wordpiece举例:
- 英文:采用BERT-base-uncased tokenizer
30522个单词;英文转化为小写
eg:
句子“play the song little robin redbreast”经过BERT的wordpiece tokenizer后变为”[‘play’, ‘the’, ‘song’, ‘little’, ‘robin’, ‘red’, ‘##bre’, '##ast ]” - 中文:采用Google Chinese BERT-base-uncased
21128个单词;不区分大小写;
中文汉字拆开(split_Chinese_character);
eg:
句子”我很喜欢一首歌: yesterday once more.”处理为['我,‘很’,‘喜’, ‘欢’,'一,'首, ‘歌’, ‘:’,‘yes’, ‘##ter’, ‘##day’, ‘on’, ‘##ce’, ‘more’, ‘.’]
三、BERT embedding
BERT的嵌入层包含三种embedding
- Token embedding
对应着Subword词表分词后的每一个单词 - Segmentation embedding
将句子的上一句与下一句进行区分对待,涉及到预训练任务当中的下句预测 - Position embedding
位置编码,为了能够让模型反映句子的顺序信息,位置编码研究前沿如今是相对位置编码
四、BERT微调—句子分类任务
句子分类任务就是把文本按照一定的规则分门别类,文本分类有以下应用场景:
那么,在下游任务中,如何进行BERT句子分类任务的微调?不妨以单个句子分类为例(还有句子对分类,判定句子对是否同义),比如,我们现在想要对“想看黑寡妇”进行情感分类,首先将“想看黑寡妇”转换成BERT模型的输入形式,在句前加[CLS]
符号,句尾加[SEP]
符号,使用subword tokenizer进行分词,将这样处理好的文本输入到BERT模型中,得到句子的向量表征(句子中的每个字均有向量表征),通过BertPooler模块,取[CLS]
的向量表征,经过MLP,tanh操作后得到一个整句的向量表征,再经过分类层得到分类结果。
由于NLP中句子是不定长的,所以可以通过Pooling层将变长的向量转换成特定的size。除了使用BertPooler模块,还可以增加一个Pooling操作,将句子表征转化成句子级别的向量。这里的Pooling操作指:
- Max-Pooling
- Average-Pooling
- Attention-Pooling
分类任务微调的损失函数:假设模型对分类任务的训练样本为
(
x
,
y
)
(x,y)
(x,y),预测类别为
c
c
c的概率为
p
c
p_c
pc,则损失函数为
C
E
(
P
C
,
X
)
=
−
I
(
y
=
c
)
∗
l
o
g
(
p
c
)
CE(P_C,X)=-I(y=c)*log(p_c)
CE(PC,X)=−I(y=c)∗log(pc)
有了损失函数就可以利用梯度的更新做网络的训练。
分类任务微调的原理与一般的网络训练没有区别。微调时,如果不固定参数,所有层的参数会更新。你可以选择固定某些层的参数,比如:embedding层。
五、BERT微调—序列标注任务
序列标注任务指的是对于待标注的一段序列 x = { x 1 , x 2 , . . . , x n } x=\{x_1, x_2,..., x_n\} x={x1,x2,...,xn},我们需要给每个 x i x_i xi预测一个标签 ( t a g ) y i (tag)y_i (tag)yi,标签 ( t a g ) (tag) (tag)集合是 T = { t 1 , t 2 , . . . , t m } T=\{t_1,t_2,...,t_m\} T={t1,t2,...,tm}。在不同的序列标注任务中对应的标签不一样。
- 中文分词任务
定义的标签 ( t a g ) (tag) (tag)集合是 { B e g i n M i d d l e E n d S i n g l e } \{Begin \ Middle\ End\ Single\} {Begin Middle End Single}
eg:“风险基因协同的神经生物学作用”被分词为
风险 基因 协同 的 神经 生物学 作用,转化为序列标注任务为:风/B险/E基/B因/E协/B同/E的/s神/B经/E生/B物/M学/E作/B用/E。 - 命名实体识别:标出句子中的实体;使用BIO标注模式;
定义的标签 ( t a g ) (tag) (tag)集合是实体类型包括 { P E R , O R G } \{PER,ORG\} {PER,ORG}
eg:乔/B-PER布/I-PER斯/I-PER 就/O职/O于/O苹/B-ORG 果/I-ORG 公/I-ORG司/I-ORG - 词性标注(Part-of-speech, POS tagging):标注出词语的词性;使用BIO标注模式
BERT序列标注任务微调方法有两种: - 方法一:每个token的向量表示经过线性层+softmax
具体过程为:将BERT模型格式将句子输入到BERT模型当中,然后给出每个词的向量表征,经过线性层+softmax,然后给出每个词的NER标签
- 方法二:BERT+CRF层
在BERT模型还没有出现以前,解决这个问题的通用方式是’BiLSTM-CRF’模型。CRF层通过学习标签之间转移的模式来规避’B-PER,I-ORG’这样的问题;
六、BERT微调—关系分类任务
关系分类任务就是从非结构化文本中抽取出结构化知识;具体为:区分出头实体与尾实体之间的语义关系,比如:
关系分类任务最直接的应用是构建知识图谱。
BERT关系分类任务微调方法有三种:
- 方法一:将句子输入到BERT模型中,然后分别拿到头实体与尾实体的向量表征(实体多个词的表征经过pooling得到),然后将头尾实体向量拼接,再经过线性层分类
- 方法二:BERT的embedding层中加入关系位置编码。在方法一的基础上加上关系位置编码,告诉BERT模型何处为头实体何处为尾实体。
- 方法三:在句子中加入新定义的字符(unusedtoken),标识出头尾实体位置;
七、BERT微调—样本不均衡问题Focal loss
在自然语言处理应用场景中,经常出现样本不均衡场景的问题,这是因为在自然语言的语料库中,一个单词出现的频率与它在频率表里的排名成反比,即频率越高的单词,出现的次数越多。碰到样本不均衡问题时,需要对训练方法做一定的改变。
- 方法一:重采样
假设 C C C是数据集类别数, n n n是类别 i i i的样本数量,则从类别 i i i中采样一个样本的概率:
instance-balanced sampling:每个样本被等概率的抽到,即
p i = n i ∑ j = 1 C n j p_i=\frac{n_i}{{\sum_{j=1}^Cn_j}} pi=∑j=1Cnjni
Class balanced sampling:每个类别被抽到的概率都相等
p i = 1 ∑ j = 1 C 1 p_i=\frac{1}{{\sum_{j=1}^C1}} pi=∑j=1C11
一般重采样,假设 q ∈ ( 0 , 1 ) q∈(0,1) q∈(0,1),
n i q ∑ j = 1 C n j q \frac{n_i^q}{{\sum_{j=1}^Cn_j^q}} ∑j=1Cnjqniq - 方法二:重加权(re_weighting):以二分类为例
正常的交叉熵损失为:
C E ( P C , X ) = − I ( y = c ) ∗ l o g ( p c ) CE(P_C,X)=-I(y=c)*log(p_c) CE(PC,X)=−I(y=c)∗log(pc)
通过增加一个系数(添加的系数与难度相关),来控制少数类别的样本对总loss的贡献
C E ( P C , X ) = − α ( c ) ∗ I ( y = c ) ∗ l o g ( p c ) CE(P_C,X)=-\alpha(c)*I(y=c)*log(p_c) CE(PC,X)=−α(c)∗I(y=c)∗log(pc)
Focal loss:
C E ( P C , X ) = − ( 1 − p c ) γ I ( y = c ) ∗ l o g ( p c ) CE(P_C,X)=-(1-p_c)^{\gamma}I(y=c)*log(p_c) CE(PC,X)=−(1−pc)γI(y=c)∗log(pc)
Focal loss+类别的重加权:
C E ( P C , X ) = − α ( c ) ( 1 − p c ) γ I ( y = c ) ∗ l o g ( p c ) CE(P_C,X)=-\alpha(c)(1-p_c)^{\gamma}I(y=c)*log(p_c) CE(PC,X)=−α(c)(1−pc)γI(y=c)∗log(pc)
如果对您有帮助,麻烦点赞关注,这真的对我很重要!!!如果需要互关,请评论或者私信!