图解Transformer(译)

原文链接

Transformer由论文Attention is All You Need提出。作为Tensor2Tensor包的一部分,该论文的TensorFlow实现代码可以从GitHub上获取。哈佛大学的NLP小组制作了一个PyTorch版本的指南说明。在以下的介绍中,我们试图简化难度,逐一介绍相关的概念,希望这样能够让普通的读者也能够轻易的理解本文。

Transformer结构图如下:

 

宏观角度

我们先将整个模型视为一个黑箱。在机器翻译应用中,该模型接收一种语言的句子作为输入,然后将其翻译成另一种语言输出。

拆开这个黑箱,我们可以看到它是由编码组件、解码组件以及它们之间的连接组成。

编码组件是由一堆编码器(论文中用了6个编码器)堆砌而成的,解码组件部分是由相同数量(也是6个)的解码器堆砌成的的。

所有的编码器在结构上是相同的(但是它们没有共享weights),每个编码器都可以分解成两个子层。

编码器的输入首先流过一个self-attention层,该层帮助编码器在对每个单词编码时能够关注输入句子的其它单词。后面,我们会深入地研究self-attention。

Self-attention的输出流向一个前馈(feed-forward)神经网络,每个位置对应的前馈神经网络都是一模一样的。

解码器中同样也有这两个子层,但是在两个子层中间增加了attention层,用来关注输入句子中的相关部分(和seq2seq模型中attention作用相似)。

将张量引入图中

到此,我们已经了解了模型的主要组件,接下来我们看一下各种向量或张量如何流经模型的各个组件中,将输入转化为输出的。

像大部分NLP应用一样。我们首先将每个输入单词通过embedding algorithm转化成向量。

Each word is embedded into a vector of size 512. We'll represent those vectors with these simple boxes.

词嵌入过程只发生在最底层的编码器中。所有的编码器都有一个相同的特点,即它们接收一个向量列表,列表中的每个向量维度为512。在底层编码器中它就是词向量,但在其它编码器中,它就是下一层编码器的输出(也是一个向量列表)。向量列表的大小是可设置的超参数,一般是我们训练集中最长句子的长度。

将输入序列进行词嵌入(词嵌入也就是单词向量化处理)之后,每个词向量都会流经编码器中的两个子层。

接下来我们看看Transformer的一个关键特性,输入序列中每个位置的单词都有自己的路径流入编码器。在self-attention层中,这些路径之间存在依赖关系。而前馈层没有这些依赖关系,因此,这些路径在流经前馈层时可以被并行执行。

接下来,我们以一个更短的句子为例,看看在编码器的每个子层中发生了什么。

现在开始Encoding!

如之前提到的,一个编码器接收向量列表作为输入。然后将向量列表中的向量传递到self-attention层进行处理,然后传递到前馈神经网络层中,最后将输出结果传递到下一个编码器中。

The word at each position passes through a self-attention process. Then, they each pass through a feed-forward neural network -- the exact same network with each vector flowing through it separately.

宏观角度下的自注意力机制

不要被self-attention这个词迷惑了,好像每个人都应该熟悉这个概念,其实我也是在到读到Attention is All You Need这篇论文时才弄懂这个概念的。让我们精炼一下它的工作原理。

例如,下面的句子是我们想要翻译的输入语句。

”The animal didn't cross the street because it was too tired”

这个“it”在句子中指的是什么?它指的是street还是animal?这对于人类来说是一个非常简单的问题,但对于一个算法来说并不算那么容易。

当模型处理单词“it”时,self-attention会允许“it”与“animal”建立联系。

当模型处理每个单词(输入序列中的每个位置信息)时,self attention允许将的其他位置信息作为辅助线索,帮助模型更好地编码当前的单词。

如果你熟悉RNNs,回忆一下它是如何维持隐藏层状态的,RNNs会将它已经处理过的前面的所有words/vectors的表示与它正在处理的当前words/vectors结合起来。Transformer使用Self-attention将其它所有相关单词的理解融入到我们正在处理的单词中。

As we are encoding the word "it" in encoder #5 (the top encoder in the stack), part of the attention mechanism was focusing on "The Animal", and baked a part of its representation into the encoding of "it".

请务必检查Tensor2Tensor notebook,在里面你可以下载一个Transformer模型,并用交互式可视化方式来检验。

自注意力机制详情

首先了解一下如何使用向量来计算self-attention,然后再看下如何使用矩阵来实现。

第一步,根据每个编码器的输入向量(每个单词的词向量)生成三个向量。对于每个单词,我们生成一个查询向量、一个键向量和一个值向量。这三个向量通过词嵌入与三个矩阵相乘得到,这些矩阵在训练过程中需要学习。

可以发现这些新向量在维度上比词向量(embedding vector)更低。它们的维度是64,而词向量和编码器的输入/输出向量的维度是512。实际上不强求维度更小,这只是一个架构上的选择,它可以让多头attention的计算更稳定。

Multiplying x1 by the WQ weight matrix produces q1, the "query" vector associated with that word. We end up creating a "query", a "key", and a "value" projection of each word in the input sentence.

什么是查询向量、键向量、值向量?

它们都是有助于计算和理解注意力机制的抽象概念。当阅读完下面attention是如何计算之后,你就会对这些向量在注意力机制中的角色有更清晰的认识。

第二步,计算attention就是计算一个分值。例如,我们正在为该例子中的第一个单词“Thinking”计算self-attention。我们需要拿输入句子中的每一个单词对“Thinking”打分。这些分数决定了在编码单词“Thinking”的过程中有多关注句子的其它部分。

这些分数是通过打分单词(所有输入句子的单词)的键向量与“Thinking”的查询向量点积计算得来的。所以如果我们处理位于位置#1的单词的自注意力的话,第一个分数是q1和k1的点积,第二个分数是q1和k2的点积。

第三步和第四步是将分数除以8(8是论文中使用的键向量的维数64的平方根,这会让梯度更稳定。这里也可以使用其他值,8是默认值),然后加上softmax操作归一化分值,使得分值全为正数且加和为1。

Softmax分值决定了每个单词在当下位置(“Thiking”)的关注度。显然,已经在这个位置的单词将获得最高的softmax分数,但有时关注另一个与当前单词相关的单词也会有所帮助。

第五步,将每个值向量softmax分数相乘(这是为了准备之后将它们求和)。保留我们想要关注的单词的值,并削弱不相关的单词的值(例如,让它们乘以0.001这样的小数)。

第六步,加权值向量求和,得到该位置的self-attention的输出结果(在我们的例子中是对于第一个单词)。

这样self-attention的计算就算完成了。得到的向量就可以传递给前馈神经网络了。然而实际应用中,这些计算是以矩阵形式完成的,以便计算得更快。我们接下来看看单词级别的矩阵计算。 

自注意力机制的矩阵运算实现

第一步,计算查询矩阵、键矩阵和值矩阵。我们将输入句子的词嵌入装入矩阵X中,将其乘以我们训练的权重矩阵(WQ、WK、WV)。

Every row in the X matrix corresponds to a word in the input sentence. We again see the difference in size of the embedding vector (512, or 4 boxes in the figure), and the q/k/v vectors (64, or 3 boxes in the figure)

最后,鉴于我们处理的是矩阵,我们可以将步骤2到步骤6合并为一个公式来计算self-attention层的输出。

The self-attention calculation in matrix form

多头注意力机制

论文通过增加一种叫做“多头”(“multi-headed”)注意力机制,进一步完善了self-attention层。在两个方面提高了注意力层的性能:

1.它扩展了模型关注不同位置的能力。在上面的例子中,虽然每个编码都在z1中有或多或少的体现,但是它可能被实际的单词本身所支配。如果我们翻译一个句子,比如“The animal didn’t cross the street because it was too tired”,我们会想知道“it”具体指的是哪个词,这时模型的“多头”注意力机制会是用帮助的。

2.它赋予了注意力层多个“表示子空间”。接下来我们将会看到,对于“多头”注意力机制,我们有多组Query/Key/Value权重矩阵而非仅仅一组(本文Transformer使用八个注意力头,因此我们对于每个编码器/解码器有八组矩阵集合)。这些集合的每一组都是随机初始化的。在训练之后,每个集合都被用来将输入词嵌入(或来自较低层编码器/解码器的向量)投影到不同的表示子空间中去。

With multi-headed attention, we maintain separate Q/K/V weight matrices for each head resulting in different Q/K/V matrices. As we did before, we multiply X by the WQ/WK/WV matrices to produce Q/K/V matrices.

如果我们做与上述相同的自注意力计算,只需八次不同的权重矩阵运算,我们就会得到八个不同的Z矩阵。

这给我们带来一点挑战。前馈层不需要8个矩阵------它只需要一个矩阵(由每一个单词的表示向量组成)。所以我们需要一种方法把这8个矩阵压缩成一个矩阵。那该怎么做呢?其实可以把这些矩阵拼接在一起,然后用一个额外的权重矩阵WO与它们相乘。

上述几乎就是多头自注意力(multi-headed self-attention)的全部内容。我认为这还仅是一部分矩阵。为了便于观察,我们试着把它们集中在一张图片中。

现在我们已经加入了注意力头(attention heads),让我们重温之前的例子,看看我们在例句中编码“it”时,不同的注意力“头”集中在哪里:

As we encode the word "it", one attention head is focusing most on "the animal", while another is focusing on "tired" -- in a sense, the model's representation of the word "it" bakes in some of the representation of both "animal" and "tired".

然而,如果我们把所有的注意力头都加到图示里,事情就更难解释了:

使用位置编码表示序列顺序

截止到目前为止,我们对模型的描述缺少了一种如何理解输入语句中单词顺序的方法。

为了解决这个问题,Transformer为每个输入的词嵌入添加了一个向量。这些向量遵循模型学习的指定模式,这有助于确定每个单词的位置,或序列中不同单词之间的距离。这里的理解是,将位置向量添加到词嵌入中使得它们在接下来的运算(Q/K/V映射和点积运算)中,能够更好地表达词与词之间的距离。

To give the model a sense of the order of the words, we add positional encoding vectors -- the values of which follow a specific pattern.

如果我们假设词嵌入的维度为4,则实际的位置编码如下所示:

A real example of positional encoding with a toy embedding size of 4

这种模型会是什么样子呢?

在下图中,每一行对应了一个词向量的位置编码,所以第一行对应着输入序列的第一个单词。每行包含512个值,每个值介于1和-1之间。为了便于模式可视化,我们对它们进行了涂色。

A real example of positional encoding for 20 words (rows) with an embedding size of 512 (columns). You can see that it appears split in half down the center. That's because the values of the left half are generated by one function (which uses sine), and the right half is generated by another function (which uses cosine). They're then concatenated to form each of the positional encoding vectors.

原始论文中描述了位置编码的公式(第3.5节)。你可以在get_timing_signal_1d()中看到生成位置编码的代码。对于位置编码而言,这不是唯一可能的方法。然而,它的优点是能够扩展到未知的序列长度(例如,当我们的训练模型要求翻译比训练集里的句子更长的句子时)。

July 2020 Update: The positional encoding shown above is from the Tranformer2Transformer implementation of the Transformer. The method shown in the paper is slightly different in that it doesn’t directly concatenate, but interweaves the two signals. The following figure shows what that looks like. Here’s the code to generate it:

Residuals模块

在继续进行下去之前,我们需要提到一个编码器架构中的细节:在每个编码器中的每一个子层(自注意力、前馈神经网络)的周围都有一个残差连接,并且都跟随着一个“层-归一化”步骤。

如果我们去可视化这些向量以及这个和自注意力相关联的层-归一化操作,那么看起来就像下面这张图描述一样:

同理,解码器的子层也是这样的。如果我们想象一个2层编码-解码结构的transformer,它看起来会像下面这张图: 

解码组件

我们已经谈到了大部分编码器方面的概念,我们基本上也就知道解码器是如何工作的了。但我们最好还是看看编码器和解码器是如何一起工作的。

编码器一开始需要处理输入序列。顶层编码器的输出之后会转变成一个包含向量K和V的注意力向量集。这些向量将被每个解码器用于自身的“编码-解码注意力”层,这些层帮助解码器关注输入序列哪些位置合适:

After finishing the encoding phase, we begin the decoding phase. Each step in the decoding phase outputs an element from the output sequence (the English translation sentence in this case).

接下来的步骤重复此过程,直到到达一个特殊的终止符号,它表示transformer的解码器已经完成了它的输出。每一步的输出在下一次的步骤中被提供给底端解码器,并且就像编码器之前做的那样,这些解码器会输出它们的解码结果 。另外,就像我们对编码器的输入所做的那样,我们会嵌入并添加位置编码给那些解码器,来表示每个单词的位置。

解码器中的自注意力层与编码器中的稍有不同:

在解码器中,自注意力层只被允许处理输出序列中更靠前的那些位置。在softmax步骤前,它会把后面的位置给隐去(把它们设为-inf)。

这个“编码-解码注意力”层工作方式基本就像多头自注意力层一样,只不过它是通过在它下面的层来创造查询矩阵,并且从编码器的输出中取得键/值矩阵。

最后的Linear和Softmax层

解码组件最后会输出一个浮点向量。我们如何把浮点数变成一个单词?这就是最后的线性变换层要做的工作,紧跟着线性变换层的就是softmax层。

线性层是一个简单的全连接神经网络,它可以把解码组件生成的向量投射到一个非常大的logits向量上。

假设我们的模型从训练集中学习到一万个不同的英文单词(我们模型的“输出词表”)。那么logits向量为一万个单元格长度的向量——每个单元格对应某一个单词的分数。

然后softmax层会把这些分数转换成概率(都为正数、加和为1.0)。概率最高的单元格被选中,该单元格所对应的单词则作为这一步的输出。

This figure starts from the bottom with the vector produced as the output of the decoder stack. It is then turned into an output word.

训练部分概括

我们已经过了一遍完整的transformer的前向传播过程,那我们就可以直观感受一下它的训练过程。

训练过程中,一个未经训练的模型会通过一个完全一样的前向传播。但因为我们用一个有标记的训练集训练它,所以我们可以用它的输出去与真实的输出做比较。

为了把这个流程可视化,假设我们的输出词汇仅仅包含六个单词:“a”,“am”,“i”,“thanks”,“student”以及“<eos>”(end of sentence的缩写形式)。

The output vocabulary of our model is created in the preprocessing phase before we even begin training.

一旦我们定义了我们的输出词表,我们可以使用一个相同宽度的向量来表示我们词汇表中的每一个单词。这也被认为是一个one-hot 编码。所以,我们可以用下面这个向量来表示单词“am”:

Example: one-hot encoding of our output vocabulary

接下来我们讨论模型的损失函数——这是我们用来在训练过程中优化的标准,通过它可以训练得到一个非常准确的模型。

损失函数

比如我们正在训练模型。现在是第一步,我们用一个简单的例子来训练——把“merci”翻译成“thanks”。

这意味着我们想要一个表示单词“thanks”概率分布的输出。但是因为这个模型还没被训练好,所以不大可能现在就出现这个结果。

Since the model's parameters (weights) are all initialized randomly, the (untrained) model produces a probability distribution with arbitrary values for each cell/word. We can compare it with the actual output, then tweak all the model's weights using backpropagation to make the output closer to the desired output.

你会如何比较两个概率分布呢?我们可以简单地用其中一个减去另一个。更多细节请参考cross-entropyKullback-Leibler divergence

但注意到这是一个过于简化的例子。更实际的情况是,使用一个句子作为输入。例如,输入“je suis étudiant”并期望输出是“i am a student”。那我们就希望我们的模型能够成功地在这些情况下输出概率分布:

●每个概率分布被一个以词表大小(我们的例子里是6,但现实情况通常是3000或10000)为宽度的向量所代表。

●第一个概率分布在与“i”关联的单元格有最高的概率

●第二个概率分布在与“am”关联的单元格有最高的概率

●以此类推,第五个输出的分布表示“”关联的单元格有最高的概率

The targeted probability distributions we'll train our model against in the training example for one sample sentence.

在一个足够大的数据集上充分训练后,我们希望模型输出的概率分布看起来像这个样子:

Hopefully upon training, the model would output the right translation we expect. Of course it's no real indication if this phrase was part of the training dataset (see: cross validation). Notice that every position gets a little bit of probability even if it's unlikely to be the output of that time step -- that's a very useful property of softmax which helps the training process.

因为这个模型一次只产生一个输出,我们可以假设这个模型只选择概率最高的单词,并把剩下的词抛弃。这是其中一种方法(叫贪心解码)。另一个方法是保留前两个最高概率的两个词(例如“I”和“a”),那么在下一步里,跑模型两次:其中一次假设第一个位置输出是单词“I”,而另一次假设第一个位置输出是单词“a”,并且无论哪个版本产生更少的误差,都保留概率最高的两个翻译结果。然后我们为第二个和第三个位置重复这一步骤......。这个方法被称作“beam search”,在我们的例子中,beam_size是2(这意味着在任何时候,都有两个部分的假设(未被完成的翻译)会被保留),并且top_beams也是2(这意味着我们将会返回两个翻译结果)。这些都是可以提前设定的参数。

再进一步

希望本文能够帮助您对Transformer的主要概念有个突破性的理解。如果您想在这个领域更加深入,我建议可以走以下几步:

• 阅读 Attention Is All You Need论文,Transformer的博客文章(Transformer: A Novel Neural Network Architecture for Language UnderstandingTensor2Tensor使用说明。
• 观看"Łukasz Kaiser’s talk",梳理整个模型及其细节。
• 尝试项目Jupyter Notebook provided as part of the Tensor2Tensor repo
• 尝试项目Tensor2Tensor

相关工作:

• Depthwise Separable Convolutions for Neural Machine Translation

• One Model To Learn Them All

• Discrete Autoencoders for Sequence Models

• Generating Wikipedia by Summarizing Long Sequences

• Image Transformer

• Training Tips for the Transformer Model

• Self-Attention with Relative Position Representations

• Fast Decoding in Sequence Models using Discrete Latent Variables

• Adafactor: Adaptive Learning Rates with Sublinear Memory Cos

Acknowledgements

Thanks to Illia PolosukhinJakob UszkoreitLlion Jones Lukasz KaiserNiki Parmar, and Noam Shazeer for providing feedback on earlier versions of this post.

Please hit me up on Twitter for any corrections or feedback.

Written on June 27, 2018

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值