图解Transformer(译)

原文地址:https://jalammar.github.io/illustrated-transformer/
论文地址:https://arxiv.org/abs/1706.03762
Tensorflow 版本实现:https://github.com/tensorflow/tensor2tensor
Pytorch 版本实现: https://nlp.seas.harvard.edu/2018/04/03/attention.html

1、简介

注意力是一个有助于提高神经机器翻译应用程序性能的概念。Transformer是2017年Google提出的一种利用注意力机制来提高模型训练速度的模型。但是将Transformer的价值挖掘到极致的(目前来看),是OpenAI公司在六年后开发的基于Transformer架构的大语言模型,即生成式预训练Transformer(Generated Pre-trained Transformer, GPT)。据说GPT3.5的预训练模型目前已经拥有1750亿的参数量。

2、宏观视角

首先,我们将整个模型看做一个黑盒子。机器翻译就是输入一种语言,经过这个黑盒子翻译成另一种语言。
在这里插入图片描述
打开这个模型,我们可以看到一个编码器组件、一个解码器组件以及它们之间的连接。

编码器组件是由多个编码器堆叠而成(原文中堆叠的个数为6,这个数量并不固定,个人可以根据自己的实验效果进行修改)。解码器组件同样由6个解码器堆叠而成。

在这里插入图片描述

每个编码器的结构完全相同(但他们之间不共享权重),并且可以拆分出两个子层:

编码器的输入首先经过一个自注意力层(Self-Attention),Self-Attention可以帮助编码器在对特定单词进行编码时查看(关注)输入句子中的其它单词。
自注意力层的输出被传递到一个前馈神经网络(Feed Forward Neural Network),完全相同的前馈网络被独立地应用于每个位置。意思是每个位置的单词的自注意力输出都会单独通过同一个前馈神经网络进行处理,而不是将整个序列作为一个整体进行处理。
解码器同样包含上述两个子层,但在这两个子层中间还嵌入了一个注意力层,它可以帮助解码器在解码某个单词的时候关注输入序列中的有关位置的内容。(这个有点类似于seq2seq model
在这里插入图片描述

3、将张量引入到图中

上面介绍了模型的主要组件,下面再来看看各种向量/张量是如何在各组件之间进行传输的。
与常见的NLP一样,我们首先使用嵌入算法(Embedding Algorithm)将每个输入单词转换为向量(每个单词都被嵌入到一个512维的向量中,我们这里只使用一些简单的框来表示这些向量)。
在这里插入图片描述
词嵌入操作仅发生在最下面一层的编码器中。所有的编码器都接收一个由512维向量组成的列表,不同之处在于,最底层的编码器接收的是词嵌入向量,而其它编码器接收的是它的上一个编码器的输出列表。这个列表的长度是可以设置的,通常取训练集中最长的那个句子的长度。
词嵌入完成之后,所有的词向量都将流向编码器中的两个组件。
在这里插入图片描述

从这张图中可以看到Transformer的一个关键属性,即每个位置上的单词在编码器中都有自己的传输(流动)路径。在自注意力层中,这些路径之间存在着某些依赖关系,而前馈网络层中则不存在这种依赖关系,因此在流经前馈网络层时,不同的路径可以并行处理。

下面,我们将示例切换为一个较短的句子:Thinking Machines,具体查看编码器的每个子层中都发生了些什么。

4、开始编码

如上所述,每一个编码器都接收一个向量列表最为输入,然后将该列表依次送入self-attentionfeed-forward neural network,最后将输出传递给下一个编码器。

4.1 自注意力的宏观解读

让我们先来提炼一下自注意力的工作原理。假设下面这个句子是我们想要翻译的输入序列:

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

这里的 it 指代什么呢?是 street 还是 animal ?这个问题对于人类来说很简单,但是对于一个算法而言却并不是那么容易理解的。

当模型在处理 it 这个单词的时候,自注意力就可以让它与animal 这个词关联起来。

当模型处理输入序列中每个位置的单词时,自注意力允许它查看输入序列中其它位置的单词以寻找更好编码该单词的线索。

如果你熟悉 RNN,可以想象一下,通过保持隐藏状态,RNN 可以将之前处理过的单词/向量的表示与当前正在处理的单词/向量结合起来。自注意力是Transformer用于将其他相关单词的“理解”融入当前正在处理的单词中的一种方法。

当我们在最上层的一个编码器中对单词it进行编码时,注意力机制的一部分关注到了The animal,并将其融入到了it的编码中。

注:上面的图可以在 Tensor2Tensor notebook 这个交互式可视化网站进行查看,当然,需要加载Transformer的模型。

4.2 自注意力的微观解析

我们首先看如何使用向量计算自注意力,然后再看看它是如何实际实现的——使用矩阵。

计算自注意力的——
第一步:从编码器的每个输入向量(本例中是每个单词的词嵌入向量)中创建三个向量:Query向量、Key向量和Value向量。这些向量是通过将词嵌入向量分别乘以 W Q W^Q WQ W K W^K WK W V W^V WV三个矩阵创建的,矩阵可以随机初始化,然后进行训练。

注:新创建向量的维度要比嵌入向量小。例如,词嵌入向量和编码器的输入/输出向量都是512维,而querykeyvalue是64维的。实际上,并不一定要降低querykeyvalue的维度,这里将维度降低只是为了在使用多头注意力机制的时候计算量能够与使用单注意力机制的计算量相同。

在上图中,将 X 1 X_1 X1 W Q W^Q WQ相乘就可以得到 q 1 q_1 q1——与“Thinking”相关的query向量。我们最终会为输入序列中的每个单词创建一个query,一个Key和一个Value的投影。
querykeyvalue看起来很抽象,现在只知道它们是用来计算注意力的,下面将继续了解每个向量所起的作用。

第二步:计算得分。
仍然以本例中的第一个单词:“Thinking ”为例,假设我们正在计算它的自注意力,那么我们就需要根据这个单词对输入序列中的每一个单词都进行评分,分数的大小就决定了我们在编码 “Thinking ” 时对其它单词的关注程度。例如,单词 A 和 B 各获得了90分和30分,那么单词A对当前编码的单词贡献更大,值得我们更多的关注。

每个单词的得分是将固定的query向量与各单词的key向量进行点积运算求得的。这个query向量是当前正在编码的那个单词的查询向量。例如,当我们正在编码位置为1的单词(Thinking)时,那么该位置的得分就是 q 1 q_1 q1 k 1 k_1 k1的点积,而位置2的单词(Machines)的得分就是 q 1 q_1 q1 k 2 k_2 k2的点积。

第三步和第四步:缩放并归一化得分

第二步中计算出来的得分还要除以一个常数(默认值是8),然后将结果传递给softmax运算。

文中key向量的维度是64(前文也有提到过),这里除以的就是key向量维度的平方根,这样做可以使梯度更加稳定。softmax函数对得分进行归一化处理,使所有分数的和为1。softmax的计算公式如下:
y i = s o f t m a x ( x i ) = e x i ∑ k = 1 N e x k y_i = softmax(x_i) = \frac{e^{x_i}}{\sum_{k=1}^{N}e^{x_k}} yi=softmax(xi)=k=1Nexkexi
这个 softmax 分数决定了每个单词对于当前正在编码的那个单词的贡献程度。显然,当编码 “Tkinking” 时,“Tkinking” 自己所在位置的得分是最高的。但是其他位置的单词与“Tkinking” 之间也存在一定的关联,因此也是有用的。

其实在原文里作者把自注意力机制起名为缩放点积注意力机制(Scaled Dot-Product Attention),如下图。笔者认为从这里的第二个步骤到第四个步骤中就能感受到何为点积,何为缩放。常见的两种注意力函数就是加法注意力(additive attention)和点乘/乘法注意力(dot-product attention),作者使用的就是点乘注意力函数,不同之处仅在于此处增加了一个缩放系数。因为点积的结果可能会随着向量维度的增加而变大,所以需要进行缩放来稳定梯度。

第五步:将每一个 query 向量乘以对应的softmax得分(为了进行求和)。主要就是想尽量保留我们关注的单词的值,而把无关单词的影响最小化(比如通过乘以0.001这样的softmax得分)。

第六步:将加权处理后的 query 向量进行相加,这样就得到了当前位置单词的自注意力输出,即本例中 “Thinking” 的自注意力。

至此,我们就求完了单个词嵌入向量的自注意力,根据前文所述,下面就可以将得到的结果向量发送到前馈神经网络层进行处理。但是在实际应用中,为了加快处理速度,这个计算是以矩阵形式进行的。

5、矩阵形式的自注意力计算

第一步:计算Query矩阵、Key矩阵和Value矩阵(之前创建的是向量)。计算方法是:先将所有的词嵌入向量打包成一个x矩阵(词嵌入矩阵),然后将X分别乘以三个经过训练得到的权重矩阵 W Q W^Q WQ W K W^K WK W V W^V WV就可以得到对应的Q、K、V矩阵了。

其实X矩阵也很好理解,它的每一行就代表输入序列中的一个单词的词嵌入向量。词嵌入向量是512维的,我们这里仍然用4个矩形框表示,而 q/kv 是64维的,在图中用三个矩形框表示。

最后:因为我们处理的是矩阵,所以可以将第二步到第六步浓缩为一个公式来计算自注意力层的输出,即:
在这里插入图片描述

6、多头注意力机制

论文通过增加一种多头注意力机制(Multi-Head Attention)进一步完善自注意力层。如图所示:

多头注意力机制从以下两个方面提升注意力层的性能:

  1. **它增强了模型关注不同位置的能力。**在上面的例子中,虽然最终的结果z1中包含了其它所有编码中的部分信息,但是实际上起主导作用的还是这个单词本身(因为它的权重最大)。然而当我们想翻译这个句子:“The animal didn’t cross the street because it was too tired”的时候,我们显然比较关心这里的“it”到底指代什么,这就不是通过单纯地赋予单词本身更大的权重所能解决的问题了。

  2. **它为注意力层提供了多个“子空间表示”。**如下图所示,当我们使用多头注意力的时候,就会有不止一组权重矩阵Query/Key/Value(Transformer原文里使用的是8组),且每一组权重矩阵都是随机初始化的。然后,经过训练,每组权重矩阵都会将输入的词嵌入(或者是来自上一层的编码器/解码器的输出)投影到不同的子空间表示。


    如果使用不同的权重矩阵进行8次相同的自注意力计算,我们最终会得到8个不同的Z矩阵,如下:

但是,前馈网络层并不希望接收8个矩阵作为输入,它期望接收的是一个单一的矩阵,每个词对应这个矩阵中的一个向量。因此,就需要有一种方法将这8个矩阵压缩成一个矩阵。具体的方法是将这8个矩阵进行拼接(concat),然后乘以一个额外的权重矩阵 W O W^O WO

以上就是多头注意力机制的全部内容了,其中涉及到了很多矩阵运算步骤,我们将所有步骤都放到一张图中,这样更加一目了然:
在这里插入图片描述
让我们再回到之前的例子中,看看在编码单词 “it” 时,不同注意力头关注的焦点都在哪里。

注:图中八个颜色的框代表八个注意力头,每种颜色根据深浅程度表征在该位置处的重要程度(竖着看)。上图中只展示了橙色和绿色这两个头。

可以看出,图中的一个注意力头比较关注the animal(对应位置的橙颜色最深),另一个注意力头则关注于tired(对应位置的绿颜色最深)。因此,从某种意义上来说,模型对 “it” 的表示中就包含了 “animal” 和 “tired” 的部分表示。

当我们将所有的头都展示出来,如下图,可以发现不同的头关注的位置确实是不一样的。

7、使用位置编码表示序列的顺序

到目前为止,我们所描述的模型还缺少一件事情,那就是如何考虑输入序列中单词的顺序。

为了解决这个问题,Transformer会向每个输入嵌入中添加一个向量。这些向量遵循特定的模式,模型会学习这种模式,从而帮助它确定每个单词的位置,或序列中不同单词之间的距离。其原理(或直觉)就是,将这些值添加到词嵌入中后,一旦它们被投射到 Q/K/V 向量并进行点积注意力计算时,就可以在嵌入向量之间提供有意义的距离信息。

总之就是,为了让模型具有单词顺序的概念,我们添加了位置编码向量——这些向量的值遵循着特定的、可学习的模式。

举例来看,假设词嵌入的维度是4,那么实际的位置编码将如下所示:
在这里插入图片描述
这种可学习的模式具体表现形式如下图所示:

在这幅图中,每一行都对应一个位置编码向量。因此,第一行就是我们要添加到输入序列中第一个词嵌入的向量。每一行包含512个值(即维度是512),每个值的范围在 -1 都 1 之间。

更具体一点来看,该示例包含20个单词(行)和512维的嵌入大小(列)。左半部分的值是由正弦函数生成的,右半部分的值是由余弦函数生成的,然后将它们连接起来以形成每个位置的编码向量。
在这里插入图片描述

论文中对于位置编码的描述在 3.5 小节,生成位置编码的代码可以在:get_timing_signal_1d()函数 中看到。当然,这并非是位置编码的唯一实现方式,但这种方式的优点在于它能够扩展到未知长度的序列(例如,训练模型被要求翻译一个比训练集中任何句子都长的句子)。

上面展示的位置编码是来自Transformer的Tensor2Tensor实现。论文中展示的方法略有不同,它不是直接连接两部分的信号,而是对这两部分信号进行交织处理。这种位置编码的生成代码可见:transform_positional_encoding,其结果如下:

## 8、残差连接

在继续文章内容之前,我们需要提到编码器架构中的一个细节,即每个编码器中的每个子层(自注意力机制、前馈神经网络)都有一个残差连接,其后紧跟一个层归一化步骤。

如果我们可视化与自注意力机制相关的向量和层归一化操作,那么它看起来就是这样的:

这种残差连接同样适用于解码器的子层。如果我们将Transformer想象成由 2 个堆叠的编码器和解码器组成,那么它的结构就应该是下图所示的这样:
在这里插入图片描述

9、解码器端

现在我们已经介绍了编码器方面的大部分概念,并且也基本了解了解码器组件的原理,下面就让我们来看看它们是如何协同工作的。

编码器首先处理输入序列。顶部的编码器输出随后被转化为一组注意力向量 KV。这些向量将被每个解码器在其“编码器-解码器注意力”层中使用,以帮助解码器将注意力集中在输入序列中的适当位置。

编码阶段结束后开始解码,解码阶段的每一步都会从输入序列中输出一个元素,在本例中输出的就是翻译后的单词(Je —> I)
在这里插入图片描述
接下来的步骤重复这一过程,直到出现一个特殊符号,表明 Transformer 解码器已经完成其输出。每一步的输出都会在下一时间步骤中被传递到最底层的解码器,解码器就像编码器那样逐层向上汇总其解码的结果。同样地,我们会将位置编码嵌入并添加到解码器的输入中,以指示每个单词的位置。
在这里插入图片描述
解码器中的自注意力层与编码器中的自注意力层操作方式略有不同:

  1. 在解码器中,自注意力层仅允许关注输出序列中的较早位置的单词。这是通过在自注意力计算的 softmax 步骤之前屏蔽未来位置(将其设置为 -inf)来实现的。

    这就是原文中提到的 Masked Multi-Head Attention机制。相当于在解码过程中使用一个掩码和位置偏移机制,来防止预测当前位置的时候能关注到后续位置。也就是说,让网络在预测 i 点位置的输出时,只能够依赖于 i 点位置之前的已知输出信息,i 点之后的信息对它做了一个遮蔽处理。

  2. 编码器-解码器注意力层的工作原理与多头自注意力一样,不同之处在于它的查询矩阵(Q)是从它的下一层解码器的输出中创建的,而键(K)和值(V)矩阵是从编码器的输出中获取的。

上述两点都可以从这张论文原图中体现:

10、线性层和Softmax层

解码器堆栈输出的是一个浮点向量,如何将其转换为一个单词,这就是最后的线性层和紧随其后的 Softmax 层的工作了。

线性层是一个简单的全连接神经网络,它将解码器堆栈生成的向量投射到一个更大的、被称作 logits 的向量。

假设我们的模型识得 10000 个英语单词(模型的“输出词汇表”),这些单词是从训练数据集中学习到的。那么 logits 向量将有 10000 个单元——每个单元对应一个单词的分数。

然后,Softmax 层将这些分数转换成概率(所有概率都是正数且和为1)。最后选择概率最高的那个单元,其关联的那个单词就是这个时间步(time step)的输出。
在这里插入图片描述

11、训练回顾

现在我们已经介绍了Transformer的整个前向训练过程,讲解过程中使用的Transformer模型是已经训练好了的。虽然使用未训练的模型也会经历完全相同的传递过程,但是使用在有标记的训练集上训练好的模型可以将输出与实际的正确输出进行比较。

为了可视化这一过程,假设我们的输出词汇表中只包含六个单词:aamithanksstudent<eos>(“end of sentence”的缩写)。
在这里插入图片描述
在开始训练之前,模型的输出词汇表是在预处理阶段创建的。一旦定义了输出词汇表,我们就可以使用相同宽度的向量来表示词汇表中的每个单词。这也被称作“独热编码”(one-hot encoding)。例如,我们可以使用以下向量表示单词 am

12、损失函数

回顾完这些内容后,我们再来看一下模型的损失函数——训练阶段优化的指标,目的是得到一个经过训练的且精度很高的模型。

仍然假设,我们正处在训练阶段的第一步,训练模型来处理一个简单的例子——将merci翻译成thanks。这意味着我们希望模型的输出是一个概率分布,指示出现单词thanks的概率。但由于这个模型还没有训练好,所以现在还不太可能实现。
在这里插入图片描述
由于模型的参数(权重)是随机初始化的,因此(未经训练的)模型会为每个 单元/单词 生成一个具有任意值的概率分布。我们可以将其与实际输出进行比较,然后使用反向传播调整所有模型的权重,使输出更接近期望输出。

如何比较两个概率分布?我们可以简单地将一个概率分布减去另一个。更多细节可以参考 cross-entropyKullback–Leibler divergence

但是请注意,这只是一个过于简化的例子。更现实的情况是我们将使用一个比单词更长的句子。例如,输入:“je suis étudiant”,期望输出:“i am a student”。这实际上意味着我们希望模型连续地输出概率分布,其中:

  • 每个概率分布由一个宽度为 vocab_size的向量表示(在上面的示例中是6,更现实的情况是像30000、50000这样大的数字)。

  • 第一个概率分布在与单词i关联的单元格中具有最大概率。

  • 第二个概率分布在与单词am关联的单元格中具有最大概率。

  • 以此类推,直到第五个输出分布指示<end of sentence>符号,它也在这10000个元素的词汇表中有一个关联的单元格。
    在这里插入图片描述

在足够大的数据集上对模型进行足够时间的训练后,我们希望产生的概率分布是这样的:
在这里插入图片描述

13、结语

希望本文以及笔者的译文可以对读者起到帮助。此外,Attention Is All You Need 是一个很好的起始点,通过学习本文可以了解Transformer的主要概念。如果你想深入了解,这里建议通过以下步骤来学习(有些资源可能需要翻墙):

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Transformers是一种用于自然语言处理和其他相关领域的深度学习模型。它是通过编码器-解码器结构实现的。编码器将输入序列转换为一系列隐藏状态,而解码器则根据编码器的输出和之前的上下文生成输出序列。 关于图解transformer,引用中提到的图显示了Transformer的位置编码方法,其中将两个信号交织在一起。这个图可以帮助我们更好地理解位置编码的实现方式。 此外,引用中还展示了一个包含两个堆叠编码器和解码器的Transformer结构的图示。这个图可以帮助我们了解多层Transformer的组织结构。 最后,引用中提到的训练模型的直觉可以帮助我们更好地理解Transformer的工作原理。这个直觉的图示可能显示了一些与训练有关的信息,可以帮助我们更好地理解整个前向传递过程。 综上所述,通过引用中提到的图示,我们可以更好地理解Transformer模型的一些关键概念和操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [【Transformer图解 Transformer](https://blog.csdn.net/sikh_0529/article/details/128968765)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值