文章目录
详解Transformer(Attention Is All You Need)
简介
Attention机制由Bengio团队于2014年提出,并广泛应用于深度学习的各个领域。然而真正使其广泛应用是在2018年谷歌团队提出了BETR
算法,其在NLP的 11 11 11项任务中取得了惊人的效果,而BETR
算法的核心就是Transformer
机制。
论文地址: https://arxiv.org/abs/1706.03762?amp=1
源码地址: https://github.com/jadore801120/attention-is-all-you-need-pytorch
核心思想:
Transformer
中抛弃了传统的CNN
和RNN
,整个网络结构完全是由Attention机制组成。更准确地讲,Transformer
由且仅由self-Attenion和Feed Forward Neural Network组成。一个基于Transformer
的可训练的神经网络可以通过堆叠Transformer
的形式进行搭建,作者的实验是通过搭建编码器和解码器各 6 6 6层,总共 12 12 12层的Encoder-Decoder,并在机器翻译中取得了BLEU值得新高。
原文中,对Transformer
的定义为:
Transformer is the first transduction model relying entirely on self-attention to compute representations of its input and output without using sequence aligned RNNs or convolution.
Transformer详解
1. 从整体看Transformer结构
作者论文中是在machine translation中对Transformer
进行介绍的,在这里我也从这个领域进行介绍。
首先, 我们可以将整个Transformer
视为一个黑盒子,那么我们的输入就是一种语言,输出就是另一种语言,如下图。

打开黑盒子,我们可以看到一个编码器和一个解码器,以及他们中间的连接

继续看编码器和解码器,就会发现编码器由 6 6 6个编码block组成,解码器同样由 6 6 6个解码block组成。

编码器的结构都是相同的(但他们不共享权重)。每一个被分解成两个子层——Self-Attention 和 Feed Forward Neural Network 。

解码器包含编码器的两个结构,同时两者之间还有一个 Encoder-Decoder Attention结构,也被称为 Cross-Attention结构

因此整体来看,网络结构则如下所示:
2. 输入编码
讲完Transformer
的主要组件模型后,就详细的从输入部分开始逐个解析。
跟正常的NLP算法相同,首先是通过Word2Vec
等嵌入方法,将输入的语料转换为特征向量,原文中使用的词嵌入的维度为 d m o d e l = 512 d_{model} = 512 dmodel=512。

嵌入(embedding) 只发生在最底层的编码器block中,编码器其余block的输入是其正下面的block的输出。不过相同的是,他们都是接到一个长度为 512 512 512的特征向量。

3. Self-Attention
宏观理解
Self-Attention
是Transformer
最核心的内容之一。可能大家很多人感觉这个词很熟悉,但其实对我个人而言,在接触到Transformer
之前并没有接触过这个概念。所以我首先从宏观角度来解释一下什么是Self-Attention
。
比如说,我们要翻译下面这个句子
The animal didn't cross the street because it was too tired.
上面的it
指的是什么呢?是animal
还是street
呢?
这对我们人而言是个很简单的问题,但是对于机器或者算法来说就并不是那么简单了。
当模型在处理it
的时候,Self-Attention
会将it
与animal
关联起来。
整体来看就是:当模型在处理每个词(输入序列中每个位置)时,Self-Attention
使得其可以查看输入序列中的其他位置,以助于为这个词寻找更好的编码。

细节阐述
我在这里先简化一下Self-Attention
的计算,先用向量的方式来进行计算,后续再使用更复杂的矩阵的方式进行计算。
向量计算Self-Attention
计算Self-Attention
的步骤:
(1) 首先使用每个输入向量 X X X(在上述例子中是每个词进行编码后的特征向量)乘以三个不同的权重矩阵 W Q , W K , W V W^Q, W^K, W^V WQ,WK,WV,创建三个向量—— Query向量( q q q ),Key向量( k k k )和Value向量( v v v )。
其中,
q , k , v q, k, v q,k,v向量——长度均为 64 64 64,
W Q , W K , W V W^Q, W^K, W^V WQ,WK,WV向量——均为 512 × 64 512 \times 64 512×64。

(2) 为每一个词向量 X X X计算一个 s c o r e score score,其计算方式如下:
s c o r e = q ⋅ k score = q \cdot k score=q⋅k

(3) 将 s c o r e score score除以 d k \sqrt{d_k} dk,即进行计算: s c o r e d k \frac{score}{\sqrt{d_k}} dkscore。这使得在训练过程中梯度更加稳定。
其中,
d k d_k dk——词向量 k k k隐藏层的维度,在论文中是 64 64 64。
(4) 对上述结果进行 s o f t m a x softmax softmax,即进行归一化操作。

(5) s o f t m a x softmax softmax的结果点乘向量 v v v,得到加权后的每个输入向量的评分 v v v
这一步的作用:保持我们关注词向量的特征,并且消除不关注词向量的特征(如将其乘以 0.0001 0.0001 0.0001这样一个极小的值)
(6) 对上述结果求和,这样就得到了Self-Attention
中一个单词的输出。

这样就结束了Self-Attention
的计算过程,并将输出的特征向量 z z z输入到后续的Feed Forward Neural Network
。
矩阵计算Self-Attention
在实际网络运算过程中,并不是以向量为单位,而是以矩阵为单位进行运算。
(1) 计算 Q, K, V \text{Q, K, V} Q, K, V矩阵:通过对矩阵 X \text{X} X点乘训练的权重矩阵 W Q , W K , W V \text{W}^\text{Q},\text{W}^\text{K},\text{W}^\text{V} WQ,WK,WV得到。

(2)-(6) 之后的计算与向量计算相同,如下所示:

Self-Attention整体计算过程
(1) 将输入单词转化成嵌入向量
(2) 根据嵌入向量得到 q , k , v q, k, v q,k,v 三个向量
(3) 为每个向量计算一个 s c o r e score score: s c o r e = q ⋅ k score = q \cdot k score=q⋅k
(4) 为了梯度的稳定,将 s c o r e score score除以 d k \sqrt[]{d_k} dk
(5) 对 s c o r e score score进行 s o f t m a x softmax softmax激活函数,使得其归一化
(6) s o f t m a x softmax softmax操作后的结果点乘 v v v向量,得到加权的每个输入向量的评分 v v v
(7) 对结果累加,得到最终结果 z = ∑ v z=\sum{v} z=∑v
Self-Attention中的Short-cut结构
需要注意的是,Self-Attention
的输出,并不是直接输入到后续的Feed Forward Neural Network
中,而是先输入到一个Layer Normalization层,再向后面层继续传播。
如果我们将LN
层可视化的展示出来,即为:

这样就通过一个LN
层,构成了ResNet
的残差结构
4. Multi-Head Attention
Multi-Head Attention
相当于 h h h个Self-Attention
的集成,在原文中使用的是 h = 8 h=8 h=8。
在这种机制下,我们有多组 Q , K , V \text{Q}, \text{K}, \text{V} Q,K,V权重矩阵,每一组都是随机初始化的。在训练的时候,每一组代表输入的 X \text{X} X到不同子空间的映射。

通过这样的方式,我们就可以得到 8 8 8个最终输出的 Z \text{Z} Z矩阵。
但是下一层的Feed Forward Neural Network
并不需要 8 8 8组 Z \text{Z} Z矩阵,而是仅需要一个 Z \text{Z} Z矩阵,因此我们需要对输出的 8 8 8个进行处理。
我们将这 8 8 8个矩阵全部concat
到一起,并乘以一个单独的权重矩阵 W o \text{W}^{\text{o}} Wo
这样,我们的整体过程就如下所示:

此时,我们尝试用Multi-Head Attention
机制重新回顾我们最开始的例子,假设有 2 2 2个注意力头,it
这时候的注意力则变为:

如果我们把所有的( 8 8 8个)注意力头全部加入进来,则会变为:

5. Encoder-Decoder Attention(Cross Attention)
在解码器中,每个block中比编码器中多一个Encoder-Decoder Attention
(Cross Attention
)。在Cross Attention
中, Q \text{Q} Q来自于解码器的上一个输出, K \text{K} K和 V \text{V} V来源于编码器的输出。其计算过程则与Self-Attention
完全相同。
需要特别注意的是,最初始Decoder
的输入,也就是上图中的Output Embedding
是起始符 < / s > </s> </s>,并不包含任何语义信息,仅仅是在第一步的时候用于输入。同样,最后也以一个特殊的结尾符 < B O S > <BOS> <BOS>结束.
由于在机器翻译中,解码过程是一个顺序操作的过程,也就是当解码第 k k k个特征向量时,为我们只能看到 k − 1 k-1 k−1及其之前的解码结果,论文中把这种情况下的Multi-Head Attention
叫做Masked Multi-Head Attention
。
6. Loss
这一块就不多说了,损失大家懂的都懂,不懂的短时间内也讲不明白,直接放结果。
原文中用到的Loss主要有 2 2 2个
- CE Loss
- Kullback-Leibler散度
7. 位置编码
截止到现在,我们对Transformer的描述还缺少一个部分,那就是对输入序列中单词顺序的表征方法,即我们常说的Position Encoding
,或者说位置编码。
具体来说,Position Encoding
在编码词向量中加入了单词的位置信息,这样Transformer
就能区分不同位置的单词了。
通常情况下,位置编码方式有两种
- 根据数据学习
- 设计编码规则,按照编码规则来确定位置编码
原文中作者使用第二种方法来进行位置编码。通常位置编码是一个与输入的 X \text{X} X向量等长的特征向量,论文中设其为 d m o d e l d_{model} dmodel,这样有利于和词向量进行单位加
操作。

原文中给出的位置编码公式如下:
P E ( p o s , 2 i ) = sin ( p o s 1000 0 2 i d m o d e l ) PE(pos, 2i) = \sin(\frac{pos}{10000^{\frac{2i}{d_{model}}}}) PE(pos,2i)=sin(10000dmodel2ipos)
P E ( p o s , 2 i + 1 ) = cos ( p o s 1000 0 2 i d m o d e l ) PE(pos, 2i+1) = \cos(\frac{pos}{10000^{\frac{2i}{d_{model}}}}) PE(pos,2i+1)=cos(10000dmodel2ipos)
其中,
p o s pos pos——单词位置
i i i——单词的维度
d m o d e l d_{model} dmodel——输入词向量长度
关于位置编码的实现,可以在Google开源的算法get_timing_signal_1d()函数中找到对应的代码。
作者这样设计的原因主要是考虑到,在NLP
任务中,除了单词的绝对位置,单词的相对位置也非常重要。
根据公式 sin ( α + β ) = sin α cos β + cos α sin β \sin(\alpha + \beta) = \sin\alpha\cos\beta + \cos\alpha\sin\beta sin(α+β)=sinαcosβ+cosαsinβ,和 cos ( α + β ) = cos α cos β − sin α sin β \cos(\alpha + \beta) = \cos\alpha\cos\beta - \sin\alpha\sin\beta cos(α+β)=cosαcosβ−sinαsinβ,这表明位置 k + p k+ p k+p的位置向量可以表示为位置 k k k特征向量的线性变换,这为模型捕捉单词之间的相对位置关系提供了很大的便利。
优缺点
优点
- 虽然
Transformer
最终也没有摆脱深度学习的套路,仅仅是一个全连接(或者一维卷积)+Attention的结合,但是其完全抛弃掉在NLP
中最根本的RNN
或CNN
,并且取得了很不错的效果。 Transformer
的独特设计,带来极大性能提升的关键是将任意两个单词的距离是 1 1 1,这对解决NLP
中棘手的长期依赖问题非常有效Transformer
机制不仅仅可以应用于NLP
领域,有很大的扩展空间- 算法的并行性非常好,目前的硬件条件完全可以进行相应训练。
缺点
- 直接抛弃掉
RNN
和CNN
虽然创新性很高,但使得模型丧失了捕捉局部特征的能力,互相结合的策略可能会带来更好的结果 Transformer
机制使得其失去了位置信息,这在NLP
中非常重要,而论文中虽然在特征向量中加入了位置编码,但这属于一个权宜之计,并没有改变Transformer
在结构上的固有缺陷。
常见问题
1. Q \text{Q} Q和 K \text{K} K关联性计算的问题
(1)什么是 Q \text{Q} Q、 K \text{K} K?
Q \text{Q} Q:Queries,表示查询
K \text{K} K:Key,表示关键词
这种说法主要借鉴推荐系统中的含义,根据 Q \text{Q} Q、 K \text{K} K计算关联性,然后推荐 V \text{V} V(但是Self-Attention
的目的不是单纯的推荐 V \text{V} V)
(2)为什么 Q ⋅ K \text{Q} \cdot \text{K} Q⋅K表示计算关联性
首先我们要明确,内积的意义是什么?
内积就是在求取一个向量在另一个向量上的投影长度,其表征的意义就在于 两个向量之间的关联性
(3)既然是计算关联性,为什么不用 X ⋅ X T \text{X} \cdot \text{X}^T X⋅XT
主要有以下两个好处:
- X \text{X} X通过与权重矩阵点乘,将输入分别变换为 Q ⋅ K \text{Q} \cdot \text{K} Q⋅K,解决了可能存在的长度不一致问题
- 通过中间层,学习到了非线性特征
2. 为什么要在 s o f t m a x softmax softmax之前,除以 d k \sqrt{d_k} dk
- 论文中提出的观点是:加性注意力在
Self-Attention
机制学习维度较小的时候效果优于点积注意力 - 个人观点
- 首先要明确一点: d k d_k dk是词向量隐藏层的维度
- 在 s o f t m a x softmax softmax之前,肯定要除以一个数,防止输入 s o f t m a x softmax softmax的值过大,导致导数趋近于 0 0 0
- 选择 d k \sqrt{d_k} dk是因为:假设 Q , K \text{Q}, \text{K} Q,K是均值为 0 0 0,方差为 1 1 1的分布,则他们的点积: Q ⋅ K T = ∑ i = 1 d k q i k i \text{Q} \cdot \text{K} ^T= \sum_{i=1}^{d_k}q_ik_i Q⋅KT=∑i=1dkqiki的分布是 期望为 0 0 0,方差为 d k \sqrt{d_k} dk。而此时除以 d k \sqrt{d_k} dk,则相当于最终结果为期望为 0 0 0方差为 1 1 1的分布,类似于归一化。
3. 把自身作为 Q , K \text{Q}, \text{K} Q,K之后为什么还要点乘 V \text{V} V
其实在点乘 V \text{V} V之前的操作,也就是 s o f t m a x ( Q ⋅ K T d k ) softmax(\frac{Q \cdot K^T}{\sqrt{d_k}}) softmax(dkQ⋅KT)是在求取一个 s c o r e score score,或者通俗点说就是在求取与一个权重分布,来表示相关性。
而这个权重分布最终还是要反映到初始特征空间中,也就是 V \text{V} V中的。
所以整体来看,其实之前点乘 V \text{V} V之前主要是在的到在 V \text{V} V上的权重分布,最后通过点乘 V \text{V} V得到加权后的值。