图解 Transformer

Transformer 是在论文 Attention is All You Need 中提出的。它的 TensorFlow 实现是 Tensor2Tensor 软件包的一部分。哈佛大学的 NLP 小组创建了一份指南,用 PyTorch 实现该论文。在这篇文章中,我们将尝试把事情简化一些,并逐一介绍相关概念,希望能让大家更容易理解。

1. 从整体视角来看 Transformer

首先,让我们把模型看作一个黑盒。在机器翻译应用中,它会接收一种语言的句子,并输出另一种语言的译文。

在 Transformer 模型中,我们可以看到编码模块(encoding)、解码模块(decoding)以及它们之间的连接。

编码模块(encoding)由一堆编码器(encoders)组成,解码模块(decoding)由一堆解码器(decoders)组成。原始论文中将六个编码器堆叠在一起 --“六”这个数字并没有什么神奇之处,完全可以尝试其他排列方式。

编码器的结构完全相同(但不共享权重)。每个编码器都分为自注意力层(self-attention)和前馈神经网络层(Feed Forward Neural Network)两个子层,如下图所示:

编码器的输入首先进入自注意力层,该层帮助编码器在编码特定单词时查看输入句子中的其他单词。我将在后面的文章中详细介绍自我注意层。
自注意力层的输出被送入前馈神经网络。完全相同的前馈网络将独立应用于每个位置。解码器除了这两个层之外,在它们之间还有一个注意力层,帮助解码器专注于输入句子的相关部分(类似于 seq2seq 模型中的注意力)。

2. 从数据张量的视角看 Transformer

我们已经看到了模型的主要组成部分,下面让我们开始了解各种向量/张量,以及它们如何在这些组成部分之间流动,从而将模型的输入转化为输出。
与一般的 NLP 应用一样,我们首先使用嵌入算法(embedding)将每个输入词转化为一个向量(原始论文中这个向量的维度是512维)。

embedding 操作发生在编码之前。所有编码器都有一个共同的抽象概念,那就是编码器会收到一个向量,每个向量的维度为 512,在第一个编码器中,这个向量就是单词嵌入,而在其他编码器中,这个向量就是前一个编码器的输出。这个向量的维度是我们可以设置的超参数,一般是训练数据集中最长句子的长度。
在输入序列中嵌入单词后,每个单词都会分别流经编码器的两层。

在这里,我们将看到 Transformer 的一个关键特性,即每个位置上的单词在编码器中都有自己独特的执行路径。在自注意层中,这些路径之间存在依赖关系。然而,前馈层不存在这些依赖关系,因此在流经前馈层时,各路径可以并行执行。
接下来,我们将把例子换成一个较短的句子,并看看编码器的每个子层都发生了什么。

3. Encoding 层

编码器接收一个向量作为输入。它在处理这个向量时,会将这些向量传递到一个 “自我注意 ”层,然后再传递到一个前馈神经网络,最后将输出向上发送给下一个编码器。

3.1. Self-Attention 模块

不要被我 "自我关注 "这个词所迷惑,好像它是每个人都应该熟悉的概念。我个人在阅读《注意力就是你所需要的一切》一文之前,从未接触过这个概念。让我们来提炼一下它的工作原理。
假设下面的句子是我们要翻译的输入句:”The animal didn't cross the street because it was too tired”
这句话中的 "it"指的是什么?是指街道还是指动物?这个问题对人类来说很简单,但对算法来说就不那么简单了。当模型处理 "it"这个词时,自我注意力会让它把 "it"和 "animal"联系起来。
当模型处理每一个单词(输入序列中的每一个位置)时,自我注意力会让它查看输入序列中的其他位置的单词,以寻找线索,从而为这个单词进行更好的编码。如果你熟悉 RNN,就会想到保持隐藏状态是如何让 RNN 将其处理过的先前单词/向量的表征与当前处理的单词/向量结合起来的。self-attention 就是 Transformer 将对其他相关单词的 "理解 "融入到当前处理的单词中的方法。

让我们先看看如何使用词向量来计算自注意力,然后再看看它是如何通过矩阵运算实现的。计算自注意力的第一步是根据编码器的每个输入向量(在本例中是每个单词的嵌入)创建三个向量。我们要为每个单词创建一个查询向量Q、一个键向量K和一个值向量V。这些向量是通过将词嵌入值乘以三个权重矩阵Wq、Wk、Wv来创建的。
这些新创建的向量的维度小于嵌入向量。它们的维度为 64,而嵌入和编码器输入/输出向量的维度为 512。它们并不是一定要更小,目的是使多头注意力的计算量(大部分)保持不变。

将 x1 与 WQ 权重矩阵相乘,得出 q1,即与该词相关的 "查询 "向量。最终,我们为输入句子中的每个单词创建了一个 "查询"、一个 "关键 "和一个 "值 "投影。

什么是 "查询"、"键 "和 "值 "向量?它们是抽象概念,用于计算和思考注意力。一旦你阅读了下面的注意力计算方法,你就会对这些向量所起的作用了如指掌。

计算自我注意力的第二步是计算分数。假设我们要计算本例中第一个单词 "Thinking"的自我注意力。我们需要对输入句子中的每个单词进行评分。分数决定了我们在编码某个位置的单词时,对输入句子其他部分的关注程度。
分数的计算方法是将查询向量与我们要评分的各个单词的关键向量进行点乘。因此,如果我们处理的是 1 号位置单词的自注意力,那么第一个分数就是 q1 和 k1 的点积。第二个分数是 q1 和 k2 的点积。

第三和第四步是将分数除以 8(本文中使用的关键向量维度64的平方根)。这样可以获得更稳定的梯度。这里还有其他可能的值,但这是默认值),然后将结果通过 softmax 运算。Softmax 会对分数进行归一化处理,使其全部为正值,加起来等于 1。

这个 softmax 值定义了输入的每个词对“Thinking”这个词的表征性。显然,这个位置上的单词将拥有最高的 softmax 分数,但有时关注与“Thinking”有关的另一个单词也是有用的。

第五步是将每个 value 乘以 softmax 分数(准备将它们相加)。这里要保持我们想要关注的单词的 values 不变,而忽略无关的单词(通过乘以例如 0.001 这样的小数)。
第六步是对加权的 value 向量求和,结果就是自注意力层在该位置(第一个单词)的输出。

至此,自注意力层的计算结束。我们可以将计算得到的 z1 向量发送给前馈神经网络。不过,在实际应用中,这种计算是以矩阵形式进行的。

3.2. Self-Attention 模块的矩阵运算

第一步是计算Query、Key 和 Value 矩阵。为此,我们将 embeddings 打包成一个矩阵 X,然后乘以我们训练过的权重矩阵(WQ、WK、WV)。

X 矩阵中的每一行都对应输入句子中的一个单词。我们再次看到 embedding 向量(512,在图中用 4 个方框表示)和 q/k/v 向量(64,在图中用 3 个方框表示)的大小差异。

最后,由于我们处理的是矩阵,我们可以将3.1节中的第二步到第六步浓缩为一个公式来计算自我关注层的输出。

3.3. 多头注意力

原始论文进一步完善了自注意力层,增加了一种多头注意力机制。这从两个方面提高了注意力层的性能:
1. 它扩展了模型关注不同位置的能力。

在上面的例子中,z1 大概率由它本身主导,但同时也包含了一些其他单词的编码。如果我们要翻译的句子是 "The animal didn’t cross the street because it was too tired",那么知道 "it"指的是哪个词将会非常有用。
2. 它为注意力层提供了多个 "表征子空间"。

接下来我们将看到,使用多头注意力,我们不仅有一组,而且有多组 Query/Key/Value 权重矩阵(Transformer 使用了八个注意力头,因此每个编码器/解码器最终都有八组)。每一组都是随机初始化的。然后,在训练结束后,每一组注意力头将输入嵌入(或来自下级编码器/解码器的向量)投射到不同的表征子空间中。

对于多头注意力,我们为每个头保持单独的 Q/K/V 权重矩阵,从而得到不同的 Q/K/V 矩阵。与之前一样,我们将 X 乘以 WQ/WK/WV 矩阵,得出 Q/K/V 矩阵。

如果我们进行自注意力计算,只是用不同的权重矩阵进行八次不同的计算,我们最终会得到八个不同的 Z 矩阵。

这里有一个问题:前馈层(FFN)不需要八个 z 矩阵,它只需要一个矩阵(每个单词一个向量)。因此,我们需要一种方法将这八个矩阵浓缩为一个矩阵。

怎么做呢?我们将矩阵拼接起来,然后乘以一个额外的权重矩阵 WO。

这就是多头自注意力的全部内容。总结如下图所示:

这里需要注意的是,在最开始我们输入 X 的是用户的 input token,之后的每一次输入 R 都是用户的 input token + 模型的 output token。

既然我们已经提到了注意力头,那么让我们重温一下之前的例子,看看在对例句中的单词 "它 "进行编码时,不同的注意头会将注意力集中在哪里:

当我们对 "it"这个词进行编码时,一个注意力头最关注的是 "animal",而另一个注意力头关注的是 "tire"。从某种意义上说,模型对 "it"这个词的表征包含了 "animal"和 "tire"的部分表征。

如果我们把所有的关注点都加进去,事情就变得复杂了:

3.4. 使用位置编码表示序列的顺序

迄今为止,我们所描述的模型中还缺少一件事,那就是如何考虑输入序列中单词的顺序
为了解决这个问题,Transformer 为每个输入 embedding 添加了一个位置编码向量。这些向量遵循模型学习到的特定模式,有助于确定每个单词的位置,或序列中不同单词之间的距离关系。在 embedding 向量投射到 Q/K/V 向量和点积注意之前,将这些 values 添加到 embedding 向量中,就能提供 embedding 向量之间的距离关系。

为了让模型了解单词的顺序,我们添加了位置编码向量--其值遵循特定的模式。如果 embedding 向量的维数为 4,那么实际的位置编码将如下所示:

在下图中,每一行都对应一个向量的位置编码。因此,第一行将是我们为输入序列中第一个单词的 embedding 添加的位置编码向量。每一行包含 512 个值,每个值介于 1 和 -1 之间。我们用颜色对它们进行了编码,这样模式就清晰可见了。

图中是 embedding 向量维度 为512维(列)的 20 个单词(行)的位置编码的真实示例。可以看到,它从中间被分成两半。这是因为左半边的值是由一个函数(使用正弦)生成的,而右半边的值是由另一个函数(使用余弦)生成的。然后将它们连接起来,形成每个位置编码向量。

您可以在 get_timing_signal_1d() 中看到生成位置编码的代码。图中展示的并不是唯一的位置编码方法。不过,它的优点是能够适应未知长度的序列(例如,如果我们训练有素的模型被要求翻译一个比训练集中任何句子都长的句子)。

上面显示的位置编码来自 Transformer 的 Tensor2Tensor 实现。论文中展示的方法略有不同,它不是直接连接,而是将两个信号交织在一起。下图展示了这种方法。

3.5. 残差连接

在继续讨论之前,我们需要提及编码器结构中的一个细节,即每个编码器中的每个子层(自注意力模块、FFN模块)周围都有一个残差连接(源于ResNet),其后还有一个层归一化(layer-normalization)步骤。

如果我们将与自注意力相关的向量和层归一化运算可视化,它将看起来像这样:

解码器的子层也是如此。如果我们把一个编码器和一个解码器的连接看成一个 Transformer,它看起来会是这样的:

4. Decoder 层

我们已经介绍了编码器方面的大部分概念,基本上也知道解码器的组件是如何工作的了。不过,让我们来看看它们是如何协同工作的。
编码器首先处理输入序列。然后,顶层编码器的输出被转换成一组注意力向量 K 和 V。这些向量将被每个解码器用于其 "encoder-decoder attention"层,帮助解码器将注意力集中在输入序列中的适当位置:

解码阶段的每一步都会输出输出序列(本例中为英语翻译句子)中的一个元素。

接下来就是重复这一过程,直到出现一个特殊符号,表明 transformer 的解码器已完成输出。每个步骤的输出都会在下一个时间步中送到最下面的解码器,解码器会像编码器一样将解码结果以“吐泡泡”的形式一个一个输出。就像对编码器输入所做的那样,我们在解码器的输入中嵌入并添加位置编码,以指示每个字的位置。

4.1. 解码器的注意力机制

解码器中的自注意力层与编码器中的自注意力层运行方式略有不同。
在模型训练阶段,喂给模型的是完整的翻译前-翻译后的句子对,Transformer 模型通常在训练时并行处理整个序列,而不是一个词一个词地逐步处理。如果不 mask 掉后面的序列的话,解码器在计算自注意力时可以看到当前词之后的位置,就会发生信息泄漏,即模型在生成某个词时已经知道后面的词,这会使得训练失去意义。mask 未来位置可以确保模型只能根据之前的词预测下一个词,这与模型实际推理阶段的生成情况相同。

具体做法是在自注意力层 计算softmax 之前 mask 掉未来位置(将其设置为负无穷大-inf,这样在计算 softmax 时,这些位置的注意力权重就会变为零,相当于被忽略了)。
“Encoder-Decoder Attention”层的工作原理与多头自注意相同,只是它的 Queries 矩阵由其下层创建,而Keys 和 Values 矩阵则来自编码器堆栈的输出。

5. 线性层和softmax层

解码器堆栈输出一个浮点矢量。如何将其转化为我们认识的单词?这就是最后一个线性层的工作,该层之后是一个softmax 层。
线性层是一个简单的全连接神经网络,它将解码器堆栈产生的矢量投射到一个更大的矢量中,这个矢量被称为对数矢量。
假设我们的模型知道 10,000 个独特的英语单词(我们模型的 "输出词汇"),这些单词是它从训练数据集中学习到的。这样,logits 向量就有 10,000 个单元--每个单元对应一个独特单词的得分。这就是我们如何解释线性层之后的模型输出。
然后,softmax 层会将这些分数转化为概率(全部为正,加起来等于 1.0)。概率最高的单元格将被选中,与之相关的单词将作为该时间步骤的输出结果。

该图从底层开始,首先是解码器堆栈输出的矢量。然后将其转化为输出字。

6. Transformer 模型的训练过程

6.1. embedding

既然我们已经介绍了经过训练的变换器的整个前向传递过程,我们不妨来看看训练模型的过程。
在训练过程中,一个未经训练的模型会经历完全相同的前向传播过程。但由于我们是在有标签的训练数据集上对其进行训练,因此我们可以将其输出与实际的正确输出进行比较。
为了直观地说明这一点,假设我们的输出词汇表只包含六个单词("a"、"am"、"i"、"thanks"、"student "和"<eos>"("end of sentence"的缩写))。

我们模型的输出词汇是在开始训练之前的预处理阶段创建的。

一旦我们定义了输出词汇,我们就可以使用长度相同的向量来表示词汇中的每个单词。这也被称为独热编码(one-hot encoding,一种最简单的编码形式)。例如,我们可以使用以下向量来表示单词 "am":

在总结之后,我们来讨论模型的损失函数--我们在训练阶段优化的指标,以获得训练有素、有望达到惊人精度的模型。

6.2. 损失函数

假设我们正在训练模型。假设这是训练阶段的第一步,我们用一个简单的例子来训练它--将 "merci "翻译成 "thanks"。这意味着,我们希望输出是一个表示 "谢谢 "一词的概率分布。但由于这个模型还没有经过训练,所以现在还不太可能实现。

由于模型的参数(权重)都是随机初始化的,因此(未经训练的)模型会为每个单元格/单词生成一个具有任意值的概率分布。我们可以将其与实际输出进行比较,然后使用反向传播调整模型的所有权重,使输出更接近预期输出

如何比较两个概率分布?我们只需将一个从另一个中减去即可。更多详情,请参阅交叉熵和库尔贝克-莱伯勒发散。但请注意,这只是一个简化的例子。实际情况是,我们会使用一个比一个单词更长的句子。例如--输入“je suis étudiant”,预期输出:"i am a student"。这实际上意味着,我们希望我们的模型能连续输出以下概率分布:

  • 每个概率分布由一个宽度为 vocab_size 的向量表示(在我们的简单示例中为 6,但更现实的数字是 30,000 或 50,000)
  • 第一个概率分布在与单词 "i"相关的单元格中的概率最高
  • 第二个概率分布在与单词 "am"相关的单元格中的概率最高
  • 以此类推,直到第五个输出分布显示"<句子结束>"符号,它也与 10,000 个元素词汇表中的一个单元格相关联。

在训练示例中,我们将根据一个样本句子的目标概率分布来训练我们的模型。

在足够大的数据集上训练模型足够长的时间后,我们希望产生的概率分布会是这样的:

希望在训练时,模型能输出我们期望的正确译文。当然,这并不能说明这个短语是否是训练数据集的一部分(参见:交叉验证)。请注意,每个位置都有一点概率,即使它不太可能是该时间步的输出结果--这是 softmax 非常有用的特性,有助于训练过程。

现在,由于模型一次产生一个输出,我们可以假定模型正在从概率分布中选择概率最高的单词,并丢弃其余的单词。这是一种方法(称为贪婪解码)。另一种方法是保留最前面的两个单词(例如 "I "和 "a"),然后在下一步中运行模型两次:一次假设第一个输出位置是单词 "I",另一次假设第一个输出位置是单词 "a"。对 2 号和 3 号位置重复上述步骤......等等。这种方法被称为 "beam search",在我们的例子中,beam_size 是2(这意味着内存中始终保留两个部分假设(未完成的翻译)),top_beams 也是两个(这意味着我们将返回两个翻译)。这两个超参数都可以进行更改。

希望这篇文章能帮助你了解 transformer 的基本概念。

参考:

The Illustrated Transformer – Jay Alammar – Visualizing machine learning one concept at a time.2

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值