Jay Alammar
一次讲清一个机器学习概念 · 中文译文 · 原文 2018-06-27

图解 Transformer

The Illustrated Transformer — 中文译文

译者说明:本文翻译自 Jay Alammar 的 CC BY-NC-SA 4.0 文章,插图来自原文站点。仅供学习,非商业用途。更完整、含动画的更新版见作者新书 LLM-book.com 第 3 章。
📘 2025 更新:作者已将本文扩展成书。书中第 3 章涵盖 Multi-Query Attention、RoPE 位置编码等七年来的演进。

上一篇文章里,我们看了注意力(Attention)——现代深度学习里无处不在的机制。Attention 显著提升了神经机器翻译的效果。本文聚焦 Transformer:一种用注意力大幅加速训练的模型。它在特定任务上超过了 Google 的神经机器翻译模型;更大的好处在于易于并行化——Google Cloud 也推荐用 Transformer 作为 Cloud TPU 的参考模型。下面我们把模型拆开,看它如何工作。

Transformer 出自论文 Attention Is All You Need。TensorFlow 实现见 Tensor2Tensor;哈佛 NLP 组有带 PyTorch 的逐段注解。本文会适度简化,按概念循序渐进,方便没有深厚背景的读者理解。

2025 更新:作者制作了免费短视频课程(含动画):

宏观视角

先把模型想象成一个黑盒:在机器翻译里,输入一种语言的句子,输出另一种语言的译文。

Transformer 黑盒:输入法语输出英语
Transformer 黑盒:输入一种语言,输出另一种语言

打开黑盒,可见编码器解码器以及二者之间的连接。

编码器与解码器

编码器是一叠 encoder(论文里堆了 6 层——数字 6 并无魔法,可实验其他深度);解码器是同样数量的 decoder 堆叠。

六层编码器与六层解码器

各 encoder 结构相同但权重不共享。每个 encoder 有两层子模块:

Encoder 结构

Decoder 也有这两层,中间还夹一层编码器-解码器注意力,帮助 decoder 聚焦输入句的相关部分(类似 seq2seq 里的 attention)。

Decoder 结构

把张量放进图景

了解主要组件后,看向量/张量如何在组件间流动,把训练好的模型的输入变成输出。

与常见 NLP 流程一样,先把每个输入词用 embedding 变成向量。

词嵌入
每个词嵌入为 512 维向量(图中用小方块表示)

Embedding 只在最底层 encoder 发生。更上层的 encoder 接收的是列表:每个元素是 512 维向量——底层是词嵌入,上层则是下一层 encoder 的输出。列表长度是超参数,通常取训练集最长句长。

嵌入后,每个词依次流过 encoder 的两层子模块。

张量在 encoder 中流动

Transformer 的一个关键性质:每个位置的词在 encoder 里有自己的路径。自注意力层里路径之间有依赖;FFN 没有,因此流过 FFN 时各路径可并行执行。

下面换一句更短的例子,看 encoder 每个子层里发生什么。

现在开始编码

Encoder 接收向量列表,先过自注意力,再过 FFN,再把输出传给上一层 encoder。

每个位置独立过自注意力与 FFN
每个位置:自注意力 → 同一 FFN(各向量单独流过)

自注意力:宏观理解

别被「自注意力」吓到——作者本人也是读到 Attention Is All You Need 才第一次接触。核心直觉如下。

假设输入句是:

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

句中的 it 指什么?街还是动物?对人简单,对算法不简单。

处理 it 时,自注意力让模型把它与 animal 关联起来。处理每个位置时,模型都可以看序列中其他位置,以得到更好的表示。

若熟悉 RNN:隐状态把「之前处理过的词」融入当前词。自注意力是 Transformer 用来把「其他相关词的理解」烘焙进当前词的方法。

编码 it 时关注 The Animal
在第 5 层 encoder 编码 it 时,注意力机制聚焦 The Animal,并将其表示的一部分融入 it 的编码

可在 Tensor2Tensor notebook 加载 Transformer 并交互查看注意力。

自注意力:计算细节

先看用向量怎么算,再看矩阵实现。

第 1 步:从每个输入向量(词嵌入)生成三个向量:Query(Q)Key(K)Value(V)——分别乘以训练得到的权重矩阵 WQ、WK、WV

这些向量维度是 64,而嵌入是 512——这是架构选择(便于多头注意力计算量大致恒定),并非必须更小。

Q K V 向量
每个词得到 query、key、value 三个投影

Q/K/V 是便于计算与思考的抽象;看完下面步骤就明白各自角色。

第 2 步:算分数。例如为第一个词「Thinking」算自注意力:用它的 query 与句中每个词的 key 做点积,得到「该位置应关注其他位置多少」的分数。

Q 与 K 点积得分

第 3、4 步:分数除以 8(key 向量维度 64 的平方根,使梯度更稳定),再经 softmax,得到非负且和为 1 的权重。

softmax 归一化

当前位置的词通常权重最高,但有时也需要关注其他相关词。

第 5 步:用 softmax 权重乘以各 value 向量——想保留的词权重大,无关词被压到接近 0。

第 6 步:加权求和 value,得到该位置自注意力层的输出。

加权求和 value

自注意力计算到此结束,结果向量送入 FFN。实际实现用矩阵批量计算,见下一节。

矩阵形式的自注意力

第 1 步:把嵌入打包成矩阵 X,乘以 WQ、WK、WV 得到 Q、K、V 矩阵。X 的每一行对应一个词。

X 与 W 相乘得 QKV

合并:第 2~6 步可写成一条公式:

Attention(Q,K,V) 矩阵公式
Attention(Q, K, V) = softmax(QKT / √dk) V

多头注意力(The Beast With Many Heads)

论文用多头注意力进一步改进自注意力,好处有二:

  1. 扩展模型关注不同位置的能力——例如译「it」指谁时,需要看清指代关系。
  2. 提供多个「表示子空间」——Transformer 用 8 个头,即 8 组 Q/K/V 权重矩阵,各自随机初始化,训练后把输入投影到不同子空间。
每个头独立的 QKV

对 8 组不同权重各算一遍自注意力,得到 8 个 Z 矩阵。

八个 Z 矩阵

FFN 只接受一个矩阵,因此把 8 个 Z 拼接后再乘以输出权重矩阵 WO

拼接后乘 WO
多头注意力总览
多头自注意力全流程一览

再看编码 it 时不同头关注哪里:一头主要关注 the animal,另一头关注 tired——it 的表示融合了二者信息。

两个注意力头
全部注意力头
所有头叠加后更难直观解读,但信息更丰富

用位置编码表示词序

到目前为止的模型还缺少对词序的显式建模。Transformer 给每个嵌入加上一个位置编码向量,按固定模式生成,帮助模型感知位置与距离。

嵌入加位置编码

若嵌入维度为 4,位置编码示意如下:

维度 4 的位置编码示例

真实模型里每行是一个位置的 512 维编码,数值在 -1 到 1 之间,热力图可见规律:

20 词 × 512 维位置编码
左半用 sin、右半用 cos 生成后拼接(论文 3.5 节公式)

2020 年 7 月更新:上图来自 Tensor2Tensor 实现;原论文用 sin/cos 交错而非左右拼接:

论文版位置编码

残差连接与层归一化

Encoder 每个子层(自注意力、FFN)外都有残差连接,并接层归一化(Layer Norm)

自注意力残差与 LayerNorm

Decoder 子层同理。2 层 encoder + 2 层 decoder 的完整结构示意:

两层堆叠
完整 Transformer 堆叠

解码器一侧

Encoder 侧概念掌握后,Decoder 组件类似;重点看它们如何协同。

Encoder 先处理输入序列;顶层 encoder 输出转为 K、V,供每个 decoder 的「编码器-解码器注意力」使用,让 decoder 聚焦输入的合适位置。

解码第一步
编码结束后进入解码:每步输出目标序列的一个元素

重复直到输出特殊符号 <eos>。每步输出喂给下一时间步的底层 decoder;decoder 输入同样要做嵌入 + 位置编码。

解码多步

Decoder 的自注意力与 encoder 略有不同:只能 attend 到当前位置之前的输出——通过对未来位置 mask(设为 -inf)再 softmax 实现。

「编码器-解码器注意力」与多头自注意力类似,但 Q 来自下层 decoder,K/V 来自 encoder 栈输出。

最后的线性层与 Softmax

Decoder 栈输出浮点向量。如何变成词?经线性层Softmax

线性层把 decoder 输出投影到很大的 logits 向量。假设词表有 10,000 个英文词,logits 就有 10,000 维,每维对应一个词的分。

Softmax 把分数变成概率(非负、和为 1),取概率最大的词作为该步输出。

线性层与 softmax 输出词

训练回顾

完整前向传播讲完后,简要看训练直觉。未训练模型走同样前向,但有标签可对比输出。

假设词表只有 6 个词:aamithanksstudent<eos>(句末)。

输出词表

每个词用同样宽度的 one-hot 向量表示,例如 am

one-hot 示例

损失函数

训练第一步:把 merci 译成 thanks。我们希望输出分布在 thanks 上概率最高,但未训练模型只会给出随机分布。

随机输出 vs 标签
随机初始化 → 任意分布;与标签对比后用反向传播调整权重

比较两个概率分布常用交叉熵或 KL 散度(文中简化为相减示意)。

更 realistic 的例子:输入 je suis étudiant,期望输出 i am a student。模型应逐步输出分布,使第 1 步峰值在 i,第 2 步在 am……第 5 步在 <eos>

目标概率分布

充分训练后,希望分布接近目标:

训练后的分布

推理时可每步取概率最大的词(贪心解码),或保留 top-k 候选并行扩展(束搜索 beam search,如 beam_size=2 同时保留两条未完成假设)。二者都是可调超参数。

继续深入

若本文帮你入门 Transformer,建议:

致谢

感谢 Illia Polosukhin、Jakob Uszkoreit、Llion Jones、Lukasz Kaiser、Niki Parmar、Noam Shazeer 对原文草稿的反馈。