以机器翻译任务为例子,训练数据是法语句子“Je suis etudiant”和翻译成英文后的句子“I am a student”。
Inputs是法语句子“
J
e
{Je }
Je
s
u
i
s
{suis}
suis
e
t
u
d
i
a
n
t
{etudiant}
etudiant”,句子中的单词“Je”经过word embedding转换为嵌入向量
x
1
{x_1}
x1,再加上positional embedding(位置编码向量)得到第一个Encoder block的输入向量
x
1
{x_1}
x1.
句子中的每个单词都是自身嵌入向量+位置编码向量得到最终的Encoder端输入embedding.
每个单词的embedding 向量
x
i
{x_i}
xi 进入self-attention,得到向量
z
i
{z_i}
zi,
z
i
{z_i}
zi再传入前馈神经网络。
对于RNN来说,在生成it_单词的向量
z
i
{z_i}
zi时,只能考虑到it_前面的那些单词。但是Transformer在Encoder端使用self-attention,可以实现在生成每个单词对应的向量
z
i
{z_i}
zi的时候,考虑到当前句子中所有单词和它之间的关系。
这样向量
z
i
{z_i}
zi中就包含了上下文信息。
以上面的图为例,it_单词对应的向量
z
i
{z_i}
zi包含了句子中所有其它单词对它的影响程度(联系紧密程度),也就是在it_单词对应的embedding向量中嵌入了上下文信息。
初始的文本输入通过**word embedding转为嵌入向量+positional embedding(位置编码)**输入到第一个Encoder block,此时单词对应的embedding向量x i并没有包含整个句子的上下文信息,通过self-attention生成的向量z i就包含了整个句子的上下文信息,即句子中哪些单词对它更“重要”。
现在来看self-attention中每一步是怎么计算得到最后的输出向量
z
i
{z_i}
zi
第一步:生成Q K V,辅助计算注意力机制
有三个权重矩阵
W
Q
{W^Q}
WQ 、
W
K
{W^K}
WK 、
W
V
{W^V}
WV ,它们的初始值通常是通过一种随机初始化的方法来得到的,随着训练的进行,模型通过反向传播算法根据损失函数来调整权重矩阵的值,从而逐步优化模型参数。
如上图所示,对于第一个单词“Thinking”进行self-attention计算,查询向量q1是x1*权重矩阵
W
Q
{W^Q}
WQ 得到的,键向量k1是
x
1
{x_1}
x1 * 权重矩阵
W
K
{W^K}
WK 得到的,值向量
v
1
{v_1}
v1是
x
1
{x_1}
x1 * 权重矩阵
W
V
{W^V}
WV 得到的。
对于句子中的每个单词,都要进行self-attention计算,得到向量
z
i
{z_i}
zi。
对于单词“Thinking”,进行self-attention计算(即QKV操作):
第二步:单词“Thinking”对应的embedding是
x
1
{x_1}
x1,Query向量是
q
1
{q_1}
q1,利用
q
1
{q_1}
q1计算当前句子中所有单词的Score,
q
1
∗
k
1
{q_1*k_1}
q1∗k1得到第一个单词的分数,
q
1
∗
k
2
{q_1*k_2}
q1∗k2得到第二个单词的分数……,这个分数就表明在生成向量
z
1
{z_1}
z1的时候要对整个句子中每个单词关注多少;
第三步:把得到的分数除以维度的平方根(这步是为了梯度更加稳定,有利于训练);
第四步:把上一步结果通过softmax函数转换为[0,1]区间内的概率分布值,目的是让差异变大,也就说当前单词“Thinking”和句子中哪些单词联系密切对应的softmax值就越大,联系越弱对应的softmax值就越小;
第五步:句子中每个单词的Value向量乘以对应的softmax分数,就是“Thinking”对应的值向量
v
1
∗
0.88
{v_1*0.88}
v1∗0.88,“Machines”对应的值向量
v
2
∗
0.12
{v_2*0.12}
v2∗0.12;
第六步:对加权后的值向量求和,即对于单词“Thinking”把整个句子中所有单词加权后的值向量求和得到
z
1
{z_1}
z1,
z
1
{z_1}
z1就是“Thinking”的embedding嵌入
x
1
{x_1}
x1在self-attention层该位置的输出。
这样,单词“Thinking”现在对应的嵌入向量
z
1
{z_1}
z1就包含了该单词在整个句子中的上下文信息,这个单词和句子中其他单词之间的关系紧密程度。
用到的核心公式是:
这样,self-attention计算就完成了,得到的
z
i
{z_i}
zi向量可以传递给前馈神经网络,
上面的过程是一个单词如何进行self-attention计算。
在实际模型中,为了加快计算效率,self-attention机制是通过矩阵的计算实现的:
第一步,对整个句子生成Q K V向量
假设句子中是2个单词,
X
{X}
X矩阵的维度就是2*(一个embedding的维度),前面的一个14的小方格代表一个单词,这个图中的24小方格就是代表两个单词。
Q K V列上的维度是64,这是Transformer中设定的维度。
Q是当前句子的查询向量,K是当前句子的键向量,V是当前句子的值向量矩阵;
最后得到self-attention层计算得到的结果矩阵
Z
{Z}
Z。
前面讲的一个单词经过self-attention得到向量z,可以包含该单词在整个句子中的上下文信息,包含该单词和其他单词之间联系的紧密程度。
那么这里利用矩阵经过self-attention得到矩阵
Z
{Z}
Z,矩阵Z中每个向量就对应了每个单词经过self-attention得到的输出
z
i
{z_i}
zi向量。
在Transformer中采用的是多头注意力机制(multi-head attention),
多头注意力机制可以理解为使用多种不同的Q K V,计算得到不同的self-attention结果。
为什么使用多头注意力机制?论文中给出的原因是:将模型分为多个头,形成多个子空间,让模型去关注不同方面的子信息,最后再将各个方面的信息综合起来。
每个头唯一的不同就是使用的权重矩阵WQ WK WV不相同,第0个头使用的权重矩阵是
W
0
Q
{W_0^Q}
W0Q
W
0
K
{W_0^K}
W0K
W
0
V
{W_0^V}
W0V ,第1个头使用的权重矩阵是
W
1
Q
{W_1^Q}
W1Q
W
1
K
{W_1^K}
W1K
W
1
V
{W_1^V}
W1V ……
Transformer中使用的是8个注意力头,会得到8个注意力计算的结果矩阵
Z
0
{Z_0}
Z0 …
Z
7
{Z_7}
Z7,
注意,这里得到的8个结果矩阵都是为了计算一个句子的自注意力,
换言之,如果是计算一个单词的自注意力,这里的8个结果向量都是为了计算这一个单词的自注意力。
但是对于一个单词来说,前馈神经网络只接收self-attention该位置上的一个输出作为输出;对于一个句子来说,前馈神经网络只接收一个向量矩阵作为输入。也就是说,对于一个单词embedding向量
x
i
{x_i}
xi来说,虽然经过多头注意力机制会得到8个向量
z
0
{z_0}
z0…
z
7
{z_7}
z7 ,但需要把这8个向量弄成一个,作为给前馈神经网络的输入。
8个
Z
{Z}
Z矩阵合并成一个矩阵Z的方法如上图所示:
需要把
Z
0
{Z_0}
Z0 …
Z
7
{Z_7}
Z7这8个矩阵进行合并:先进行concate连接操作,再乘上权重矩阵得到最后的输出矩阵
Z
{Z}
Z。
把
Z
{Z}
Z输入到前馈神经网络中,进行后续的计算。
整体对于一个句子的多头注意力机制过程如下图:
第一个编码器的输入是嵌入向量组成的矩阵
X
{X}
X,后边编码器的输入(矩阵
R
{R}
R)是前一个编码器的输出,输出的矩阵
Z
{Z}
Z和
X
{X}
X、
R
{R}
R的维度相同,把矩阵Z作为后面前馈神经网络的输入。
每个注意力头关注到的信息有什么不一样?
当只有one-head的时候,it_主要关注animal_;当有two-heads的时候,it_主要关注animal_和tire;当有all-heads的时候,比较难从图上进行解释,也反映了神经网络的可解释性比较差。
前面展示的是输入一句话,但通常在训练过程中,都是采用batch的方式一次计算多句话,出现问题:不能保证每句话的长度都相同。解决办法:padding操作
当只输入一句话的时候,X的维度是2维的;当输入一个batch(多句话)的时候,X的维度是3维的,
每一行中灰色的部分就是padding填充的,
embedding_dimension设定的是512,X用图形化表示应该是3维,阴影的部分就代表维度512,想象一下相当于是多个二维的面叠起来构成立体的三维。
padding操作添加在self-attention部分,需要保证padding的操作不会影响softmax计算,所以灰色的部分填充负无穷,这样softmax计算结果是0,不会有影响。
在进行padding操作的时候,先得到padding 矩阵,0表示不需要填充,1表示需要填充,1乘上负无穷得到后面的矩阵,把后面这个矩阵输入到softmax中,得到score分数。
注意:padding操作不仅仅存在于编码器,只要使用batch进行计算,都需要用到。
回顾一下Transformer整体结构图,可以看到其中绿色的方块“Add&Norm”,
通过上面的结构图可以看到,Add操作就是把“self-attention的计算结果向量矩阵
Z
{Z}
Z + 输入的embedding向量矩阵
X
{X}
X”;
类似RestNet网络中的残差连接,起到特征增强的作用。
使用的是NLP中常用的layer norm,layer norm就是对每个样本都计算均值和方差,再对每个特征归一。
看上面的图,Self-attention层输出的向量
z
{z}
z,构成矩阵
Z
{Z}
Z 对(
X
{X}
X+
Z
{Z}
Z)进行Layer Norm层归一化操作,得到新矩阵,再把矩阵中的向量输入前馈神经网络。
这里的前馈神经网络就是一个简单的MLP(多层感知机),包括两层Linear和一个ReLU激活函数。
解码过程如上图所示,每个time step(时间步)会输出一个解码后的单词。
传统的seq to seq模型的解码器部分使用的是RNN模型,
对于RNN模型,在训练过程中,输入t时刻的词,模型看不到未来的词,只有当模型训练结束,模型才能看到t+1时刻的词。
因为RNN是串联的,所有RNN有上面的特性。但是Transformer是并行计算的结构,所以需要添加mask操作。
再来复习下Transformer的整体结构,
上图是Transformer训练过程的结构图,其中Decoder端的输入Outputs就是翻译的句子结果,比如翻译的句子对是,Outputs就是“我爱中国”,经过word embedding得到嵌入向量,再加上positional embedding,作为第一个Decoder block的输入,单个词对应的embedding向量和权重矩阵
W
Q
、
W
K
、
W
V
{W^Q、W^K、W^V}
WQ、WK、WV相乘得到查询向量
q
{q}
q,键向量
k
{k}
k,值向量
v
{v}
v。当前单词的q向量和句子中每个单词的k向量相乘得到每个单词的score。(即进行QKV计算)
对整个句子操作得到矩阵Scaled Scores,为了让当前时刻不看到后面的句子,采用mask操作,就是让Scaled Scores矩阵和Look-Ahead Mask相加得到最终的Masked Scores矩阵,就是把方格中对应的灰色部分数据填成负无穷,负无穷经过softmax变成0,。
就是给了解码端“我”,让它预测下一个时间步的输出“爱”,这个时候不能让模型看到“我”后面的句子“爱中国”,因为本来就是让模型去预测“我”后面的下一个字是什么,如果不进行mask操作,相当于模型已经知道“我”后面的信息是“爱中国”,这样再预测就没有意义了。
相当于 已经告诉了模型问题的答案,再让模型去学习怎么回答就没有意义了。
Decoder端中第一个注意力机制是Masked Multi-Head Attention,
Decoder端中第二个注意力机制Multi-Head Attention,
K
{K}
K 和
V
{V}
V来自Encoder的输出,
Q
{Q}
Q来自上一层Masked Multi-Head Attention的输出。因为Decoder端中第二个注意力机制的
K
{K}
K和
V
{V}
V来自Encoder,所以这个注意力机制也被叫做Masked Encoder-Decoder Attention。
再来看下Transformer的整体结构
解码组件最后输出一个向量(蓝色方块表示),需要把这个输出向量变成一个单词,这就是线性变换层要做的操作,线性变换层Linear是一个简单的全连接神经网络。把解码组件产生的向量投射到一个比它自身大的多的向量中。
假设模型从训练集中学习到一万个不同的英语单词,那么线性变换层之后,向量的长度就是词表的长度_vocab_size,也就是一万。
再进行softmax操作,就得到了这一万个词每个词对应的概率,在其中选择一个概率最大的作为输出,假设下标为5的单词概率最大,就输出下标5在词表中映射的单词am。
首先建立output vocabulary(输出词表),定义词表完成后,就可以使用一个和词表长度相同的向量来表示词表中的单词。
下图词表中有6个单词,就可以使用一个维度为6的向量来表示词表中的每个单词,即为one-hot编码。
训练刚开始的时候,模型的参数都是随机生成的,模型产生的概率分布在每个单元格里赋予了随机的数值,我们可以用真实的输出来比较它。
输入是法语句子,期望的输出是“I am a student”,理想的概率分布如下图左边所示,但实际情况不可能,所以就期望通过softmax使得正确的输出对应的概率值最大。
优化的时候就是要优化这两个概率分布,使得它们尽可能相同,这就涉及到模型的损失函数问题,一般设置交叉熵损失,辅助模型进行反向传播。
训练结束之后,进入生成阶段,就是输入一个句子,对这个句子进行解码,一个时间步生成一个单词,得到翻译后的句子。这个时候,就设计到解码策略的问题,即怎么选择当前要输出的单词。
前面用的选择当前概率最大的词作为输出是贪婪采样策略,还有其它几种采样方式如下图:
贪婪采样:每个时间步选择当前概率最大的,作为输出;直到出现结束符或达到最大句子长度。
Beam Search:超参数集束宽度来设定每个时间步会保留几个取值路径,最后再比较这两个输出的概率大小,选择一个概率最高的,作为最终的输出。
基于温度系数的采样:在归一化(softmax)的过程中添加超参数t,通过改变t来控制概率分布的形状,使得分布更加均匀或集中。
Top-k和Top-p采样,先把词表中每个单词的概率进行降序排列,然后缩小采样范围,Top-k就是选前k个,Top-p就是词表中前多个单词的概率相加超过p,就选前多少个单词。然后再随机(真的随机)选择一个单词。
内容来自看b站视频做的笔记,加深了对Transformer架构的理解
:https://www.bilibili.com/video/BV1ng4y1K7GZ/?spm_id_from=333.1007.top_right_bar_window_history.content.click