深入理解 Transformer:从数据流动看模型架构

论文链接:Attention Is All You Need

这篇论文提出了 Transformer 模型架构,这是一个序列到序列的模型,在论文中被应用于英德和英法两个机器翻译任务。

由于 Transformer 被用于翻译任务,整个模型的输入维度是 (batch_size, seq_len)​,输出维度是 (batch_size, seq_out_len)。模型采用 Encoder-Decoder 架构,下面是模型的整体结构图:

这里的维度指的是 Tokenization(分词)后的数据,详见后面描述

Encoder 端的数据流动

让我们从输入端开始,逐步追踪数据在 Encoder 中的流动过程。

首先是 Tokenization(分词) 阶段。虽然模型示意图中没有明确标出这一步,但实际应用中它是必不可少的。Tokenization 的作用是将无限可能的文本流转化为一个有限的词表。输入是一个 (batch_size,)​ 的数组,每个元素是一个输入句子的字符串,经过分词后输出就是我们所说的 inputs,维度变为 (batch_size, seq_len),其中第二维表示每个字词在词表中的索引。

接下来是 Input Embedding(词嵌入) 层。这一层的作用是将离散的索引映射为稠密的向量表示。经过这一步,数据维度从 (batch_size, seq_len)​ 扩展为 (batch_size, seq_len, d_model)​,其中 d_model 是模型的隐藏维度,第三维就是每个字词的向量表示。

然后是 Positional Encoding(位置编码) 。这篇论文采用的是绝对位置编码,使用正弦和余弦函数来生成位置信息。为什么需要位置编码呢?因为注意力机制会一次性并行处理句子中的所有字词,这个过程本身是没有顺序概念的,也就是说它是置换不变的(permutation invariant)。如果不添加位置信息,模型就无法区分"我爱你"和"你爱我"这样的句子。位置编码会直接加到词嵌入上,因此不改变数据的维度,依然保持 (batch_size, seq_len, d_model)

最后,数据会经过 6 层堆叠的 Encoder 层 。每一层 Encoder 都包含多头自注意力机制和前馈神经网络,但无论经过多少层,数据的维度始终保持为 (batch_size, seq_len, d_model)。Encoder 的最终输出会被共享给所有 6 个 Decoder 层,作为它们各自交叉注意力(Cross-Attention)的输入源。

这里有个值得注意的点:虽然所有 Decoder 层都使用同一个 Encoder 输出作为原始输入,但每个 Decoder 层会用自己独立的权重矩阵将这个输出投影成 K 和 V。因此,虽然输入源相同,但经过各自的线性变换后,每层实际使用的 K 和 V 是不同的。而 Q 则来自 Decoder 自身的隐藏状态,每一层的 Q 也都不同。这种设计让每个 Decoder 层能从不同角度关注输入序列的信息。

Decoder 端的数据流动

现在让我们看看 Decoder 端的数据流动。这里有个重要的概念需要理解:outputs 在训练和推理时扮演着不同的角色。

在训练阶段,outputs 有双重身份。一方面,它作为模型的输入,这种方式叫做 Teacher Forcing,即把正确答案喂给模型;另一方面,它也作为标签用于计算损失。而在推理阶段,模型采用自回归的方式逐个生成 token,每次预测一个词,然后把预测结果作为下一步的输入。

和 inputs 类似,outputs 最初也是一个 (batch_size,)​ 的字符串数组,每个元素是目标语言的句子。经过 Tokenization 后变为 (batch_size, seq_len)。这里需要特别注意:由于输入是英语,输出是法语或德语,因此输入和输出使用的是完全不同的词表和分词器。

接下来的流程和 Encoder 端类似。经过 Output EmbeddingPositional Encoding 后,数据维度变为 (batch_size, seq_len, d_model)

然后数据进入 6 层堆叠的 Decoder 层 。每一层 Decoder 包含三个子模块:首先是 Masked Self-Attention,它会遮蔽未来的信息,防止模型在预测当前词时"偷看"后面的词;然后是 Cross-Attention,这里 Decoder 会与 Encoder 的输出进行交互;最后是 Feed-Forward Network。经过这 6 层后,数据维度依然是 (batch_size, seq_len, d_model),但此时的表示还没有对应到具体的词表。

为了得到最终的预测结果,需要经过 Linear 层 。这一层将隐藏状态映射到词表空间,输出维度变为 (batch_size, seq_len, vocab_size)​,表示每个位置对应词表中每个词的得分(logits)。但这些得分还不是概率,因此需要通过 Softmax 层 进行归一化,将 logits 转换为概率分布。最终输出依然是 (batch_size, seq_len, vocab_size),但此时每个值表示的是某个批次中某个位置预测为某个词的概率。

Encoder 内部的数据维度变化

现在让我们深入到单个Encoder层内部,看看数据是如何流动和变化的。每个Encoder层主要包含两个子模块:多头自注意力(Multi-Head Self-Attention)和前馈神经网络(Feed-Forward Network),每个子模块后面都有残差连接和层归一化。

假设输入到某一层Encoder的数据维度是 (batch_size, seq_len, d_model),我们来追踪它的变化过程。

首先进入 多头自注意力模块 。这个模块会将输入分别通过三个线性变换生成Q、K、V,每个的维度都是 (batch_size, seq_len, d_model)​。然后这些向量会被切分成多个头,假设有h个头,那么每个头的维度就变成 (batch_size, seq_len, d_model/h)​。每个头独立计算注意力,计算完后再拼接回来,维度重新变为 (batch_size, seq_len, d_model)。最后经过一个输出投影矩阵,维度保持不变。

接下来是 残差连接和层归一化 。残差连接就是把注意力模块的输入和输出相加,这要求它们维度必须相同,都是 (batch_size, seq_len, d_model)。然后进行层归一化,维度依然不变。

然后数据进入 前馈神经网络(FFN) 。这是一个两层的全连接网络,第一层会将维度扩展到 (batch_size, seq_len, d_ff)​,其中 d_ff​ 通常是 d_model​ 的4倍(论文中是2048,而d_model是512)。经过ReLU激活函数后,第二层再将维度压缩回 (batch_size, seq_len, d_model)

最后又是一次 残差连接和层归一化 ,将FFN的输入和输出相加后归一化,最终输出维度依然是 (batch_size, seq_len, d_model)

可以看到,虽然Encoder内部经历了多次维度变化(特别是在多头注意力的头切分和FFN的维度扩展),但由于残差连接的存在,每一层的输入和输出维度必须保持一致,都是 (batch_size, seq_len, d_model)。这也是为什么6层Encoder堆叠后,数据维度始终不变的原因。

Decoder 内部的数据维度变化

Decoder层的结构比Encoder更复杂一些,它包含三个子模块:掩码多头自注意力(Masked Multi-Head Self-Attention)、交叉注意力(Cross-Attention)和前馈神经网络(Feed-Forward Network)。

假设输入到某一层Decoder的数据维度是 (batch_size, seq_len, d_model)​,同时从Encoder接收的输出也是 (batch_size, seq_len_enc, d_model)​(注意这里的seq_len_enc是输入序列的长度,可能与输出序列长度不同)。

首先是 掩码多头自注意力模块 。这个模块的处理方式和Encoder中的自注意力基本相同,也是生成Q、K、V,切分成多个头,计算注意力,再拼接回来。唯一的区别是这里使用了掩码(mask),防止当前位置看到未来的信息。整个过程中,输入和输出的维度都是 (batch_size, seq_len, d_model)。然后经过残差连接和层归一化,维度保持不变。

接下来是 交叉注意力模块 。这里的Q来自上一步的输出,维度是 (batch_size, seq_len, d_model)​;而K和V来自Encoder的输出,维度是 (batch_size, seq_len_enc, d_model)。注意这里的关键点:Q的序列长度是输出序列长度,K和V的序列长度是输入序列长度,它们可以不同。

在交叉注意力的计算中,Q会与K做点积得到注意力权重,维度是 (batch_size, seq_len, seq_len_enc)​,表示输出序列的每个位置对输入序列每个位置的关注程度。然后用这个权重对V进行加权求和,最终输出的维度是 (batch_size, seq_len, d_model)​,序列长度跟随Q,也就是输出序列的长度。经过输出投影、残差连接和层归一化后,维度依然是 (batch_size, seq_len, d_model)

最后是 前馈神经网络 ,这部分和Encoder完全一样。先扩展到 (batch_size, seq_len, d_ff)​,再压缩回 (batch_size, seq_len, d_model),然后经过残差连接和层归一化。

总结一下,Decoder层的输入和输出维度都是 (batch_size, seq_len, d_model)​,但在交叉注意力中会与Encoder输出 (batch_size, seq_len_enc, d_model) 进行交互。这种设计让Decoder既能关注自己已经生成的内容(通过掩码自注意力),又能关注输入序列的信息(通过交叉注意力)。

关键理解要点

整个数据流动的维度变化链路可以总结为:索引 (B, L)​ → 嵌入 (B, L, D)​ → 编码 (B, L, D)​ → 词表 (B, L, V)​ → 概率 (B, L, V)

位置编码之所以必要,是因为注意力机制本身不包含任何位置信息。如果没有位置编码,模型就无法区分词序,"猫吃鱼"和"鱼吃猫"对模型来说是一样的。

Encoder 和 Decoder 的交互方式是:Encoder 的输出作为 Decoder 每一层交叉注意力的 K 和 V,而 Q 则来自 Decoder 自身的隐藏状态。这种设计让 Decoder 能够关注到输入序列的相关信息。

最后,训练和推理的方式有本质区别。训练时使用 Teacher Forcing,可以并行处理整个目标序列,效率很高;而推理时必须自回归地逐个生成 token,每次只能预测一个词,因此速度较慢。