经典网络架构学习-Transformer

前言

transformer对刚入门深度学习的我来说,太难懂了,从网上查了很多博客,大多文章都来自The Illustrated Transformer
视频链接。这篇文章的图太生动形象了,容易理解。下面就翻译下这篇文章吧,加深记忆。如果你是刚入门transformer,强烈建议你好好阅读本文或者原文。网上太多基于这篇文章的衍生文章,而且写的并不好懂,你看完这篇文章,在看我给你的链接参考文章,会更容易懂。

概 述

首先,我们先将模型视为一个黑盒。在机器翻译应用程序中,它将采用一种语言的句子,然后以另一种语言输出其翻译
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2PoZIU06-1657680707723)(https://note.youdao.com/yws/res/21002/WEBRESOURCE98038a33c9d4241f38fca7c84324e28d)]

弹开那个擎天柱(Transformer与变形金刚是一个词,所以产生这个梗)的好东西,我们看到一个编码组件,一个解码组件,以及它们之间的连接
在这里插入图片描述

编码部分是一摞编码器(文件中把六个编码器堆在一起–六这个数字并不神奇,人们绝对可以尝试其他的排列方式)。解码部分是一叠相同数量的解码器。
在这里插入图片描述

编码器的结构都是相同的(但它们并不共享权重)。每一个都被分解成两个子层。

在这里插入图片描述

编码器的输入首先流经一个自我注意力层–该层帮助编码器在对特定单词进行编码时查看输入句子中的其他单词。我们将在本篇文章的后面仔细研究自我注意力。

自我注意力层的输出被送入前馈神经网络。完全相同的前馈网络被独立地应用于每个位置。

解码器有这两个层,但在它们之间有一个注意力层,帮助解码器专注于输入句子的相关部分(类似于seq2seq模型中注意力的作用)。

在这里插入图片描述

图解张量

现在我们已经看到了模型的主要组件,让我们开始看看各种向量/张量,以及它们如何在这些组件之间流动,以将训练模型的输入转换为输出。

扫描二维码关注公众号,回复: 14733651 查看本文章

与一般的NLP应用程序一样,我们首先使用 embedding algorithm.将每个输入词转换为向量。

在这里插入图片描述

嵌入只发生在最底层的编码器中。所有编码器都有一个共同的抽象概念,那就是它们会收到一个大小为512的向量列表–在最底层的编码器中,这将是单词嵌入,但在其他编码器中,这将是正下方编码器的输出。这个列表的大小是我们可以设置的超参数–基本上就是我们训练数据集中最长的句子的长度。

在我们的输入序列中嵌入单词后,每个单词都流经编码器的两层中的每一层。
在这里插入图片描述
在这里,我们开始看到Transformer的一个关键属性,即每个位置的字在编码器中流经自己的路径。在自我注意层的这些路径之间存在着依赖关系。然而,前馈层没有这些依赖关系,因此,在流经前馈层时,各种路径可以并行执行。

接下来,我们将把这个例子换成一个较短的句子,我们将看看在编码器的每个子层中发生了什么。

编码器

正如我们已经提到的,一个编码器接收一个向量列表作为输入。它通过将这些向量传入 "自我注意 "层,然后传入前馈神经网络来处理这个列表,然后将输出向上发送至下一个编码器。
在这里插入图片描述

自注意力机制概述

不要被我的 "自我注意 "这个词所迷惑,好像每个人都应该熟悉这个概念。我个人从来没有接触过这个概念,直到读了《注意力就是你所需要的一切》一文。让我们来提炼一下它的作用。

假设下面这个句子是我们要翻译的输入句:

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

这句话中的“it”指的是什么?是指街道还是动物?对人类来说,这是一个简单的问题,但对算法而言却不那么简单。

当模型处理“ it”一词时,自注意力机制使其能够将“it”与“animal”相关联。

在模型处理每个单词(输入序列中的每个位置)时,自注意力使其能够查看输入序列中的其他位置,以寻找思路来更好地对该单词进行编码。

如果你熟悉RNN,请想一下如何通过保持隐状态来使RNN将其已处理的先前单词/向量的表示与当前正在处理的单词/向量进行合并。Transformer使用自注意力机制来将相关词的理解编码到当前词中。

在这里插入图片描述

自注意力细节

我们先来看看如何使用向量来计算自我注意力,然后再继续看它的实际实现方式–使用矩阵。

计算自我注意力的第一步是根据编码器的每个输入向量(在本例中是每个词的嵌入)创建三个向量。因此,对于每个词,我们创建一个Query向量、一个Key向量和一个Value向量。这些向量是通过将embedding与我们在训练过程中训练的三个矩阵相乘而创建的。

请注意,这些新向量的维度比embedding向量小。它们的维数是64,而embedding和编码器输入/输出向量的维数是512。它们不一定要更小,这是一个架构的选择,以使多头注意力的计算(大部分)保持一致。
在这里插入图片描述

什么是“Query”,“Key”和“Value”向量?

它们是一些抽象概念,对于计算和思考注意力很有用。一旦你继续阅读下面的注意力计算方法,你就会知道几乎所有你需要知道的关于这些向量中每一个所起的作用。

计算自我注意力的第二步是计算一个分数(score)。比如我们要计算本例中第一个词 “Thinking”的自我注意力。我们需要对输入句子中的每个词与这个词进行评分。分数决定了当我们在某个位置对一个词进行编码时,要对输入句子的其他部分给予多大的关注。

分数的计算方法是将查询向量与我们要打分的各个单词的关键向量进行点积。因此,如果我们正在处理1号位置的单词的自我注意,第一个分数将是q1和k1的点积。第二个分数将是q1和k2的点积。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XEV5Xpeh-1657680707727)(https://note.youdao.com/yws/res/21065/WEBRESOURCEbc11780f722192dc806b6f0cd90b0bb9)]

第三和第四步是将分数除以8(本文中使用的关键向量维度的平方根, 也就是64。这将使得拥有更稳定的梯度。这里可能有其他可能的值,但这是默认值),然后将结果通过softmax操作。Softmax将分数归一化,所以它们都是正数,加起来是1

在这里插入图片描述

这个softmax分数决定了每个词在这个位置的表达量。显然,这个位置上的词会有最高的softmax得分,但有时关注另一个与当前词相关的词也是很有用的。

这个softmax分数将会决定在这个位置上的单词会在多大程度上被表达。显然,当前位置单词的softmax得分最高,但有时候,注意一下与当前单词相关的另一个单词也会很有用。

第五步是将每个Value向量乘以softmax分数(对后续求和的准备工作)。这里直觉的反应是保持我们要关注的单词的value完整,并压过那些无关的单词(例如,通过把它们乘以0.001这样的很小的数)。

第六步是对加权向量进行求和。这将在此位置(对于第一个单词)产生自注意层的输出。

在这里插入图片描述

自注意力的矩阵计算

第一步是计算Query,Key和Value矩阵。我们通过将我们的embedding打包成一个矩阵X,并将其与我们训练的权重矩阵(WQ、WK、WV)相乘.

在这里插入图片描述

最后,由于我们正在处理矩阵,我们可以将步骤二到六压缩到一个公式中,以计算自注意层的输出。

在这里插入图片描述

多头怪兽

该论文通过增加一种称为 "多头 "注意的机制进一步完善了自我注意力层。这在两个方面提高了注意力层的性能:

  • 它扩大了模型对不同位置的关注能力。是的,在上面的例子中,z1包含其他的编码的一小部分,但它可能是由实际的词本身所主导的。如果我们在翻译 “The animal didn’t cross the street because it was too tired”这样的句子,我们就会想知道 "it"指的是哪个词。

  • 它给了注意层多个 “表征子空间(representation subspaces)”。正如我们接下来所看到的,在多头注意力机制下,我们不仅有一个,而是有多组Query/Key/Value权重矩阵(Transformer使用八个注意力头,所以我们最终为每个编码器/解码器提供了八组)。每一组都是随机初始化的。然后,在训练之后,每一组都被用来将输入embedding(或来自下级编码器/解码器的向量)投射到不同的表示子空间。

在这里插入图片描述

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

在这里插入图片描述

这给我们带来了一些挑战。前馈层所预期的并不是8个矩阵,而是一个单一的矩阵(每个单词一个向量)。因此,我们需要一种方法来将这八个矩阵压缩为单个矩阵。

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

在这里插入图片描述

这几乎是多头注意力机制的全部内容。我意识到,这是相当多的矩阵。让我试着把它们都放在一个视图中,这样我们就可以一起查看它们

在这里插入图片描述

现在我们已经谈到了注意力头,让我们重新审视之前的例子,看看在我们的例句中对 "it "这个词进行编码时,不同的注意力头都集中在哪里:

在这里插入图片描述

然而,如果我们把所有的注意力都加到图片上,事情就会更难解释。

在这里插入图片描述

用位置编码来表示序列的顺序

到目前为止,我们所描述的模型还缺少一样东西,那就是对输入序列中的词的顺序进行说明的方法。

为了解决这个问题,Transformer为每个输入的embedding添加一个向量。这些向量遵循模型学习的特定模式,能够帮助我们确定每个单词的位置,或序列中不同单词之间的距离。在这个地方我们的直觉会是,将这些值添加到embedding中后,一旦将它们投影到Q / K / V向量中,以及对注意力点积,就可以在embedding向量之间提供有意义的距离

在这里插入图片描述

如果我们假设embedding的维数为 4,则实际的位置编码将如下所示:

在这里插入图片描述

这种模式可能是什么样子的?

在下图中,每一行都对应于一个向量的位置编码。因此,第一行将是我们添加到输入序列中第一个词的embedding的向量。每一行包含512个值,每个值在1和-1之间。我们对它们进行了颜色编码,从图案是可见的。

在这里插入图片描述

论文中描述了位置编码用到的公式(第3.5节)。你可以在get_timing_signal_1d()中查看用于生成位置编码的代码。这不是唯一的位置编码方法。但是,它的优势在于能够放大到看不见的序列长度(例如,我们训练后的模型被要求翻译一个句子,而这个句子比我们训练集中的任何句子都长)

2020年7月更新:上面显示的位置编码来自Transformer的Tranformer2Transformer实现。论文中用的方法略有不同,论文中没有直接链接,而是将两个信号交织。下面的图显示了这种方式的样子。这是用来生成它的代码

残差

在继续进行讲解之前,我们需要提一下编码器结构中的一个细节,那就是每个编码器中的每个子层(自注意力,ffnn)在其周围都有残差连接,后续再进行层归一化(layer-normalization)步骤。

在这里插入图片描述

如果我们要可视化向量和与自我注意相关的层范数操作,它看起来像这样:

在这里插入图片描述

这也适用于解码器的子层。如果我们想到一个2个堆叠编码器和解码器的Transformer,它看起来像这样:

在这里插入图片描述

解码器端

现在我们已经介绍了编码器端的大多数概念,我们基本上也知道解码器的组件是如何工作的。接下来让我们看一下它们如何协同。

编码器首先处理输入序列。然后,顶部编码器的输出被转换为一组注意力向量 K 和 V。每个解码器在其“编码器-解码器注意力”层中使用这些,这有助于解码器将注意力集中在输入序列中的适当位置:

在这里插入图片描述

后续步骤一直重复该过程,直到得到一个特殊符号,标志着Transformer解码器已完成其输出。每个步骤的输出都被馈送到下一个步骤的底部解码器,并且解码器会像编码器一样,将其解码结果冒泡。就像我们对编码器输入所做的操作一样,我们给这些解码器输入做嵌入并添加位置编码来表示每个单词的位置。

在这里插入图片描述

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

在解码器中,自注意力层仅被允许参与到输出序列中的较早位置。这是通过在自注意力计算中的softmax步骤之前屏蔽将来的位置(将它们设置为-inf)来完成的。

“编码器-解码器注意力”层的工作方式与多头自注意力类似,不同之处在于它从下一层创建其Queries矩阵,并从编码器堆栈的输出中获取Keys和Values矩阵。

线性层和softmax层

解码器堆栈输出浮点数向量。我们如何把它变成一个词?这是最后一个线性层的工作,然后是Softmax层。

线性层(Linear layer)是一个简单的全连接神经网络,它将解码器堆栈产生的向量投影到一个非常大的向量中,称为logits向量。

假设我们的模型知道 10000 个独特的英语单词(我们模型的“输出词汇”),它是从其训练数据集中学习的。这将使logits向量宽10000个单元格 - 每个单元格对应于一个唯一单词的分数。这就是我们如何解释模型通过线性层的输出

然后,softmax层将这些分数转换为概率(全部为正数,加起来均为1.0)。选择概率最高的单元格,并生成与其关联的单词作为此时间步长的输出

在这里插入图片描述

训练过程回顾

现在,我们已经讲解了一个训练完毕的Transformer的前向过程,那么再看一下模型的训练过程也是很有用的。

在训练过程中,未经训练的模型将历经完全相同的前向过程。但是,由于我们正在用已标记的训练数据集对其进行训练,因此我们可以将其输出与正确的输出进行比较。

为了直观地视觉化讲解这一点,我们假设输出词汇表仅包含六个单词(“a”,“am”,“i”,“thanks”,“student”和“ ”(“end of sentence”的缩写)) 。

在这里插入图片描述

一旦定义好了输出词汇表,我们就可以使用一个相同宽度的向量来表示词汇表中的每个单词了。这也被称为one-hot encoding。因此,例如,我们可以使用下面这个向量来表示单词“am”:

在这里插入图片描述

损失函数

假设我们正在训练我们的模型。假设这是我们训练阶段的第一步,我们用一个简单的例子训练它,使其将“merci”转换为“thanks”。

这意味着,我们希望输出的是一个能表示单词“thanks”的概率分布。但是,由于该模型尚未经过训练,因此目前这还不太可能发生。

在这里插入图片描述

如何比较两个概率分布?我们只是简单地从另一个中减去一个。有关更多详细信息,请查看交叉熵(cross-entropy)Kullback-Leibler 散度

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

  • 每个概率分布都由宽度vocab_size向量表示(在我们的简单示例中vocab_size为6,但更贴近实际情况的数量往往为30,000或50,000)
  • 第一个概率分布在与单词“i”关联的单元格处具有最高的概率
  • 第二个概率分布在与单词“am”关联的单元格处具有最高的概率
  • 依此类推,直到第五个输出分布标志着“ ”符号,该符号也具有自己的单元格,也处在10,000个元素词汇表中。
    在这里插入图片描述

在足够大的数据集上训练模型足够长的时间后,我们希望生成的概率分布如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ajkzlkYS-1657680707732)(https://note.youdao.com/yws/res/21219/WEBRESOURCE9adda303f77f9813e62ef8e9477989f1)]

现在,由于该模型一次产生一个输出,我们可以假设该模型是从该概率分布中选择概率最高的词,并丢弃其余的词。这是一种方法(称为贪婪解码)。另一种方法是保留,比如说,前两个词(比如说,‘I’和’a’),然后在下一步,运行模型两次:一次假设第一个输出位置是’I’,另一次假设第一个输出位置是’a’,考虑到1号和2号位置,哪个版本产生的误差小就保留哪个。我们对2号和3号位置重复这一步骤…等等。这个方法被称为 “beam search”,在我们的例子中,beam_size是两个(意味着在任何时候,两个部分假设(未完成的翻译)都保留在内存中),top_beams也是两个(意味着我们会返回两个翻译)。这两个都是超参数,你可以进行实验。

Transform 更多资料

Follow-up works

猜你喜欢

转载自blog.csdn.net/BXD1314/article/details/125759352