Transformer模型详细解读
相应的代码实现及解读可见另一篇博客【学习笔记】Transformer代码详细解读
模型架构(Model Architecture)
Transformer的编码端由N个Encoder堆叠而成,解码端由N个Decoder堆叠而成。这N个Encoder的结构是一样的,但是参数不一样,并不共享参数,N个Encoder独立训练,Decoder同理。模型如下图所示。
为方便解读,在本文中,假设 d m o d e l d_{model} dmodel=512, h e a d head head=8,输入序列的长度 s e q _ l e n seq\_len seq_len=16。
编码器(Encoder)
1. 位置编码(Positional encoding)
Question 1:为什么需要位置编码?
Transformer的并行输入,相比RNN,增加了处理速度,但是忽略了一个序列中token之间的位置关系。
实现
d m o d e l d_{model} dmodel=512,即对于任意一个单词,它的位置编码维度为512维。下式表明,在这512个维度中,偶数维度(即2i)使用 s i n sin sin,奇数维度(即2i+1)使用 c o s cos cos,按照如下公式进行计算。其中, p o s pos pos为该单词在整个序列中的绝对位置。
原理
首先,根据PE公式,对于不同的pos,它的位置编码是不同的,这包含了绝对位置信息。
其次,根据下图推导可知,pos+k位置的位置向量可由pos位置和k位置的位置向量线性组合而成,这又包含了相对位置信息。但是,这种相对位置信息会在注意力模块处消失。
2. 点积缩放的多头注意力机制(Scaled Dot-Product Muti-Head Attention)
首先,输入的词向量(经过位置编码以后)通过三个参数矩阵 W Q ( d m o d e l ∗ d k ) W^Q(d_{model}*d_k) WQ(dmodel∗dk), W K ( d m o d e l ∗ d k ) W^K(d_{model}*d_k) WK(dmodel∗dk), W V ( d m o d e l ∗ d v ) W^V(d_{model}*d_v) WV(dmodel∗dv),这三个参数矩阵都是可学习参数,通过线性变换得到Q、K、V三个向量。由于 d m o d e l d_{model} dmodel=512, h e a d head head=8, s e q _ l e n seq\_len seq_len=16。那么,词向量维度为[16,512],由于模型的各模块输入输出相同, d k = d v = d m o d e l / h = 512 / 8 = 64 d_k=d_v=d_{model}/h=512/8=64 dk=dv=dmodel/h=512/8=64,故共有 h h h套参数,每套有三个参数矩阵,该三个参数矩阵的维度为[512,64]。通过输入X与三个参数矩阵做线性变换得到的Q、K、V向量的维度为[16,64]。如图所示。
计算注意力得分
此处要深刻理解:
- Q、K、V的意义 Q ∗ K T Q*K^T Q∗KT的意义,以及之后乘上V的意义。
- 点积缩放的意义。
-
首先,将Q和K做内积,内积(dot-product),是一个向量在另一个向量上投影的长度,结果越大相关性越大。其意义类似于信息检索模型中使用Query计算与Key的相似度。对于任意一个token的Query向量,都将其与所有(包括自身)的Key向量进行内积。
Q ∗ K T Q*K^T Q∗KT的意义:查询矩阵Q的每个token对键矩阵K的每个token的相关程度、注意力权重(未经softmax归一化)。
如下图所示(此处的 d m o d e l d_{model} dmodel=768),结果自然是“我是想吃酸菜鱼”里面每个字对其他字(包括自身)的注意力权重(一个数值)。(这里是网上找的示例图片,这里的768应当是 d m o d e l / h e a d d_{model}/head dmodel/head)。
-
将得到的结果做点积缩放(Scaled Dot-product),即除以 d k \sqrt{d_k} dk。
-
经过softmax进行归一化,得到的注意力得分与V相乘得到最终的结果。
注意力权重 x VALUE矩阵 = 最终结果
- 意义:V是值向量,是从嵌入矩阵中提取的文本信息内容本身,将注意力矩阵和V相乘,是一个加权的过程。注意力权重即查询Q对索引K的关注程度,对关注程度高的,注意力权重大的,应该赋予更大的权重,融合更多的信息;而对于相关性弱的内容,加权以后会得到较少的信息。
- 举例:首先是“我”这个字对“我想吃酸菜鱼”这句话里面每个字的注意力权重,和V中“我想吃酸菜鱼”里面每个字的第一维特征进行相乘再求和,这个过程其实就相当于用每个字的权重对每个字的特征进行加权求和,然后再用“我”这个字对对“我想吃酸菜鱼”这句话里面每个字的注意力权重和V中“我想吃酸菜鱼”里面每个字的第二维特征进行相乘再求和,依次类推,最终也就得到了(L,768)的结果矩阵,和输入保持一致。
- 信息检索、搜索引擎场景举例:你有一个问题Q,然后去搜索引擎里面搜,搜索引擎里面有好多文章,每个文章V有一个能代表其正文内容的标题K,然后搜索引擎用你的问题Q和那些文章V的标题K进行一个匹配,看看相关度(QK —>attention值),然后你想用这些检索到的不同相关度的文章V来表示你的问题,就用这些相关度将检索的文章V做一个加权和,那么你就得到了一个新的Q’,这个Q’融合了相关性强的文章V更多信息,而融合了相关性弱的文章V较少的信息。这就是注意力机制,注意力度不同,重点关注(权值大)与你想要的东西相关性强的部分,稍微关注(权值小)相关性弱的部分。参考自知乎评论
Question 2:为什么要做缩放?
Q和K向量内积的结果容易得到很大的值,过大的x带来过大的幅度,根据softmax公式,一个值的结果与所有值之和相关,会将梯度带向平缓的地方,导致梯度消失现象。缩放可以确保softmax的梯度稳定性。解释
多头注意力
实际模型中,往往会使用多个头来捕捉输入数据中的不同模式信息,类似于多核卷积。例如,一个头可能捕获句子的句法特征,一个头可能捕获句子的语法特征。
具体而言,通过多套参数,即多套不同的参数矩阵 W Q W^Q WQ, W K W^K WK, W V W^V WV产生多个Query,Key,Value。将多个头加权计算注意力后的输出结果进行拼接(Concat),然后通过线性层 W o W^o Wo进行一次线性变换(Linear),得到最终输出,最终输出的维度和初始词向量输入的维度相同。
3. 残差和层归一化(Add&LayerNorm)
残差连接(Residual Connection):将输入向量(经过位置编码后的)与注意力模块输出的向量进行对位相加,详见ResNet论文。残差机制可以缓解梯度消失,解决网络退化问题。
层归一化(Layer Normalization):对单个样本的所有维度特征做归一化。
Question 3:为什么用LayerNorm而不用BatchNorm?
BatchNormalization是对所有样本的同一特征进行归一化,这在NLP任务中是不适用的,因为序列数据是不定长的,样本长度变化大时,每次小批量计算均值和方差抖动较大。并且,BN是在训练结束后存一个全局的均值和方差,预测时用。在NLP任务中,当预测长度和训练长度差别较大时,BN效果较差。LN是对每个样本计算的均值和方差,则不需要保存一个全局的均值和方差。此外,对序列数据中的一个样本的所有token做归一化,比对多个样本的多个token做归一化更合理一些,因为每个样本的语义信息是独立的。
BatchNorm适用于CV,而LayerNorm适用于NLP,这是由两个任务的本质差异决定的,视觉的特征是客观存在的特征,而语义特征更多是由上下文语义决定的一种统计特征,因此他们的标准化方法也会有所不同。
4. 前馈神经网络(Position-wise Feed-Forward Networks)
网络结构
-
该结构即:前面部分的输出通过两个Feed-Forward(由两个全连接层构成的网络:Linear(ReLU(Linear(Zi)))),其实就是一个单隐藏层的MLP,中间隐藏层的维度是 d f f = 2048 d_{ff}=2048 dff=2048,输入和输出层都是 d m o d e l = 512 d_{model}=512 dmodel=512。
-
在两个线性层之间要过一个ReLu激活函数;
-
然后接一个残差连接,即Zi和Feed-Forward的输出进行Add对位相加。最后把相加的结果进行一次LN标准化。
Question 4:Position-wise是什么意思?
对每个点独立做,即对序列中的每个token独立过同一个MLP,即作用在输入的最后一个维度上。过线性层时每个token的变换参数是一样的。因为在前面的注意力模块,序列信息已经汇聚完成,所以此处可以对每个位置单独做,把提取到的表征向量非线性化,转换到另一个语义空间中,这一块其实和RNN是一样的。
解码器(Decoder)
1. 掩码多头注意力机制(Masked Muti-Head Attention)
Question 5:Decoder为什么要进行掩码?
在预测(推理)时,当前词是看不到未来时刻单词的,即是自回归的。但是在训练时,是Teacher Forcing的,即解码器的输入是标注的答案文本(ground truth)。因此,在训练时,需要将当前词之后的词全部Mask,使当前词关注不到未来的词,以保证和预测时一致。
实现
Mask 操作是在 Self-Attention 的 Softmax 之前, Q ∗ K T Q*K^T Q∗KT之后,对注意力权重矩阵使用的。
Question 6:为什么注意力矩阵行上的Pad不用mask?
因为没有必要,但是把它mask上也可以。因为行上的pad是Query,本身就是没有意义的,它关注谁都没用。而列上的Mask是Key的,PaddingMask的目的是不让我们的Query关注到Key上的Pad。另一方面,Encoder的Padding Mask,不进行行上的Mask,因为在后面的Cross-Attention模块,Encoder的输出会作为Key和Value,此时,它的Pad部分(也就是前面说的行)在这个模块会被Mask,是一个道理的。因此,要被Mask的应该是Key,Query是不用被Mask的,因为用不上,Decoder的Masked Muti-Head-Attention的行不做Mask也是这个道理吧?->Decoder不Mask行上的Pad,是因为最后会解码出pad吗?还是可以把句子截断,丢掉pad,根据target_len?这一部分的详细解释可见下列博客。为什么注意力矩阵行上的Pad不用mask
2. 交叉多头注意力机制(Cross Muti-Head Attention)
根据Encoder的输出计算得到K、V,根据上一个Decoder block的输出计算得到Q,然后做注意力计算。这样做的好处是在 Decoder的时候,每一位单词都可以利用到Encoder所有单词的信息 (这些信息无需 Mask)。这样做的意义在于,可以根据当前要解码的单词,去编码端查询应该关注哪些信息。
3. 输出概率分布解码生成文本
最终得到上述的编码向量后,将其经过一个线性层映射到词表大小,解码预测过程相当于一个词表大小级别分类。然后过Softmax对概率进行归一化。
Softmax 根据输出矩阵的每一行预测下一个单词: