图解Transformer
Reference: The Illustrated Transformer
本文自译用于加深理解与印象。
关于注意力机制,可以参考先前的Seq2Seq Model with Attention
Transformer是论文Attention is All You Need提出的。在这篇文章中,我们将尝试把事情弄得简单一点,逐个介绍概念,以便更好理解。
A High-Level Look
我们首先把模型看作是一个黑箱。在机器翻译领域的应用中,输入一种语言的一个句子,会输出另外其翻译结果。
揭开盖子,我们能够看到一个编码组件encoding component
,一个解码组件decoding component
,还有其之间的连接关系connections
。
编码组件是一堆编码器构成的(Paper中堆叠了六个编码器,六个并没有什么说法,你也可以尝试其他数字)。解码组件也是由一堆解码器构成的(数量与编码器相同)。
所有编码器在结构上都是相同的,然而他们并不共享参数(或权重)。 每一个都可以被拆分为两个子层sub-layers
。
编码器的输入首先流过self-attention
层,self-attention
层可以帮助我们在对某个特定的词进行编码的时候同时关注到句子中其他位置单词的影响。
self-attention
层的输出被送往feed-foward neural network
,即前馈神经网络层。完全相同的前馈网络,独立地作用于每一个位置position
上。
解码器也有上述这两个层,但除此以外,在这两层之间,还有一个attention layer
,帮助解码器更加关注输入句子中相关的部分。(作用类似于Seq2Seq中的注意力机制的作用。)
Bringing The Tensor Into The Picture
现在,我们已经了解了模型的主要组件,下面让我们开始研究各种矢量/张量以及它们如何在这些组件之间流动,以将经过训练的模型的输入转换为输出。
首先我们将每一个输入单词通过embedding algorithm
转换为一个词向量。
嵌入过程只发生在最底部的encoder。对于所有的编码器Encoder
,他们都接受一个size为512的向量列表作为输入。只不过对于最底部的Encoder,其输入为单词经过嵌入后得到的词向量,而其他的Encoder的输入,是其下方一层Encoder的输出。列表的size是一个我们可以设定的超参数——通常来讲它会是我们训练集中最长的一个句子的长度。
在将输入序列中的单词进行Embedding
之后,他们中的每一个都会流过编码器的两层。
从这里我们可以看到一个Transformer非常重要的特性,那便是每一个位置上的单词在Encoder中自己的路径上各自流动。在self-attention
层中,这些路径之间存在相互依赖。而前馈层feed-forward
中彼此间并无依赖。所以在流经前馈层的时候,可以进行并行化处理。
下面我们将举一个短句的例子,然后观察sub-layer
上发生了什么。
Now We’re Encoding
像我们先前提到的,一个编码器接收一个向量列表作为输入。这个向量列表首先被送往self-attention
层,然后再送往feed-forward
前馈层。处理结束后将其output
送往下一个Encoder
。
每个位置的单词都被送往一个
self attention
层,然后再穿过一个前馈神经网络——每个向量独立穿过这个完全相同的网络。
Self-Attention at a High Level
self-attention
是Paper中提出的一个全新概念,不要被其简单的命名给迷惑。
假设我们输入了如下一个句子,并试图进行翻译:
“The animal didn’t cross the street because it was too tired”
那么句子中的it
指代谁呢?是指街道street
还是指动物animal
。这个问题对人类来说再简单不过,不过对算法来说却不是这样。
当处理单词it
时,self-attention
机制就可以让it
与animal
联系在一起。
当模型处理每个单词(即输入序列中的每个position)时,self-attention
可以在输入序列中的其他位置中寻找线索,来帮助这个单词获得更好的编码效果。
如果你熟悉RNN的话,想一下RNN是通过维持一个隐藏状态,来结合先前的处理过的向量与当前的输入向量。 而Self-attention
层是结合所有其他相关单词的"理解",将这些理解“融入”对当前处理单词的编码中。
当我们在最顶端的编码器#5对单词
it
进行编码时,注意力机制的一部分就会集中在The Animal
上,并将它的一部分编码表示(representation
,我个人理解为是编码表示)融入到对it
的编码中去。
Self-Attention in Detail
我们先看一下如何使用向量来计算self-attention
,然后再看一次它真正的实现方式——通过矩阵计算。
Self-Attention
的第一步:为输入的向量列表中的每一个向量(在这里的例子,由于是最底层,所以输入的是单词的Embedding)都创建三个向量。所以对于每个单词而言,我们创建了一个Query vector
,一个Key vector
,一个Value vector
。这三个向量是通过将输入向量乘以三个矩阵(矩阵是在训练中得到的)而得到的。
值得关注的是,这些新的向量在维度上比输入向量更小。他们的维度是64
,而embedding
和编码器的input/output
向量的维度是512。这些向量并不是必须要比原来的维度更小,这仅仅是一种架构上的选择,为的是multiheaded attention
的计算恒定。
用$x_1$乘以权重矩阵$W^Q$就得到了$x_1$的
query vector
-$q1$。类似这样,我们最终为输入序列中的每个单词都创造一个query
,一个key
,以及一个value
投影。
第二步:计算score
。例如当我们计算下例中的第一个单词"Thinking"时,我们需要基于当前单词,为输入句子中其他位置的每个单词打分(有一点条件概率的意思)。这个分数决定了在我们对这个单词进行编码的时候,对输入序列的单词需要给予多少注意力。
该分数通过将query vector
与key vector
进行点积操作dot product
来得到。所以当我们对位置1的单词计算其self-attention
时,第一个分数会是q1
和k1
的点积。第二个分数是q1
和k2
的点积。
第三步是将这些分数除以8(8是论文中key vector
维度64
的平方根,这可以使得梯度更加稳定,同时你可以选取其他的值)。
第四步是经过一个softmax
操作,softmax
可以保证score经过处理后全部为正,而且加和为1.
softmax score
决定了每个单词将对这个position
的下一次编码起到多大作用。显然,当前位置的单词会有最大的softmax score
,但是无疑,关注与当前单词有关的单词是很有用。
第五步:是将每个值向量value vector
乘以其对应的softmax
分数(要准备将他们加在一起了),这一步的动机就是放大那些我们focus
的单词,而淡化那些不那么重要的单词。
第六步:将所有加权后的值向量weighted value vector
(上一步得到的)相加,得到self-attention层对于该位置(例子中是第一个单词)的输出。
以上就是self-attention
的计算过程。得到的向量是一个我们可以直接送往前馈神经网络的向量。 在实际的实现中,我们通过矩阵运算来更快的处理。
Matrix Calculation of Self-Attention
第一步: 计算Query
,Key
and Value
矩阵。我们通过把输入的向量打包为一个矩阵,然后乘以我们训练得到的权重矩阵计算之。
X中的每一行对应输入序列中的一个单词。我们从图中又一次可以看到
embdding vector
和q/k/v
向量的在维度上是不同的。
最后:由于我们使用矩阵进行数据处理,我们可以把2-6步浓缩到一步,直接计算出self-attention
层的输出。
The Beast With Many Heads
论文随后进一步细化了self-attention
层,为其增加了一种叫做multi-headed attention
的机制。它通过两种方式提升attention-layer
的表现。
- “Multi-head"扩展了模型focus于不同位置的能力。就像上面那个例子一样,
z1
包含有其他每个字母编码的一小部分,但其主要还是被其本来位置上的单词所主宰。在我们翻译类似"The animal didn’t cross the street because it was too tired"这种句子时,Multi-Head Attention是非常有用的,因为我们想要知道it
指代的是谁。 - “Multi-head"给了
Attention Layer
一些代表子空间representation subspaces
。Multi-Headed Attention
机制下,我们不再是只有一组权重矩阵,而是有多组权重矩阵(Transformer中使用了八个attention head
,所以我们最后会得到八组Query/Key/Value
的权重矩阵。每一组都是随机初始化, 然后经过循环后,每一组都用于将输入向量投影到不同的子空间中representation subspace
。
Multi-headed Attention
机制下,我们分别保存着每个子空间(each head
)的Q/K/V
矩阵。
那么为上述八组Q/K/V
矩阵,分别做完上述的self-attention
计算后,我们会得到八个不同的Z
矩阵。
然后迎面而来的就有一个小问题,前馈神经网络并不需要8个矩阵,它期待的输入是一个矩阵,其中每一个向量代表一个单词。所以我们需要一种方法来将八个矩阵浓缩为一个。
怎么做呢?我们concatenate
这些矩阵,然后将拼接后的矩阵乘以一个额外的weights matrix
–$WO$。所得到的结果Z
就会捕捉到所有attention head
中包含的信息,然后这个Z
被送往FFNN。
以上就是Multi-head self-attention
的全部,下面我们将所有的信息集合在一张图中:
说完了attention-head
,我们回顾一下先前的例子,看一下不同的attention head
在我们对it
进行编码的是如何focus的。
当我们编码单词
it
时,一个attention head
更集中于the animal
上,另一个更集中于tired
上。从语义上分析,it在编码时融入了animal
和tired
的representation
所有attention head
全部加进来的情况如下,语义上有点难解释。
Representing The Order of the Sequence Using Positional Encoding
以上我们描述的模型中,缺失了单词在输入序列中的输入顺序。(没有时序信息)
为了记录这一点,Transformer为每个input embedding
加上了一个向量。这些向量遵循模型学习的一种特定模式specific pattern
,有助于确定每个单词的位置,或序列中不同单词之间的距离。这里的直觉是:将这些值添加到embedding
中,当其被投影到Q/K/V
向量中或者在进行点积操作时,就会提供有意义的距离信息。
假设embedding
是四维的话,那么实际的positional encoding
看起来可能是这个样子:
而pattern
看起来应该是什么样子?
在下图中,每一行对应一个向量的位置信息的编码结果。所以第一行就是我们将要加到输入序列中第一个单词的embedding
结果上的向量。每一行含有512个值,每个值的取值范围都是[-1,1],下图进行了可视化。
positional encoding
的公式在论文中详细描述了,而且这也不是唯一的对位置信息进行编码的方法。但是论文中的编码方式有一个优势:可以对未训练过的序列长度进行很好的缩放。
The Residuals
一个encoder架构的细节是: 在每个sub-layer
之后,都有一个residual connection
,随后是一个layer-normalization
归一化操作。
如果我们将向量和layer-norm
与self attention
的操作形象化,它看起来会是这样:
对于解码器的子层,也是这样。如果我们把它想象成一个由两个堆叠的编码器和解码器(Paper中是六个),它看起来应该是这样的。
The Decoder Side
我们已经讨论了编码器方面的绝大部分概念,也知道了其是如何工作的。下面让我们看一下他们是怎么样一起工作的。
编码器从处理输入序列开始,最顶端的编码器的输出随后被转换为attention vector
$K,V$的集合。这些向量在每一个解码器decoder
的encoder-decoder
层使用,用于帮助解码器focus在input sequence
的恰当位置。
下面的步骤重复这个过程,直到遇到终止符(表明decoder已经完成输出)。每一步的输出都会在下一个时间步反馈给最底层的解码器,解码器会将其输出一层一层向上bubble up
,就像编码器所做的一样。同时,我们也为解码器的输入加上了位置信息的编码。
解码器中的self-attention layer
与编码器中有微微的一些不同。
在解码器中,self-attention layer
只允许将时序上在前的位置信息“融合”进输出序列,这是通过在softmax步前进行mask来完成的。
而Encoder-Decoder Attention
层的工作原理,类似于Multiheaded self-attention
,不同的是,它从其下一层创建Query Matrix
, 而Key
和Value
矩阵都来自编码器最上层的输出。
The Final Linear and Softmax Layer
解码器输出的是一个浮点数的向量,我们应该如何将其转换为单词呢?这就是最后一个线性层的工作,其后还跟着一个Softmax层。
Linear Layer是一个简单的全连接神经网络,将解码器产生的向量投影到一个维度大的多的向量logits vector
假设我们的模型从训练集中学到了10000个不同的英文单词,那么我们的logits vector
就有10000个元素,每个都对应着一个单词的score。这就是我们将其转换成单词的方法。
Softmax 层随后将这些score转换为概率,有最高概率的单词被选中,然后输出。