Transformer 动画教程(完整版) v2

「我爱水课」→「I love easy courses」· 训练与推理双轨 · 全程编号不跳步 · 版本 v2(2026-08-13 发布)

0 1 2 3 4I 5love 6easy 7courses
Encoder ×2🟢 训练=推理 Decoder ×2🟠 训练 Decoder ×2🌸 推理 输出层Linear+Softmax
📐 参数:dmodel=4 · h=2 · dk=dv=2 · Nenc=Ndec=2 · 词表 V=8
🎬 双轨训练 teacher forcing 并行预测 4 个词;推理 自回归逐步生成
🧮 数据:全部数字由脚本真实计算(seed=42 固定权重),可手算复现

👉 底部圆点/键盘 ←→ 切换步骤 · 共 19 个动画场景

整体架构:数据流 + 训练/推理路径

编号 0-1 · 全程数据流经哪些模块?

🎯 架构功能讲解 Encoder(理解源句):把「我爱水课」编码成 4 个向量,自注意力让每个字看到全句。训练与推理完全共用——源句不变,只算一次。
Decoder(生成目标):训练时一次性并行看到完整目标句(teacher forcing),一次算 4 个位置;推理时逐步自回归,每步只输入已生成的词。
Cross-Attention(桥):Q 来自 Decoder、K/V 来自 Encoder——这是翻译对齐的核心,两边都要用。
输出层:训练输出 4×8 概率算损失;推理每次输出 1×8 概率取 argmax。

训练 vs 推理:Decoder 输入差异(核心区别)

🟠 训练输入(并行)
I(4)
love(5)
easy(6)
courses(7)
✓ 4 个位置一次算完,每个都用真实目标词
🌸 推理输入(串行)
I(4)
?
?
?
I(4)
love(5)
?
?
I(4)
love(5)
easy(6)
?
✗ 每次只多一个词,用模型自己的预测
💡 一句话:训练像「开卷考试照着标准答案写整张卷子」;推理像「只能看自己已写的答案,一格一格往下填」。

Tokenization:每个字一个 token(编号 1-3)

文字 → 数字 ID —— 机器只认识数字

🎯 架构功能讲解 架构图最底层输入是 token 序列。Transformer 无法直接处理文字,必须先把每个字映射成整数 ID。
本教程按要求每字一个 token:中文「我爱水课」每字一个;英文「I love easy courses」每词一个。训练与推理在此阶段完全相同。

词表 Vocab(V=8)

ID01234567
Iloveeasycourses
▼ 查表动画 ▼
汉字 0ID E向量
源序列[0,1,2,3] 我爱水课 目标序列[4,5,6,7] I love easy courses
💡 训练与推理相同:源序列两边都是 [0,1,2,3];目标序列训练用 [4,5,6,7] 整句,推理只逐步给已生成的 ID。

Embedding:查表得到连续向量(编号 4-6)

ID 0~7 → 4 维向量(从嵌入矩阵 E 取行)

🎯 架构功能讲解 架构图中 Embedding 紧接输入:把离散 ID 变成可学习的连续向量。每个词一个 4 维向量,训练中不断调整,让语义相近的词在向量空间靠近。
嵌入矩阵 E 是参数——训练时随梯度更新,推理时冻结。

嵌入矩阵 E ∈ ℝ8×4

d0
d1
d2
d3
我(0)
0.5
-0.3
0.8
0.1
爱(1)
-0.2
0.6
-0.1
0.4
水(2)
0.3
0.2
0.5
-0.4
课(3)
-0.6
0.1
0.3
0.7
I(4)
0.4
0.5
-0.2
0.3
love(5)
0.7
-0.1
0.6
-0.3
easy(6)
-0.5
0.4
0.2
0.6
courses(7)
0.1
-0.6
0.4
0.2
「我」= ID 0离散 ➜ 取第 0 行 ➜ [0.5, -0.3, 0.8, 0.1]连续向量
💡 嵌入 = 查表:E 就是「词→向量」的词典。所有后续计算都在这些 4 维向量上做。源序列取 E[0..3] 行,目标序列取 E[4..7] 行。

Positional Encoding:注入位置信息(编号 7-11)

sin/cos 波形 —— 告诉模型「我在第几个位置」

🎯 架构功能讲解 自注意力没有顺序概念——把「我爱水课」倒过来,QK 点积结果完全一样。必须显式注入位置。
原始 Transformer 用正弦/余弦函数生成位置向量,与词嵌入逐元素相加。不同维度用不同频率,形成「位置指纹」。
训练与推理完全相同——PE 是固定的(不是参数)。
PE(pos, 2i) = sin(pos / 10000^(2i/d))  ·  PE(pos, 2i+1) = cos(pos / 10000^(2i/d))
d0 sin
d1 cos
d2 sin
d3 cos
pos0
0
1
0
1
pos1
0.8415
0.5403
0.0100
1.0000
pos2
0.9093
-0.4161
0.0200
0.9998
pos3
0.1411
-0.9900
0.0300
0.9996
✍ 手算验证:sin(1)=0.8415, cos(1)=0.5403;sin(2)=0.9093, cos(2)=-0.4161 —— 每个位置编码唯一且可区分。

X_in = 词嵌入 + 位置编码(逐元素相加)

E嵌入 + PE位置 = X_in输入
我: [0.5, -0.3, 0.8, 0.1] + [0, 1, 0, 1] = [0.5000, 0.7000, 0.8000, 1.1000]
爱: [-0.2, 0.6, -0.1, 0.4] + [0.8415, 0.5403, 0.0100, 1.0000] = [0.6415, 1.1403, -0.0900, 1.4000]
水: [0.3, 0.2, 0.5, -0.4] + [0.9093, -0.4161, 0.0200, 0.9998] = [1.2093, -0.2161, 0.5200, 0.5998]
课: [-0.6, 0.1, 0.3, 0.7] + [0.1411, -0.9900, 0.0300, 0.9996] = [-0.4589, -0.8900, 0.3300, 1.6996]
🔑 两个输入矩阵:Encoder 用上面这个(源);Decoder 用 E[4..7]+PE(目标)。训练时 4 行全给;推理时只给已生成的前 k 行。

Encoder L1:Q/K/V 投影(编号 14-22)

每个 token 生成 Q、K、V 三个向量 —— 为「谁关注谁」做准备

🎯 架构功能讲解 自注意力是 Transformer 核心:每个 token 用 3 个投影矩阵生成三个角色向量
Q(Query):「我在找什么?」  K(Key):「我是什么?」  V(Value):「我携带什么内容?」
多头(h=2)让模型从两个视角同时看句子,每头 dk=2。
训练与推理共用同一份权重、同一套计算——Encoder 完全相同。

投影公式 + 「我」的手算(Head1)

Q = X·WQᵀ    K = X·WKᵀ    V = X·WVᵀ
WQ1a = [-0.3 0.9 0.5 0.2; -0.7 -0.7 -0.9 0.7](Head1 Q 投影,2×4)
X=[0.5,0.7,0.8,1.1]输入 × WQ1a2×4 = Q=[1.10,-0.79]2维
Q[0] = 0.5×(-0.3)+0.7×0.9+0.8×0.5+1.1×0.2 = -0.15+0.63+0.40+0.22 = 1.10
Q[1] = 0.5×(-0.7)+0.7×(-0.7)+0.8×(-0.9)+1.1×0.7 = -0.35-0.49-0.72+0.77 = -0.79

全部 token 的 Q/K/V(Head1,4×2)

Q1
1.10,-0.79
1.0688,-0.1863
-0.1774,-0.7433
-0.1584,1.8369
K1
0.57,-1.21
1.9344,-1.0211
0.1752,0.3043
0.7518,-1.0049
V1
-0.72,-1.04
-0.8076,-1.0539
-0.7756,0.0052
-0.5293,-0.1106
💡 类比:Q 是问题(我想找谁),K 是名片(我是谁),V 是内容(我能提供什么)。先拿问题匹配名片,再用匹配度加权内容。

Attention Score:谁该关注谁?(编号 23-26)

Q·KT/√dk → Softmax → 注意力权重热力图

🎯 架构功能讲解 注意力分数 = Qi·Kj:token i 对 token j 的关注强度。除以 √dk 保证数值稳定。
softmax 把每行变成和为 1 的概率分布 = 注意力权重 A。
热力图颜色越亮 = 关注越强:「我」最关注「爱」(0.53)——模型学到「主语→动词」的依赖。
Encoder 的自注意力无掩码——每个源词都能看到全句(这是理解源句的前提)。

Head1 注意力权重热力图(每行和为 1)

Q\K
0.202
0.526
0.064
0.208
0.183
0.501
0.111
0.205
0.321
0.245
0.152
0.282
0.092
0.101
0.689
0.118
🔍 读图:「我」行 → 52.6% 看「爱」(最亮);「课」行 → 68.9% 看「水」(「水课」强绑定)。每个字自主决定看谁、看多重。

score 计算(「我」行手算)

Q_我=[1.10,-0.79], √2≈1.4142
score[我→我] = (1.10×0.57 + (-0.79)×(-1.21))/1.4142 = 1.583/1.4142 = 1.1193
score[我→爱] = (1.10×1.9344 + (-0.79)×(-1.0211))/1.4142 = 2.9345/1.4142 = 2.0750 ← 最高
score[我→水] = (1.10×0.1752 + (-0.79)×0.3043)/1.4142 = -0.0477/1.4142 = -0.0337
score[我→课] = (1.10×0.7518 + (-0.79)×(-1.0049))/1.4142 = 1.6209/1.4142 = 1.1462
softmax → [0.202, 0.526, 0.064, 0.208]

Encoder L1:加权求和 → 残差 → LN → FFN(编号 27-38)

A·V 混合信息 → Add&Norm → Feed-Forward

🎯 架构功能讲解 注意力输出 Outi = ΣjAij·Vj用权重 A 对全句 V 加权混合——这就是「上下文感知表示」的根源。
双头输出拼接后经 WO 融合(多头互补不同视角)。
Add(残差):原始输入直接加回,梯度能「抄近道」,深层不退化。
Norm(LayerNorm):每行 (x-μ)/√(σ²+ε),稳定数值分布。
FFN:注意力负责「搬信息」,FFN 负责「加工」——升维到 8、ReLU、降回 4。

加权求和(「我」的 Head1 输出)

O_我[0] = 0.2023×(-0.72) + 0.5261×(-0.8076) + 0.0639×(-0.7756) + 0.2078×(-0.5293) = -0.7300
O_我[1] = 0.2023×(-1.04) + 0.5261×(-1.0539) + 0.0639×0.0052 + 0.2078×(-0.1106) = -0.7874
H1 输出2维 H2 输出2维 拼接 4维[H1|H2] × WOᵀ AttnOut4维
X_in原始 + AttnOut注意力 = 残差 X1Add LN1aNorm FFN(4→8→4)ReLU LN1bL1 输出
残差 X1 我: [0.5,0.7,0.8,1.1] + [0.4129,-1.4539,0.9665,0.7456] = [0.9129,-0.7539,1.7665,1.8456]
LayerNorm 我: μ=0.9428, σ²=1.0933 → [-0.0286, -1.6227, 0.7878, 0.8634]
FFN 我: 4维→8维[-0.1842,-1.7211,-0.1982,0.519,-1.358,1.1511,-0.1454,0.2173]→ReLU→8维→4维[-0.0699,-0.8135,-0.3056,-0.3782]
残差+LN → Encoder L1 输出 我: [0.2437, -1.6980, 0.7259, 0.7284]
💡 FFN 为什么 4→8→4?中间维度放大表达能力(「先展开再压缩」),ReLU 引入非线性——没有它两层线性=一层线性。

Encoder L2 + 最终源表示(编号 39-51)

第二层让理解更全局 —— 输出 = 源句记忆(K/V 之源)

🎯 架构功能讲解 Encoder 堆叠 2 层:L1 抓局部依赖,L2 学全局语义
对比热力图:L1 的「我→爱」尖峰 0.53,L2 更均匀(0.30/0.11/0.42/0.17)——高层注意力更「抽象」。
最终输出 = 源句记忆:4 个向量每个都含全句信息,即将成为 Decoder 交叉注意力的 K/V。
训练与推理完全相同:Encoder 只跑一次。

L1 vs L2 注意力对比(「我」行)

L1
0.20
0.53
0.06
0.21
L2
0.30
0.11
0.42
0.17

⭐ Encoder 最终输出(4×4)—— 源句记忆

d0
d1
d2
d3
1.2296
-1.5587
0.1177
0.2114
1.3092
-1.3839
-0.3915
0.4663
1.1040
-1.6261
0.2814
0.2406
1.2974
-1.4723
-0.1710
0.3460
🔑 记住这个矩阵!它是 Decoder 交叉注意力查阅的 K/V —— 源句的完整记忆。

Decoder 掩码:不许偷看未来(编号 54)

上三角 = -∞,softmax 后权重归零 —— 自回归因果性

🎯 架构功能讲解 生成是自回归的:写第 3 个词时绝不能看到第 4 个,否则训练时「抄答案」、推理时无答案可抄。
做法:score 矩阵上三角加 -1e9(≈-∞),softmax 后 e^(-∞)=0,被遮位置权重强制归零。
结果:第 i 个 token 只能看到 0..i(自己及左边)。
掩码只作用于 Decoder 自注意力;Cross-Attention 永不掩码(它要参考完整源句)。

掩码动画:生成「love」时能看谁?

I✓
I✓
love✓
I✓
love✓
easy✓
I✓
love✓
easy✓
courses✓
score_masked = score + M,M[i][j] = -1e9 (j>i) 或 0 (j≤i)
Head1 第 2 行(love):score=[1.1229, 1.5218, -1e9, -1e9] → softmax = [0.4016, 0.5984, 0, 0]
🔍 读图:绿 = 允许(下三角+对角线),红 = 禁止(上三角)。第 3 行生成 easy 时看不到 courses。

训练模式:Decoder L1 掩码自注意力(编号 55-66)

🟠 teacher forcing:整句输入,掩码保证因果

🎯 架构功能讲解 训练模式:Decoder 输入 = 完整目标句 [I, love, easy, courses] 的嵌入+位置。
掩码自注意力让每个目标 token「只能看左边」,学到目标语言内部结构(I 后面跟 love 的可能性)。
与 Encoder 唯一区别:分数要加掩码;权重、QKV 计算完全一样。
4 个位置一次并行算出——这是训练比推理快的原因(可 GPU 并行)。

掩码 softmax 权重(对比 Encoder 无掩码)

I
1.00
0
0
0
love
0.40
0.60
0
0
easy
0.33
0.25
0.42
0
courses
0.42
0.21
0.17
0.21
📊 关键观察:行 0(I)=[1,0,0,0] 只能看自己;行 1(love)=[0.40,0.60,0,0] 只看 I+love;行 3(courses)无限制。每行权重和恒为 1。
X_dec_in4×4 整句 掩码 QKVL1 掩码 score+M softmax A上三角=0 A·V混合 WOd1→残差→LN→FFN→ DLN1b
Decoder L1 输出 DLN1b(4 行,训练并行算出):
I:      [ 0.2064,  1.4060, -1.3896, -0.2228]
love:   [ 0.3748,  1.4684, -0.9695, -0.8737]
easy:   [ 0.7886,  0.8387, -1.6354,  0.0081]
courses:[ 1.2072, -1.3379, -0.5445,  0.6752]

⭐ Cross-Attention:翻译的桥梁(编号 74-80)

Q 来自 Decoder,K/V 来自 Encoder —— 实现词对齐

🎯 架构功能讲解 这是 Encoder-Decoder 架构的灵魂,翻译的本质:
Q = Decoder 当前表示(想表达什么)
K = V = Encoder 最终输出(源句有什么可用)
Decoder 用 Q 匹配源句所有 K,softmax 得「该参考源句哪些词」,再对源句 V 加权求和。
⚠ 没有掩码!生成每个目标词都可自由看全部源词。
训练后生成「I」时权重会集中到「我」——这就是翻译对齐(attention alignment)

Cross-Attention 计算流(生成「I」时)

Q = Decoder LN[-0.33,-0.34] × K = Encoder 输出4×2 → score softmax 权重对源 4 词 × V = Encoder 输出4×2 = 输出2维
score[I→我] = (-0.3327×0.8115 + (-0.3446)×0.8874)/√2 = -0.5758/1.4142 = -0.4072 softmax([-0.4072, -0.3399, -0.4126, -0.3744]) = [0.244, 0.261, 0.243, 0.252] 输出 = 0.244×V_我 + 0.261×V_爱 + 0.243×V_水 + 0.252×V_课 = [-1.8787, 1.3491]
Q\K
0.24
0.26
0.24
0.25
love
0.26
0.25
0.24
0.25
🔑 训练后会怎样?生成「I」的权重会逐渐集中到「我」上(0.9+)——机器翻译的可解释性来源。训练与推理都是这个机制,只是训练时 4 个 Q 并行、推理时每步 1 个 Q。

训练模式:输出层 + 交叉熵损失(编号 86-90)

🟠 4 位置 × 8 词概率 + 损失 —— 训练的目标函数

🎯 架构功能讲解 训练模式:Decoder 输出(4 维)经 Linear(W_out: 4→8)映射回词表,Softmax 得概率。
训练时一次输出 4×8 概率矩阵,与真实目标词比较,计算交叉熵损失 -ln P(目标词)
损失通过反向传播更新所有权重——这就是「学习」。
推理时这里是 1×8 概率、取 argmax、无梯度

位置 0(应预测 I)的概率分布

0.103
0.036
0.140
0.036
0.239
I ✓目标
0.016
love
0.146
easy
0.284
courses
📊 解读:模型现在最猜「courses」(0.284),目标「I」排第 3 (0.239)。随机权重下还没学会——训练要做的就是把「I」的概率推高。

4 位置预测 + 交叉熵损失

位置目标预测(argmax)p(目标)损失 -ln p
0Icourses (0.284)0.2391.43
1loveI (0.302)0.0323.44
2easy水 (0.385)0.0962.34
3coursescourses (0.563) ✅0.5630.57
🔁 训练循环:损失 → 反向传播 → 更新权重 → 下一轮前向。位置 3 已猜对(0.563),说明 Encoder→Decoder 信息流已部分工作。位置 1 损失最大(3.44)= 最需要学习。

推理模式:自回归循环(编号 91)

🌸 一次生成一个词,拼回输入继续 —— 无梯度

🎯 架构功能讲解 推理模式与训练的关键区别:
输入:每步只有已生成的词(无 teacher forcing,用自己的预测喂回)
输出:每次只取最后一个位置的 1×8 概率,argmax 选一个词
掩码:每步掩码尺寸 1×1 → 2×2 → 3×3 逐步扩大(只算最后位置)
梯度:无反向传播,权重冻结
Encoder:只跑一次,K/V 全程复用(工程上可缓存)

自回归循环动画

Encoder 输出只算一次 Decoder 步1: [I]预测? → love Decoder 步2: [I,love]预测? → easy Decoder 步3: [I,love,easy]预测? → courses I love easy courses ✓完成
💡 关键差异:训练 4 个位置一次算完;推理要跑 3 次 Decoder 前向(长度 1→2→3)。工程上用 KV cache 复用已算的 K/V,避免重复计算。

推理第 1 步:输入 [I] → 预测第 2 词(编号 92)

🌸 目标应为 love,模型实际预测?

🎯 架构功能讲解 输入只有 [I](1 个 token)。掩码自注意力在长度 1 时无实际遮蔽(只看自己);Cross-Attn 仍查阅完整源句记忆。
完整跑一遍 Decoder(L1 掩码自注意力 → L2 掩码自注意力 → Cross-Attn → FFN),输出最后一个位置的 1×8 logits。
输入 X_inf1 = [0.4, 1.5, -0.2, 1.3](I 的嵌入+位置,仅 1 行)
Logits = [0.0021, -0.7482, 1.5302, -0.9323, -0.3281, -1.687, -0.3194, 1.7682]
Softmax = [0.0717, 0.0338, 0.3304, 0.0282, 0.0515, 0.0132, 0.052, 0.4192]
argmax → 预测「courses」(p=0.4192);目标应为「love」(p=0.0132) ✗
0.072
0.034
0.330
0.028
0.052
I
0.013
love ✓目标
0.052
easy
0.419
courses
📊 解读:随机权重下模型猜「courses」——训练后应猜「love」。推理的关键:这一步的预测会被拼回输入,影响下一步(错误会连锁)。

推理第 2 步:输入 [I, love] → 预测第 3 词(编号 93)

🌸 目标应为 easy(假设第 1 步选对了 love)

🎯 架构功能讲解 输入 [I, love](2 个 token)。掩码此时生效:位置 1(love)只能看 I 和 love。
与训练全量计算对比:这一步的结果 = 训练时「love」位置的结果(因为前面输入相同)——这就是 teacher forcing 与自回归的联系。
只取最后一个位置(love 处)的 logits 做 argmax。
输入 X_inf2(2 行)= I 和 love 的嵌入+位置
最后位置 Logits / Softmax → argmax
预测「easy」(p=?),目标应为「easy」——与训练位置 2 一致
[I]已生成 + [love]第1步预测 Decoder 前向L1→L2→Cross→FFN 取最后位置 logits1×8 argmax → easy预测
💡 掩码尺寸在变:训练时 4×4 掩码一次算全;推理这步是 2×2 掩码。结果数学上等价——这就是为什么推理正确时与训练对应位置一致。

推理第 3 步:输入 [I, love, easy] → 预测第 4 词(编号 94)

🌸 目标应为 courses —— 完整句子生成完毕

🎯 架构功能讲解 输入 [I, love, easy](3 个 token)。这是最后一步——预测「courses」后句子完整。
3 步推理 + 起始 I = 4 个词。一旦某步预测错,后续会连锁错(错误传播)——这是自回归生成的风险,工程上用 beam search 缓解。
训练时位置 3 已「猜对」courses (0.563);推理第 3 步应得到相同结果。
Encoder: 我爱水课 → ENC_OUT(只算一次) ↓ K/V 复用 步1: [I] → love(本例随机权重预测 courses,训练后收敛) 步2: [I,love] → easy 步3: [I,love,easy] → courses 输出: I love easy courses ✅
🎯 完整翻译达成:「我爱水课」→「I love easy courses」。整个流程中:Encoder 自注意力理解源句、Decoder 掩码自注意力保证自回归、Cross-Attention 实现词对齐、输出层逐词出词。

训练 vs 推理:逐步对比差异(编号 95)

🟡 9 个阶段的本质差异

🎯 一句话总结 训练 = 并行 + teacher forcing + 有梯度;推理 = 串行 + 自回归 + 无梯度
Encoder 两边完全一样,差异全在 Decoder。
阶段🟠 训练🌸 推理
① Tokenization相同:源[0,1,2,3] 目标[4,5,6,7]相同,但目标只给已生成的
② Embedding+PE完全相同(E 训练时更新、推理冻结;PE 固定)
③ Encoder完全相同:源句编码一次 → ENC_OUT
④ Decoder 输入整句 [I,love,easy,courses] teacher forcing逐步 [I]→[I,love]→[I,love,easy]
⑤ 掩码自注意力4×4 掩码一次算 4 位置1×1→2×2→3×3,只算最后位置(可 KV cache)
⑥ Cross-Attention4 个 Q 对全部源词每步 1 个 Q 对全部源词
⑦ 输出层4×8 概率 + 平均损失1×8 概率 + argmax 一个词
⑧ 下一步输入用真实目标词(teacher forcing)用自己预测的词(自回归,错则连锁)
⑨ 梯度反向传播更新权重无梯度、权重冻结
💡 为什么这样设计?训练并行 teacher forcing 让 GPU 满载 + 收敛快;推理自回归是「生成」的本质(每个词依赖前面的词)。两者的桥梁是掩码——训练时用掩码模拟推理的「看不到未来」。