The Illustrated Transformer

这是作者Jay Alammar 的文章 The Illustrated Transformer ,原文链接:https://jalammar.github.io/illustrated-transformer/

该篇是我的阅读笔记,结合原文阅读,进行了翻译并且加上了个人的理解标注,如有错误还望指出

修订日期:2025/11/24

总体视角来看

我们先将模型视为一个黑盒来分析。在机器翻译应用中,它会接收一个句子(用一种语言),并输出另一种语言的翻译结果。

打开Transformer的精妙设计,我们可以看到编码组件、解码组件以及它们之间的连接

编码组件是一叠编码器(图中将六个编码器叠放在一起——数字六并没有什么特别之处,完全可以尝试其他排列方式)。解码组件是一叠相同数量的解码器。

所有编码器的结构都相同(但它们的权重并不相同)。每个编码器又分为两个子层:

引入张量

现在我们已经了解了模型的主要组成部分,接下来让我们看看各种向量/张量以及它们如何在这些组成部分之间流动,从而将训练好的模型的输入转换为输出。

与一般的 NLP 应用一样,我们首先使用嵌入算法将每个输入词转换为向量。

什么是嵌入算法?

嵌入算法(Embedding)是 Transformer 架构的基石,它本质上不是一个复杂的计算公式,而是一个可学习的参数查找过程

1. 核心机制:巨大的参数查找表

所谓的“算法”,在实际操作中其实是一个针对权重矩阵的查表操作。

  • 物理形态:它是一个巨大的二维实数矩阵,通常记为 WEW_E
  • 矩阵形状[词表大小, 模型维度]。例如,如果词表有 10,000 个词,模型维度是 512,那么这就是一个 10,000×51210,000 \times 512 的表格。
  • 查找逻辑:模型接收到的输入并不是单词文本,而是单词的 索引编号 (ID)
    • 如果单词 “Apple” 的 ID 是 5,嵌入层会直接定位到矩阵的第 5 行
    • 它不进行加减乘除运算,而是直接复制这一整行的数据作为输出。

2. 数据的由来:从“随机乱码”到“语义地图”

这个矩阵里的数字(即每个词的向量表示)完全是通过训练生成的,没有任何人工预设的成分。

  • 初始状态:在训练开始前,这个矩阵被初始化为随机噪声(通常是服从正态分布的小数)。此时,“Apple” 的向量可能和 “Car” 的向量非常相似,完全没有语义逻辑。
  • 训练过程:这正是深度学习的核心。
    1. 模型使用随机向量进行预测,产生巨大的误差(Loss)。
    2. 反向传播算法 (Backpropagation) 会计算误差对每个词向量的梯度。
    3. 算法会微调矩阵中特定行的数值。例如,它会调整 “King” 和 “Queen” 对应的行,使它们在数学空间中靠得更近;同时拉大 “Apple” 和 “Car” 的距离。
  • 最终结果:经过海量数据的训练,这个矩阵就变成了一张高度浓缩的“语义地图”。

3. 输出形式:稠密语义向量

经过查表后,离散的单词 ID 被转换为了稠密向量(一排浮点数)。

  • 内容实质:比如 [0.12, -0.98, 0.55, ...]。这排数字是单词在高维空间中的坐标
  • 关键作用
    1. 数学运算:它将无法计算的符号(如单词)变成了可以计算的数字,使得后续的 Self-Attention 能够通过点积(Q×KTQ \times K^T)来计算词与词之间的相似度。
    2. 语义推理:这些向量不仅代表了单词本身,还隐含了单词之间的关系。比如向量加减运算 Vector(King) - Vector(Man) + Vector(Woman) 的结果,会自动落在 Vector(Queen) 附近。

总结:嵌入算法就是将人类定义的离散符号,通过查阅一个由神经网络训练出来的巨大矩阵,映射为机器可理解的连续语义坐标的过程。

词嵌入仅在最底层的编码器中进行。所有编码器的共同抽象是,它们都接收一个大小为 512 的向量列表——在最底层的编码器中,该列表包含词嵌入;而在其他编码器中,则包含其下方编码器的输出。该列表的大小是一个可设置的超参数——它基本上等于训练数据集中最长句子的长度。

将单词嵌入到输入序列中后,每个单词都会依次经过编码器的两层。

这里我们开始看到Transformer的一个关键特性,即编码器中每个位置的词都沿着各自的路径流动。在自注意力层中,这些路径之间存在依赖关系。然而,前馈层不存在这些依赖关系,因此各个路径可以在流经前馈层时并行执行。

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

现在开始编码!

正如我们之前提到的,编码器接收一个向量列表作为输入。它通过将这些向量传递给“自注意力”层来处理该列表,然后再传递给前馈神经网络,最后将输出发送到下一个编码器。

每个位置的单词都会经过一个自注意力过程。然后,它们各自进入一个前馈神经网络——同一个网络,只是每个向量分别单独流经该网络。

Self-Attention at a High Level

假设以下句子是我们要翻译的输入句子:

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

这句话中的“it”指的是什么?是指street还是aniaml?对人类来说,这是个简单的问题,但对算法来说却并非如此。

当模型处理单词“it”时,自我注意力机制使其能够将“it”与“animal”联系起来。

当模型处理每个单词(输入序列中的每个位置)时,自注意力机制使其能够查看输入序列中的其他位置,寻找有助于更好地编码该单词的线索。

由于我们在编码器 #5(堆栈中的顶部编码器)中对单词“it”进行编码,注意力机制的一部分集中在“动物”上,并将它的一部分表示嵌入到“it”的编码中。

注重细节

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

计算自注意力机制的第一步是根据编码器的每个输入向量(在本例中为每个词的词嵌入)创建三个向量。因此,对于每个词,我们创建一个查询向量、一个键向量和一个值向量。这些向量是通过将词嵌入乘以我们在训练过程中训练的三个矩阵而得到的。

请注意,这些新向量的维度比嵌入向量小。它们的维度为 64,而嵌入向量和编码器输入/输出向量的维度为 512。它们并非必须更小,这是一种架构选择,目的是使多头注意力机制的计算量(基本)保持恒定。

X1X_1乘以权重矩阵WQW^Q得出q1q_1,即与该单词关联的Queries向量。我们最终为输入句子的每个单词创建一个"Query",一个"Key"和一个"Value"向量

什么是"Query",“Key”,“Value”?

  • QQ (查询):代表“我想找什么?”(例如:我是主语,我想找谓语)。
  • KK (索引):代表“我有什么特征?”(例如:我是动词,是谓语)。
  • VV (内容):代表“我的实际含义”(如果决定关注我,就把这部分信息带走)。

这些矩阵是随机生成的吗?它们代表什么?

  • 我们有三个权重矩阵 WQ,WK,WVW^Q, W^K, W^V
  • 在训练开始前,它们内部的数字是完全随机,是反向传播算法在训练过程中,逐渐把这些随机矩阵调整成了有意义的参数,“查询”,“索引”,“内容”是人为赋予这三个随机矩阵的含义

计算自注意力的第二步是计算得分。假设我们要计算本例中第一个词“Thinking”的自注意力。我们需要将输入句子中的每个词与这个词进行比较并给出得分。这个得分决定了在编码特定位置的词时,应该给予输入句子的其他部分多少关注。

得分的计算方法是将查询向量(Queries)与待评分词的键向量(Keys)进行点积运算。因此,如果我们正在处理位置#1 的词的自注意力机制,则第一个得分是q1q_1k1k_1的点积。第二个得分是q1q_1k2k_2的点积。

第三步和第四步是将分数除以 8(论文中使用的键向量维度 64 的平方根。这样做可以获得更稳定的梯度。这里可能还有其他值,但这是默认值),然后将结果输入到 softmax 操作中。softmax 操作会将分数归一化,使它们全部为正数且总和为 1。

1.为什么是 64?

这里的 64 指的是 dkd_k(Key 向量的维度),这是关于 多头注意力 (Multi-Head Attention) 机制。

  • 总维度 (dmodeld_{model}):Transformer 默认的模型总维度是 512。也就是说,单词 “Thinking” 一开始进入模型时,是一个长度为 512 的向量。
  • 切分 (Splitting):为了让模型有“8 只眼睛”(也就是8个头)同时观察,我们将这 512 维切成了 8 份。
  • 计算512÷8=64512 \div 8 = 64

所以,64 就是每一个注意力头 (Head) 内部处理的 Query (qq) 和 Key (kk) 向量的长度。

2.什么是Softmax函数?

Softmax 是一个“归一化”函数,它的作用是把一堆乱七八糟的数字(有正有负、有大有小),变成一套“概率分布”,这意味着所有可能结果的概率总和必须是 100%

公式:

Softmax(xi)=exijexjSoftmax(x_i) = \frac{e^{x_i}}{\sum_{j} e^{x_j}}

softmax 分数决定了每个词在该位置的出现频率。显然,该位置的词将具有最高的 softmax 分数,但有时关注与当前词相关的其他词也很有用。

第五步是将每个值向量(Values)乘以softmax得分(为将它们相加做准备)。这里的目的是保持我们想要关注的词的值不变,并消除无关词的影响(例如,通过将它们乘以0.001这样的小数)。

第六步是将加权值向量相加。这就产生了该位置(第一个词)自注意力层的输出。

至此,自注意力机制的计算就完成了。我们可以将得到的向量传递给前馈神经网络。然而,在实际实现中,为了加快处理速度,这种计算是以矩阵形式进行的。

自注意力矩阵计算

第一步是计算查询矩阵、键矩阵和值矩阵。我们通过将词嵌入打包成矩阵XX,并将其与我们训练的权重矩阵(WQWKWVW_Q,W_K,W_V)相乘来实现这一点。

由于我们要处理矩阵,因此我们可以将步骤2到6压缩成一个公式,以计算Self-Attention层的输出。

公式:Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V

  • 为什么要 Q×KTQ \times K^T(点积)?
  • 这是为了计算相似度。向量点积越大,代表两个词关系越紧密。
  • 这里的 XX 被用了两次(生成 Q 和 K),实现了**“自己查询自己”**,所以叫 Self-Attention。
  • 为什么要除以 dk\sqrt{d_k}
  • 这是一个数值稳定性的技巧。
  • 当维度很高时,点积结果会非常大。这会导致 Softmax 函数进入“饱和区”(输出非 0 即 1),梯度几乎消失,模型无法训练。除以缩放因子让数值回到合理范围。
  • 为什么要乘 VV
  • Softmax 算出来的是“概率”(比如关注度 80%)。
  • 但这只是一个数字,我们需要具体的信息。所以用这个概率去加权 VV(实际内容向量)。

公式:Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V

1. 为什么要 Q×KTQ \times K^T(点积)?

  • 这是为了计算相似度。向量点积越大,代表两个词关系越紧密。
  • 这里的 XX 被用了两次(生成 Q 和 K),实现了“自己查询自己”,所以叫 Self-Attention。

2. 什么要除以** dk\sqrt{d_k}

  • 这是一个数值稳定性的技巧。
  • 当维度很高时,点积结果会非常大。这会导致 Softmax 函数进入“饱和区”(输出非 0 即 1),梯度几乎消失,模型无法训练。除以缩放因子让数值回到合理范围。

3. 为什么要乘** VV

  • Softmax 算出来的是“概率”(比如关注度 80%)。
  • 但这只是一个数字,我们需要具体的信息。所以用这个概率去加权 VV(实际内容向量)。

The Beast With Many Heads

该论文通过添加一种称为**“多头”注意力机制**,进一步改进了自注意力层。这从两个方面提高了注意力层的性能:

  1. 它扩展了模型专注于不同位置的能力。在上面的示例中,z1z_1主要由自己本身决定,只包含所有其他编码的一点点信息,有时候会出现信息不足,模型效果不好的情况,比如我们要翻译这样的句子,“The animal didn’t cross the street because it was too tired”,那么我们会想知道“it”指的是什么,那么多头注意力就有用了,它可以从不同的方面出发,关注不同的词汇,比较完整的解出“it”所涉及的单词。
  2. 它为关注层提供了多个“表示子空间”。 正如我们接下来将要看到的,在多头关注下,我们不仅拥有一个而是多组Query/Key/Value权重矩阵(Transformer使用八个关注头,因此每个编码器/解码器最终得到八组输出) 。 这些权重矩阵中的每一个都是随机初始化的。 然后,在训练之后,将每个权重矩阵用于将输入的嵌入(或来自较低编码器/解码器的输出矩阵)投影到不同的表示子空间中。

在多头注意力机制中,我们为每个注意力头维护独立的Q/K/VQ/K/V权重矩阵,从而得到不同的Q/K/VQ/K/V 矩阵。与之前一样,我们将 XX 乘以WQ/WK/WVW_Q/W_K/W_V 矩阵,得到 Q/K/VQ/K/V 矩阵

如果我们重复上述自注意力计算,每次使用不同的权重矩阵,重复八次,最终会得到八个不同的 ZZ 矩阵。

这就给我们带来了一些挑战。前馈层并不期望接收到八个矩阵,它期望的是一个矩阵(每个词对应一个向量)。因此,我们需要一种方法将这八个矩阵合并成一个矩阵。

我们该如何实现呢?我们将矩阵连接起来,然后乘以一个额外的权重矩阵WOW^O

这就是多头自我关注的基本内容。

增强视野:Multi-Head Attention (多头注意力)

机制解释

  • 概念: 把输入向量切分成 8 份(默认),分别通过 8 组完全独立的 WQ,WK,WVW^Q, W^K, W^V 矩阵进行运算。
  • 随机初始化与参数调整(关键)
    • 初始状态:这 8 组权重矩阵(以及最后的融合矩阵 WOW^O)在训练开始的第一秒,内部所有的数值都是完全随机生成的(通常服从正态分布)。此时它们没有任何功能,也不懂任何语法或指代。
    • 训练过程:模型通过反向传播算法计算预测结果与真实结果的误差(Loss),并利用梯度下降不断微调这些矩阵里的参数。
  • 不需要人工干预(自动演化)
    • 我们不需要(也无法)人为规定“头 1 必须看语法”。
    • 分工的涌现:正是因为初始化的随机性(每个头的起跑线不一样),加上最小化总误差的目标,这 8 个头在训练过程中会被“逼”着去关注输入数据的不同特征,从而自动演化出不同的分工。
    • 结果:训练结束后,我们观察发现:头 1 可能学会了关注语法结构,头 2 可能学会了关注代词指代。这就是深度学习中神奇的“涌现”现象。

数据流向

  1. 分裂:输入被拆分。
  2. 并行计算:8 个头同时干活,互不干扰(使用各自独立演化出的参数)。
  3. 拼接 (Concatenate):计算完后,把 8 个结果拼回成一个长条。
  4. 融合 (WOW^O):最后必须乘一个大的权重矩阵 WOW^O(它也是初始随机、后经训练调整的)。它的作用是把 8 个“专家”的意见统一整合成一个最终的输出向量。

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

到目前为止,我们所描述的模型还缺少一种方法来考虑输入序列中单词的顺序。

为了解决这个问题,Transformer 会给每个输入嵌入添加一个向量。这些向量遵循模型学习到的特定模式,这有助于模型确定每个词的位置,或者说序列中不同词之间的距离。其直观之处在于,将这些值添加到嵌入中,可以在嵌入向量投影为 Q/K/VQ/K/V 向量以及进行点积注意力机制时,提供有意义的距离。

为了使模型了解单词的顺序,添加了位置编码向量——其值遵循特定的模式

位置编码是直接 加 (+) 在词嵌入上的,而不是拼接(Concat),这是一个非常反直觉的设计

Final_Input=Embedding+Positional_Encoding\text{Final\_Input} = \text{Embedding} + \text{Positional\_Encoding}

假设:

  • 单词 “Je” 的 嵌入向量 (Embedding)[0.5, -0.1, 0.9, 0.2](4维)。
  • 位置 0 的 位置编码 (PE) 算出来是 [0.0, 0.0, 1.0, 1.0]

直接相加的操作:

Final=[0.5,0.1,0.9,0.2]+[0.0,0.0,1.0,1.0]=[0.5,0.1,1.9,1.2]\begin{aligned} Final &= [0.5, -0.1, 0.9, 0.2] \\ &+ [0.0, \phantom{-}0.0, 1.0, 1.0] \\ &= [\mathbf{0.5}, \mathbf{-0.1}, \mathbf{1.9}, \mathbf{1.2}] \end{aligned}

  • 结果:维度依然是 4 维。
  • 如果是拼接:维度会变成 8 维 [0.5, -0.1, 0.9, 0.2, 0.0, 0.0, 1.0, 1.0]

Transformer 坚持不增加维度,保持输入输出形状一致。

你可能会觉得,把“位置信息”加到“语义信息”上,就像把牛奶倒进可乐里,原来的可乐不就不纯了吗?但在高维数学空间(比如 512 维)里,情况不太一样。想象一下,如果 Embedding 的信息主要存储在奇数维度,而 Position 的信息主要存储在偶数维度(虽然实际没这么绝对,但模型可以学会这种分工)。

或者更准确地说,在 512 维的空间里,语义向量和位置向量往往处于两个几乎垂直(正交)的子空间里。

  • 直接相加后,它们虽然混合在了一起,但因为方向不同,后续的神经网络层(WQ,WKW^Q, W^K)可以通过矩阵乘法,像“棱镜分光”一样,轻松地把它们再次分离开。

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

在下图中,每一行对应一个向量的位置编码。因此,第一行是我们将添加到输入序列中第一个词的嵌入向量中的向量。每一行包含 512 个值,每个值介于 1 和 -1 之间。我们用颜色编码了它们,以便于观察其模式。

这是一个包含 20 个词(行)且嵌入大小为 512(列)的位置编码示例。可以看到,它似乎被中间一分为二。这是因为左半部分的值由一个函数(使用正弦函数)生成,右半部分的值由另一个函数(使用余弦函数)生成。然后,它们被连接起来形成每个位置编码向量。

这是早期代码实现(Tensor2Tensor 库)的一种处理方式——先算出所有的 Sin 值,再算出所有的 Cos 值,然后把它们拼接 (Concatenate) 在一起

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

这张图没有中间的分界线,频率变化是从左到右非常丝滑的。

  • 原因:这是论文中的标准做法。它将 Sin 和 Cos 交替穿插在一起。
  • 排列逻辑[Sin(高频), Cos(高频), Sin(次高频), Cos(次高频), ...]
  • 第 0 列:Sin(频率 1)
  • 第 1 列:Cos(频率 1)
  • 第 2 列:Sin(频率 2)
  • 第 3 列:Czos(频率 2)

对应公式

PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel) \begin{aligned} PE(pos, 2i) &= \sin(pos / 10000^{2i/d_{model}}) \\ PE(pos, 2i+1) &= \cos(pos / 10000^{2i/d_{model}}) \end{aligned}

  • 偶数维度 (2i2i):填入 Sin 值。
  • 奇数维度 (2i+12i+1):填入 Cos 值。

这种交织 (Interleaved) 设计不仅仅是为了好看,它利用了三角函数的数学性质(和差化积),使得模型能更容易地学到相对位置

  • 相对位置线性关系:对于任意偏移量 kk,位置 pos+kpos+k 的编码向量,可以表示为位置 pospos 向量的线性变换。这意味着模型很容易算出“单词 A 在单词 B 后面第 3 格”这种关系。

残差

在继续之前,我们需要提及编码器架构中的一个细节,即每个编码器中的每个子层(自注意力、ffnn)周围都有一个残差连接,并且之后会进行层归一化步骤。

如果要将与自注意力相关的向量和层范数运算可视化,它看起来会像这样:

残差连接 (Residual Connection) + 层归一化 (Layer Normalization)

A. Add(残差连接)—— 图中的虚线箭头

注意虚线箭头,直接绕过了 Self-Attention,把原始输入 XX 加到了输出 ZZ 上。

公式是:Output=X+SelfAttention(X)Output = X + SelfAttention(X)

  • 目的是什么?
    • 防止“忘本”:Self-Attention 虽然好,但它把输入的信息打碎重组了。有时候模型算晕了,可能会把原始信息弄丢。加上原始的 XX,相当于给模型留了一条“保底”的后路。
    • 解决梯度消失:这是深度学习界的“神技”(来自 ResNet)。它像一条高速公路,让梯度(Gradient)在反向传播时能无损地流回前面的层,这样你堆叠 100 层网络也能训练得动。

B. Normalize(层归一化)

  • 目的是什么?
    • 经过一顿矩阵乘法和加法,数值可能会变得忽大忽小。LayerNorm 把这些数字重新拉回到均值为 0、方差为 1 的范围内。
    • 这能让模型训练得更快、更稳定,不容易跑偏。

“Feed Forward” 是什么?(图中蓝色的方块)

这是 前馈神经网络 (Feed Forward Neural Network, FFN)

结构机制:由两个线性变换(Linear Transformation)和一个非线性激活函数(如 ReLU)组成。

  • 公式:FFN(x)=max(0,xW1+b1)W2+b2\text{FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2

  • 形象比喻

    • Self-Attention(开会):大家(单词)聚在一起,互相交流,搞清楚谁和谁有关系。比如 “Thinking” 明白了它和 “Machines” 是主谓关系。这只是“信息的交互”。
    • Feed Forward(消化):开完会后,每个人回到自己的工位上,独立思考,消化刚才听到的信息,把这些信息转化成更深层的语义理解。

这同样适用于解码器的子层。如果我们把一个由两个堆叠的编码器和解码器组成的 Transformer 看作是这样的:

解码器侧

现在我们已经了解了编码器方面的大部分概念,也基本了解了解码器的各个组成部分是如何工作的。接下来,让我们看看它们是如何协同工作的。

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

问题 1:顶层编码器的输出被转换为 K 和 V,这是怎么转化的?

既然 Encoder 输出的是 3 个向量(为了方便计算,我们把它打包叫 Memory 矩阵),那么在 Decoder 里生成 K 和 V 时,也是对这 3 个向量分别操作的

  1. Encoder 输出:3 个向量(Memory)。
  2. 生成 K
    • 第 1 个向量 ×WK\times W^K \rightarrow Key 1
    • 第 2 个向量 ×WK\times W^K \rightarrow Key 2
    • 第 3 个向量 ×WK\times W^K \rightarrow Key 3
    • 结果:生成了 3 个 Key
  3. 生成 V
    • 第 1 个向量 ×WV\times W^V \rightarrow Value 1
    • 第 2 个向量 ×WV\times W^V \rightarrow Value 2
    • 第 3 个向量 ×WV\times W^V \rightarrow Value 3
    • 结果:生成了 3 个 Value
  • Decoder 拿到的 K:是 3 个向量(对应那 3 个词的索引特征)。
  • Decoder 拿到的 V:是 3 个向量(对应那 3 个词的内容特征)。

问题2:顶层Encoder 输出的结果为什么有一部分输入到了“编码器-解码器注意力”,有一部分输入到了“解码器2”

不是“切分”(一部分给谁,一部分给谁),而是“广播”(Broadcast)或者说是“复用”

左侧绿色的 Encoder 堆叠结构中,只有最上面那一层(Top Encoder)的输出会被送出去。右侧粉色的 Decoder 也是由多层堆叠的(比如 Decoder #1, Decoder #2… 直到 #6)。 每一层 Decoder 都有一个中间的“Encoder-Decoder Attention”子层。

顶层 Encoder 输出的那个 Memory 矩阵,就像是一份“公共参考资料”。

  • 它被复制了 N 份(取决于 Decoder 有几层)。
  • Decoder #1 拿着这份资料,结合自己生成的 Query 查了一遍。
  • Decoder #2 拿着同一份资料,结合自己生成的 Query 又查了一遍。

以下步骤重复该过程,直到出现特殊情况。当到达某个符号时,表示Transformer解码器已完成输出。每个时间步的输出都会被送入下一个时间步的底层解码器,解码器会像编码器一样向上冒泡输出解码结果。与编码器输入的处理方式相同,我们也会在解码器输入中嵌入并添加位置编码,以指示每个单词的位置。

问题3:解码器的输入是动态的,但是Linear+Softmax只取当前位置的输出?

Decoder每次都要把所有历史记录重新嚼一遍,产出也越来越多,输入几个词,就吐出几个对应的预测向量。而Linear+Softmax它知道前面积累的向量都是为了铺垫,只有序列最末尾的那一个向量,才包含了“下一个词会是什么”的预测信息。所以它只切片取最后一个进行分类预测。

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

在解码器中,自注意力层只能关注输出序列中较早的位置。这是通过-inf在自注意力计算的softmax步骤之前屏蔽较晚的位置(将其设置为0)来实现的。

“编码器-解码器注意力”层的工作原理与多头自注意力类似,只是它从下一层创建查询矩阵,并从编码器堆栈的输出中获取键和值矩阵。

最终线性层和 Softmax 层

解码器栈输出一个浮点数向量。我们如何将其转换为单词?这就是最后一个线性层的任务,其后紧接着一个 Softmax 层。

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

假设我们的模型已经掌握了 10,000 个不同的英文单词(即模型的“输出词汇表”),这些单词是从训练数据集中学习到的。这样,logits 向量就有 10,000 个单元格——每个单元格对应一个单词的得分。这就是我们对模型输出以及后续线性层输出的解读方式。

softmax 层将这些分数转换为概率(全部为正数,总和为 1.0)。选择概率最高的单元格,并将与其关联的单词作为该时间步的输出。