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)
| ID | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 词 | 我 | 爱 | 水 | 课 | I | love | easy | courses |
▼ 查表动画 ▼
我汉字
➜
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 只跑一次。
⭐ 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 无掩码)
📊 关键观察:行 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)的概率分布
📊 解读:模型现在最猜「courses」(0.284),目标「I」排第 3 (0.239)。随机权重下还没学会——训练要做的就是把「I」的概率推高。
4 位置预测 + 交叉熵损失
| 位置 | 目标 | 预测(argmax) | p(目标) | 损失 -ln p |
| 0 | I | courses (0.284) | 0.239 | 1.43 |
| 1 | love | I (0.302) | 0.032 | 3.44 |
| 2 | easy | 水 (0.385) | 0.096 | 2.34 |
| 3 | courses | courses (0.563) ✅ | 0.563 | 0.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) ✗
📊 解读:随机权重下模型猜「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-Attention | 4 个 Q 对全部源词 | 每步 1 个 Q 对全部源词 |
| ⑦ 输出层 | 4×8 概率 + 平均损失 | 1×8 概率 + argmax 一个词 |
| ⑧ 下一步输入 | 用真实目标词(teacher forcing) | 用自己预测的词(自回归,错则连锁) |
| ⑨ 梯度 | 反向传播更新权重 | 无梯度、权重冻结 |
💡 为什么这样设计?训练并行 teacher forcing 让 GPU 满载 + 收敛快;推理自回归是「生成」的本质(每个词依赖前面的词)。两者的桥梁是掩码——训练时用掩码模拟推理的「看不到未来」。