NLP预训练语言模型(三):逐步解析Transformer结构
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NLP预训练语言模型(三):逐步解析Transformer结构相关的知识,希望对你有一定的参考价值。
参考技术A Transformer是近两三年非常火的一种适用于NLP领域的一种模型,本质上是Encoder-Decoder结构,所以多应用在机器翻译(输入一个句子输出一个句子)、语音识别(输入语音输出文字)、问答系统等领域。本文基于Attention is all you need这篇论文,讲解Transformer的结构,涉及到的图片均为论文中或经典图片,参数值均来自论文,具体问题可以具体调整超参数。Transformer的组成模块分为: Attention (包括multi-head self-Attention & context-Attention), Normalization (使用的是layer Norm,区别于Batch Norm), mask (padding mask & sequence mask), positional encoding , feed forword network (FFN)。
Transformer的总架构如下图所示:
这是典型的Transformer结构,简单来说,Transformer = 预训练(input) + Encoder*N + 预训练(output) + Decoder*N+output。
模型的运行步骤为:
① 对Input做Embedding,可以使用Word2Vec等工具,维度为512维,Embedding过后结合positional encoding,它记录了输入单词的位置信息。
② 预处理后的输入向量经过多头Attention层处理,加入残差、规则化,数据给到FFN(全连接层),再加入残差、规则化。如此反复,经过6个这样的Encoder(即Nx=6x),编码部分结束。
③ 编码部分的第一个Decoder的第一个Attention接受的是来自Outputs的信息,其余的均接受来自Encoder和上一层Decoder的信息。最终的output的串行生成的,每生成一个,就放到Decoder最下面的outputs座位Decoder的输入。
④ Decoder也是6个,最终的输出要经过线性层和Softmax得到最终输出。
要注意的是,Encoder和Decoder的结构是相同的,但不共享权重;在Encoder部分,每个单词在Attention层的路径具有依赖关系,串行执行,在FFN层不具有依赖关系,并行执行。
在这个结构中,存在这样几个Attention,有:self-attention & context attention & scaled dot-product attention & multi-headed attention。要说明的是scaled dot-product attention和multi-headed attention是两种attention的计算方法,后面会介绍,前两个Attention均使用的是这两种计算方法。
这种Attention的计算公式为:
以第一个Encoder为例对流程解释如下:
① 为Encoder的每个单词创建如下的三个向量:Query vector , Key vector , Value vector。这三个向量由输入的Embedding乘以三个向量矩阵得到。要注意的是,Embedding向量维度为512,Q K V向量维度是64。
② 计算Score:对于每个词,计算它自身的 与所有的 的乘积。
③ 计算Attention:按上面Attention的公式,将Score除以一个定值(这个操作称为“scaled”),进行Softmax变换,使所有Score之和为1。最后乘以对应位置的 ,得到该单词的Attention。
这就是scaled dot-product attention这种机制的计算方法,Transformer架构中的两种Attention都使用的是这种计算方法,不同的是二者的Q K V的来源有些差异。
注 : 为什么Softmax中要除以一个根号? 论文中给出的原因 是本来 和 都是均值为0、方差为1的变量,假设二者分布相互独立,他们乘积的分布就是均值为0、方差为 ,除以根号使得Softmax内的值保持均值为0、方差为1利于梯度计算。如果不加根号会使得计算收敛很慢,因为Softmax中的值处于梯度消失区。
进一步思考: 为什么很多Attention中没有Scaled这一步? Attention分为两种,前面那种是乘法,还有加法的一种: 。实验表明,加法虽然看起来简单但计算起来并没有快多少(tanh相当于一个完整的隐层),在维度较高时确实更好,但如果加上Scaled也差不多。Transformer中选择乘法是为了计算更快,维度大的话就加上Scaled。
多头注意力机制也是一种处理的技巧,主要提高了Attention层的性能。因为上面介绍的self-attention虽然包含了其余位置的编码,但主导的还是自身位置的单词,而有时我们更需要关注其他位置,比如机器翻译中的代词具体指代哪个主语。
多头注意力机制是把Q K V三个矩阵通过h个线性变换投影,然后进行h次self-attention的计算,最后再把h个计算结果拼接起来。
在Encoder的self-attention中,Q K V均是上一层Encoder的输出,对于第一个Encoder来说,他们就是输入的Embedding与positional encoding之和。
在Decoder的self-attention中,Q K V也是上一层Decoder的输出,对于第一个Decoder来说,他们是输入的Embedding与positional encoding之和。要注意的是,这部分我们不希望获取到后面时刻的数据,只想考虑已经预测出来的信息,所以要进行sequence masking(后面讲到)。
在Encoder-Decoder attention(即context attention)中,Q是Decoder上一层的输出,K V是Encoder的输出。
Transformer中使用的是LN,并非BN(Batch Normalization)。什么是Norm规范化,一般地,可以用下面公式来表达:
公式一为规范化处理前,公式二为处理后。规范化是对数据分布的调整,比如本身数据是正态分布,调整后的数据分布就是标准正态分布,相当于调整了均值和方差。这样做的意义一是让激活值落入激活函数敏感区间,梯度更新变大,训练加快,二是消除极端值,提升训练稳定性。
Transformer使用的是LN,而不是BN。首先看二者的区别如图:
LN是对每个样本自身进行规范化,BN是对一个批次的数据在同一维度上规范化,是跨样本的。在CNN任务中,BatchSize较大,并且训练时全局记录了样本均值和方差,适用于BN。而时序问题中,对每个神经元进行统计是不现实的。LN的限制相对来说就小很多,即时BatchSize=1也无妨。
mask分为两种,一是padding mask,二是sequence mask,这两种在Transformer中出现的位置不同:padding mask在所有scaled dot-product attention中均出现,sequence mask仅在decoder的self-attention中出现。
由于每个batch的输入序列的长度不同,padding mask被用来对齐序列长度,简单来说就是短序列向长序列对齐,对齐的方法就是补0。补充上的地方是没有意义的,那么Attention就不应该给以关注。实际上,我们并不是直接在相应位置上补充0,而是补充-inf(负无穷),这样在Softmax之后,这些位置的概率就接近0了。
在处理过程中,padding mask是一个bool张量,false的地方就是补0的地方。
前面提到,sequence mask的作用是不让decoder看到当前时刻以后的信息,所以要把后面那部分信息完全遮盖住。具体的做法是,产生一个上三角矩阵,上三角的值均为1,下三角和对角线均为0。
在decoder的self-attention部分,sequence mask 和 padding mask同时作用,二者相加作为mask。
RNN处理序列问题是天然有序的,而Transformer消除了这种时序上的依赖。以机器翻译为例,输出要是一个完整的合理的句子,就需要对输入数据处理时加入位置信息,否则可能输出结果的每个字是对的,但组成不了一句话。positional encoding是对输入信息的位置进行编码,再和输入的Embedding相加。
positional encoding使用的是正余弦编码:
在偶数位置,使用公式一正弦编码,奇数位置使用公式二余弦编码。由于正余弦函数的特性,这种编码既是绝对位置编码,也包含了相对位置编码的信息。
相对位置编码信息主要依赖于三角函数和角公式:
FFN 是一个全连接网络,顺序上先线性变换,再ReLU非线性变换,再线性变换,公式如下:
参考文献:
[整理] 聊聊 Transformer
碎碎念:Transformer的细枝末节
图解什么是 Transformer
文本分类实战(八)—— Transformer模型
深度学习:transformer模型
梳理NLP预训练模型
在2017年之前,语言模型都是通过RNN,LSTM来建模,这样虽然可以学习上下文之间的关系,但是无法并行化,给模型的训练和推理带来了困难,因此有人提出了一种完全基于attention来对语言建模的模型,叫做transformer。transformer摆脱了NLP任务对于RNN,LSTM的依赖,使用了self-attention的方式对上下文进行建模,提高了训练和推理的速度。
1、Transformer
<1> Inputs是经过padding的输入数据,大小是[batch size, max seq length]。
<2> 初始化embedding matrix,通过embedding lookup将Inputs映射成token embedding,大小是[batch size, max seq length, embedding size],然后乘以embedding size的开方。
<3> 通过sin和cos函数创建positional encoding,表示一个token的绝对位置信息,并加入到token embedding中,然后dropout。
<4> multi-head attention
<4.1> 输入token embedding,通过Dense生成Q,K,V,大小是[batch size, max seq length, embedding size],然后按第2维split成num heads份并按第0维concat,生成新的Q,K,V,大小是[num heads*batch size, max seq length, embedding size/num heads],完成multi-head的操作。
<4.2> 将K的第1维和第2维进行转置,然后Q和转置后的K的进行点积,结果的大小是[num heads*batch size, max seq length, max seq length]。
<4.3> 将<4.2>的结果除以hidden size的开方(在transformer中,hidden size=embedding size),完成scale的操作。
<4.4> 将<4.3>中padding的点积结果置成一个很小的数(-2^32+1),完成mask操作,后续softmax对padding的结果就可以忽略不计了。
<4.5> 将经过mask的结果进行softmax操作。
<4.6> 将softmax的结果和V进行点积,得到attention的结果,大小是[num heads*batch size, max seq length, hidden size/num heads]。
<4.7> 将attention的结果按第0维split成num heads份并按第2维concat,生成multi-head attention的结果,大小是[batch size, max seq length, hidden size]。Figure 2上concat之后还有一个linear的操作,但是代码里并没有。
<5> 将token embedding和multi-head attention的结果相加,并进行Layer Normalization。
<6> 将<5>的结果经过2层Dense,其中第1层的activation=relu,第2层activation=None。
<7> 功能和<5>一样。
<8> Outputs是经过padding的输出数据,与Inputs不同的是,Outputs的需要在序列前面加上一个起始符号”<s>”,用来表示序列生成的开始,而Inputs不需要。
<9> 功能和<2>一样。
<10> 功能和<3>一样。
<11> 功能和<4>类似,唯一不同的一点在于mask,<11>中的mask不仅将padding的点积结果置成一个很小的数,而且将当前token与之后的token的点积结果也置成一个很小的数。
<12> 功能和<5>一样。
<13> 功能和<4>类似,唯一不同的一点在于Q,K,V的输入,<13>的Q的输入来自于Outputs 的token embedding,<13>的K,V来自于<7>的结果。
<14> 功能和<5>一样。
<15> 功能和<6>一样。
<16> 功能和<7>一样,结果的大小是[batch size, max seq length, hidden size]。
<17> 将<16>的结果的后2维和embedding matrix的转置进行点积,生成的结果的大小是[batch size, max seq length, vocab size]。
<18> 将<17>的结果进行softmax操作,生成的结果就表示当前时刻预测的下一个token在vocab上的概率分布。
<19> 计算<18>得到的下一个token在vocab上的概率分布和真实的下一个token的one-hot形式的cross entropy,然后sum非padding的token的cross entropy当作loss,利用adam进行训练。
more : multi-head相当于把一个大空间划分成多个互斥的小空间,然后在小空间内分别计算attention,虽然单个小空间的attention计算结果没有大空间计算得精确,但是多个小空间并行然后concat有助于网络捕捉到更丰富的信息。
当模型变得越来越大,样本数越来越多的时候,self-attention无论是并行化带来的训练提速,还是在长距离上的建模,都是要比传统的RNN,LSTM好很多。transformer现在已经各种具有代表性的nlp预训练模型的基础,bert系列使用了transformer的encoder,gpt系列transformer的decoder。在推荐领域,transformer的multi-head attention也应用得很广泛。
2、BERT
在bert之前,将预训练的embedding应用到下游任务的方式大致可以分为2种,一种是feature-based,例如ELMo这种将经过预训练的embedding作为特征引入到下游任务的网络中;一种是fine-tuning,例如GPT这种将下游任务接到预训练模型上,然后一起训练。然而这2种方式都会面临同一个问题,就是无法直接学习到上下文信息,像ELMo只是分别学习上文和下文信息,然后concat起来表示上下文信息,抑或是GPT只能学习上文信息。因此,作者提出一种基于transformer encoder的预训练模型,可以直接学习到上下文信息,叫做bert。bert使用了12个transformer encoder block,在13G的数据上进行了预训练,可谓是nlp领域大力出奇迹的代表。在整个流程上与transformer encoder没有大的差别,只是在embedding,multi-head attention,loss上有所差别。
bert和transformer在embedding上的差异主要有3点:
<1> transformer的embedding由2部分构成,一个是token embedding,通过embedding matrix lookup到token_ids上生成表示token的向量;一个是position embedding,是通过sin和cos函数创建的定值向量。而bert的embedding由3部分构成,第一个同样是token embedding,通过embedding matrix lookup到token_ids上生成表示token的向量;第二个是segment embedding,用来表达当前token是来自于第一个segment,还是第二个segment,因此segment vocab size是2;第三个是position embedding,与transformer不同的是,bert创建了一个position embedding matrix,通过position embedding matrix lookup到token_ids的位置上生成表示token位置的位置向量。
<2> transformer在embedding之后跟了一个dropout,但是bert在embedding之后先跟了一个layer normalization,再跟了一个dropout。
<3> bert在token序列之前加了一个特定的token“[cls]”,这个token对应的向量后续会用在分类任务上;如果是句子对的任务,那么两个句子间使用特定的token“[seq]”来分割。
bert和transformer在multi-head attention上的差异:
<1> transformer在<4.7>会concat<4.6>的attention的结果。而bert不仅会concat<4.6>的attention的结果,还会把前N-1个encoder block中attention的结果都concat进来。
<2> transformer在<4.7>之后没有linear的操作,而bert在transformer的<4.7>之后有一个linear的操作。
bert和transformer在loss上的差异:bert预训练的loss由2部分构成,一部分是NSP的loss,就是token“[cls]”经过1层Dense,然后接一个二分类的loss,其中0表示segment B是segment A的下一句,1表示segment A和segment B来自2篇不同的文本;另一部分是MLM的loss,segment中每个token都有15%的概率被mask,而被mask的token有80%的概率用“<mask>”表示,有10%的概率随机替换成某一个token,有10%的概率保留原来的token,被mask的token经过encoder后乘以embedding matrix的转置会生成在vocab上的分布,然后计算分布和真实的token的one-hot形式的cross entropy,最后sum起来当作loss。这两部分loss相加起来当作total loss,利用adam进行训练。bert fine-tune的loss会根据任务性质来设计,例如分类任务中就是token“[cls]”经过1层Dense,然后接了一个二分类的loss;例如问题回答任务中会在paragraph上的token中预测一个起始位置,一个终止位置,然后以起始位置和终止位置的预测分布和真实分布为基础设计loss;例如序列标注,预测每一个token的词性,然后以每一个token在词性的预测分布和真实分布为基础设计loss。 bert在encoder之后,在计算NSP和MLM的loss之前,分别对NSP和MLM的输入加了一个Dense操作,这部分参数只对预训练有用,对fine-tune没用。而transformer在decoder之后就直接计算loss了,中间没有Dense操作。
bert的框架决定了这个模型适合解决自然语言理解的问题,因为没有解码的过程,所以bert不适合解决自然语言生成的问题。
增大预训练模型的大小通常能够提高预训练模型的推理能力,但是当预训练模型增大到一定程度之后,会碰到GPU/TPU memory的限制。因此,albert作者在bert中加入了2项减少参数的技术,能够缩小bert的大小,并且修改了bert NSP的loss,在和bert有相同参数量的前提之下,有更强的推理能力。
3、albert
在bert以及诸多bert的改进版中,embedding size都是等于hidden size的,这不一定是最优的。因为bert的token embedding是上下文无关的,而经过multi-head attention+ffn后的hidden embedding是上下文相关的,bert预训练的目的是提供更准确的hidden embedding,而不是token embedding,因此token embedding没有必要和hidden embedding一样大。albert将token embedding进行了分解,首先降低embedding size的大小,然后用一个Dense操作将低维的token embedding映射回hidden size的大小。bert的embedding size=hidden size,因此词向量的参数量是vocab size * hidden size,进行分解后的参数量是vocab size * embedding size + embedding size * hidden size,只要embedding size << hidden size,就能起到减少参数的效果。
bert的12层transformer encoder block是串行在一起的,每个block虽然长得一模一样,但是参数是不共享的。albert将transformer encoder block进行了参数共享,这样可以极大地减少整个模型的参数量。
在auto-encoder的loss之外,bert使用了NSP的loss,用来提高bert在句对关系推理任务上的推理能力。而albert放弃了NSP的loss,使用了SOP的loss。NSP的loss是判断segment A和segment B之间的关系,其中0表示segment B是segment A的下一句,1表示segment A和segment B来自2篇不同的文本。SOP的loss是判断segment A和segment B的的顺序关系,0表示segment B是segment A的下一句,1表示segment A是segment B的下一句。
albert使用了2项参数减少的技术,但是2项技术对于参数减少的贡献是不一样的,第1项是词向量矩阵的分解,当embedding size从768降到64时,可以节省21M的参数量,但是模型的推理能力也会随之下降。第2项是multi-head attention+ffn的参数共享,在embedding size=128时,可以节省77M的参数量,模型的推理能力同样会随之下降。虽然参数减少会导致了模型推理能力的下降,但是可以通过增大模型使得参数量变回和bert一个量级,这时模型的推理能力就超过了bert。
在albert之前,很多bert的改进版都对NSP的loss提出了质疑。structbert在NSP的loss上进行了修改,有1/3的概率是segment B是segment A的下一句,有1/3的概率是segment A是segment B的下一句,有1/3的概率是segment A和segment B来自2篇不同的文本。roberta则是直接放弃了NSP的loss,修改了样本的构造方式,将输入2个segment修改为从一个文本中连续sample句子直到塞满512的长度。当到达文本的末尾且未塞满512的长度时,先增加一个“[sep]”,再从另一个文本接着sample,直到塞满512的长度。
albert在structbert的基础之上又抛弃了segment A和segment B来自2篇不同的文本的做法,只剩下1/2的概率是segment B是segment A的下一句,1/2的概率是segment A是segment B的下一句。论文中给出了这么做的解释,NSP的loss包含了2部分功能:topic prediction和coherence prediction,其中topic prediction要比coherence prediction更容易学习,而MLM的loss也包含了topic prediction的功能,因此bert难以学到coherence prediction的能力。albert的SOP loss抛弃了segment A和segment B来自2篇不同的文本的做法,让loss更关注于coherence prediction,这样就能提高模型在句对关系推理上的能力。
albert虽然减少参数量,但是并不会减少推理时间,推理的过程只不过是从串行计算12个transformer encoder block变成了循环计算transformer encoder block 12次。albert最大的贡献在于使模型具备了比原始的bert更强的成长性,在模型变向更大的时候,推理能力还能够得到提高。
4、GPT、structbert、xlnet
gpt在bert之前就发表了,使用了transformer decoder作为预训练的框架。在看到了decoder只能get上文信息,不能get下文信息的缺点之后,bert改用了transformer encoder作为预训练的框架,能够同时get上下文信息,获得成功。
structbert的创新点主要在loss上,除了MLM的loss外,还有一个重构token顺序的loss和一个判断2个segment关系的loss。重构token顺序的loss是以一定的概率挑选segment中的token三元组,然后随机打乱顺序,最后经过encoder之后能够纠正被打乱顺序的token三元组的顺序。判断2个segment关系的loss是1/3的概率是segment B是segment A的下一句,有1/3的概率是segment A是segment B的下一句,有1/3的概率是segment A和segment B来自2篇不同的文本,通过“[cls]”预测样本属于这3种的某一种。
在xlnet使用126G的数据登顶GLUE之后不久,roberta使用160G的数据又打败了xlnet。roberta的创新点主要有4点:第1点是动态mask,之前bert使用的是静态mask,就是数据预处理的时候完成mask操作,之后训练的时候同一个样本都是相同的mask结果,动态mask就是在训练的时候每输入一个样本都要重新mask,动态mask相比静态mask有更多不同mask结果的数据用于训练,效果很好。第2点是样本的构造方式,roberta放弃了NSP的loss,修改了样本的构造方式,将输入2个segment修改为从一个文本中连续sample句子直到塞满512的长度。当到达文本的末尾且未塞满512的长度时,先增加一个“[sep]”,再从另一个文本接着sample,直到塞满512的长度。第3点是增大了batch size,在训练相同数据量的前提之下,增大batch size能够提高模型的推理能力。第4点是使用了subword的分词方法,类比于中文的字,相比于full word的分词方法,subword的分词方法使得词表的大小从30k变成了50k,虽然实验效果上subword的分词方法比full word差,但是作者坚信subword具备了理论优越性,今后肯定会比full word好。
本文来自某大神的文章,仅个人学习理解用,侵删~
以上是关于NLP预训练语言模型(三):逐步解析Transformer结构的主要内容,如果未能解决你的问题,请参考以下文章
自然语言处理(NLP)——语言模型预训练方法(ELMoGPT和BERT)
最强 NLP 预训练模型库 PyTorch-Transformers 正式开源:支持 6 个预训练框架,27 个预训练模型