这篇文章可以说是把Attention机制发扬光大的文章。提出了一个交Transformer的模型,对,就是变形金刚的那个transformer。Transformer模型只使用注意力机制(Attention mechanisms)来实现Encoder和Decoder,没有使用其他的RNN或者CNN。
Transformer模型是一个Seq2Seq的模型,即输入是一个序列,输出也为一个序列。
模型架构:
从模型架构上来看,主要有以下几个部分:
左侧是Encoder,右侧是Decoder,他们主要由以下模块组成:
Encoder和Decoder的数据流如图(以3个encoder和3个decoder为例):
接下来一一分解:
说到Attention,就得说两个东西:self-attention和multi-head attention:
self-attention就是一个模型,这个模型只有三个训练参数:
对于每个输入的列向量 a i ∈ R n a_i \in \Bbb{R}^n ai∈Rn:
q i = W q a i q_i = W_q a_i qi=Wqai , q i q_i qi是k维列向量
k i , v i k_i, v_i ki,vi同理。
上图我们输入为a1,a2,a3,输入向量为b1,b2,b3,图中绘制出了b1的计算方法,b2,b3类似。
有以下几个步骤需要注意:
α i j \alpha_{ij} αij的计算
在计算 α i j \alpha_{ij} αij时,需要使用 q i q_i qi和 k j k_j kj来计算。有两种计算 α i j \alpha_{ij} αij的方法:
(1)点积法(Dot-Product Attention)
(2)相加法(Additive attention)
图片来自李宏毅深度学习ppt
这篇论文提出的Transformer使用的是Scaled Dot-Product Attention。顾名思义,就是在点积的基础上,对于得到的 α \alpha α再进行一次缩放,除以 d k \sqrt{d_k} dk。
d k d_k dk是k值和q值的维度(他俩维度相同)
在实际应用中点积法速度更快,而且空间效率更高。
在 d k d_k dk很小时,这两种方法表现差不多,但是当 d k d_k dk较大的时候,相加法效果就比没有缩放的点积法表现的更好。论文作者认为这是因为 d k d_k dk大的时候,点积的结果会变大。把softmax结果推入梯度更小的区域。
We suspect that for large values of dk, the dot products grow large in magnitude, pushing the softmax function into regions where it has extremely small gradients .
为了解决这个问题,才进行了一个除以 d k \sqrt{d_k} dk的缩放。
2. 矩阵化上述计算过程
根据图中描绘的计算过程,我们算出所有输入序列 a 1 . . . a k a_1...a_k a1...ak的q值 q 1 . . . q k q_1...q_k q1...qk,得到Q矩阵(列向量是q)
K、V矩阵同理。
假设我们使用的是点积(dot product)。那么我们的 α i = q i ⋅ k i T \alpha_i = q_i \cdot k_i^T αi=qi⋅kiT,所以 α \alpha α的矩阵为:
A = K T ⋅ Q \Alpha = K^T \cdot Q A=KT⋅Q
接下来对A矩阵进行一个缩放(Scale),就是在我们得到的 A \Alpha A矩阵的每个元素都除以一个 d k \sqrt{d_k} dk,然后使用softmax对 A \Alpha A的每一列进行归一化处理。
最后计算b。b这里就不是矩阵乘法了。 v i v^i vi是个列向量, α i , j \alpha_{i,j} αi,j是个数。
b i = ∑ j v j α i , j ′ b^i = \sum_jv^j\alpha'_{i,j} bi=j∑vjαi,j′
Self-Attention过程的规范化表达:
这里的Q就是上面的 q i q_i qi构成的矩阵Q
不用在意点乘的顺序,思想是一样的,只是行列向量有区别。
在实现的时候,Decoder中Attention模块q计算时通过目标输出或者上一层encoder的输出来计算的,k、v是通过Encoder的输出来计算的。具体实现可见torch.nn.Modules.Transformer
Self-Attention是一个single-head attention版本(单头)。顾名思义,Multi-Head Attentions就是多个Self-Attention的叠加版本。
Multi-Head Attention的规范化表达:
这里Q似乎是我们前面所说的输入 a i a_i ai构成的输入序列矩阵,因为它乘以了权重。
输出层是通过多个self-Attention的向量结果进行横向联接然后通过一个线性层做映射。
Attention无法使用相对位置信息,如果要使用,就需将位置信息重新编码然后加到输入中。
有很多位置编码的方法:
位置编码的维度和输入Embedding后的维度相同,这样可以将位置编码的信息直接加到 α i \alpha_i αi上。
Transformer上使用的是三角函数的:
pos代表位置编号。
i代表特征的维度。偶数用上面的,奇数用下边的。
位置编码信息还可以使用指数的 e i e^i ei
这个就是一个两层的全连接网络,第一层的激活函数为ReLU
F F N ( x ) = R e L U ( x W 1 + b 1 ) W 2 + b 2 FFN(x) = ReLU(xW_1+b_1)W_2+b_2 FFN(x)=ReLU(xW1+b1)W2+b2
输出层是512,隐藏层是2048。
这里使用的就是NLP中常用的Embedding方法。
应为是seq2seq模型,我们的输入序列不可能总是长度一样的。我们需要把他们设置成相同的长度,方法就是以最长的句子为基准,短的句子使用
假设我们的输入序列长度为5,但是我们的输入只有3个,那么这个序列为 s e q = { a 1 , a 2 , a 3 , p a d d i n g , p a d d i n g } seq =\{a_1, a_2, a_3, padding, padding\} seq={a1,a2,a3,padding,padding}
这样我们可以得到相同长度的序列了,但是带来一个新的问题,如果我们的句子很短,后面就有许多padding要跟着一起做运算。会不会影响我们Attention部分的输出呢?答案肯定是会的。不然也就没有masking啥事了。
Masking的原理:
原理很简单,我们再回顾一下b1是如何计算出来的:
b1是a1与其他的输入进行计算相关系数 α \alpha α的累加来的。假设上图中 a 3 a_3 a3是一个padding字符。我们只需要让 α 13 ′ \alpha_{13}' α13′为0,就可以忽略它的影响。
α 13 ′ \alpha_{13}' α13′为0,我们就需要 α 13 \alpha_{13} α13是一个较大的负数,这样经过softmax函数之后, α 13 ′ → 0 \alpha_{13}'\rightarrow 0 α13′→0。(注意这段话的 α ′ \alpha' α′和 α \alpha α)
论文中,有两种masking的方式:
[1]. Vaswani A, Shazeer N, Parmar N, et al. Attention is all you need[J]. arXiv preprint arXiv:1706.03762, 2017.
[2]. 李宏毅深度学习课程2021