Transformer 动画教程

「我爱水课」→「I love easy courses」· 每个字一个 token · 逐步动画演示每一处计算

0 1 2 3 4I 5love 6easy 7courses
源语言 4 tokensEncoder 中间表示 4×4注意力融合 目标语言 4 tokensDecoder 自回归
📐 参数:dmodel=4 · 注意力头 h=2 · 每头 dk=dv=2 · Encoder 2 层 · Decoder 2 层 · 词表 8 词
🧮 数据:所有权重为固定随机值(1 位小数),每一步动画背后都是真实手算结果,可逐位验证

👉 点击下方「下一步」开始,或点击底部圆点跳转任意步骤

整体架构

数据流经哪些模块?每一步在架构图中处于什么位置?

Encoder ×2读源句 Cross-Attn桥接 Decoder ×2写目标句 Linear+Softmax出词
🧭 本教程的导航图:每个步骤标题下的「架构位置」标签会指明当前数据走到哪里。红色虚线框 = 当前模块。

Transformer = Encoder(理解) + Decoder(生成)

🎯 架构功能讲解 Encoder:把「我爱水课」4 个字编码成 4 个向量。它的自注意力让每个字「看到」整句话——「课」在编码时会融合「水」「爱」「我」的信息。
Decoder:逐词生成「I」「love」「easy」「courses」。每生成一个词,它用 掩码自注意力约束自己「只能看已生成的词」(保证不会泄密未来),再用 交叉注意力回头查看 Encoder 的 4 个向量(找到「I」对应「我」的翻译依据)。
输出层:把 Decoder 输出映射回 8 个词的概率分布,取概率最大的词作为预测。
Encoder: X → SelfAttn → FFN → (×2) → 源表示
Decoder: Y → MaskedSelfAttn → CrossAttn(源表示) → FFN → (×2) → 目标表示 → Linear → Softmax
💡 一句话记忆:Encoder 是「读书」,Decoder 是「写作文」——写的时候既不能抄后面的答案(掩码),又要参考读过的书(交叉注意力)。

Tokenization:每个字一个 token

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

🎯 架构功能讲解 架构图最底层的输入是「token 序列」。Transformer 无法直接处理文字,必须先把每个字映射成一个整数 ID。
本教程按题目要求 每个字一个 token:中文「我爱水课」每字一个;英文「I love easy courses」每词一个(英文空格分词)。真实系统中用 BPE 子词分词,原理相同:离散符号 → 词表索引

词表 Vocab(V=8)

ID01234567
Iloveeasycourses
▼ 查表动画 ▼
汉字 0ID E向量
💡 为什么用 ID 而不是 one-hot?one-hot 向量 99% 是 0、浪费空间且无法表达词间相似;查表得到的稠密向量(4 维)既紧凑又能通过训练让相似词靠近。

两个输入序列

我 0
爱 1
水 2
课 3
目标
I 4
love 5
easy 6
courses 7

源序列 = Encoder 输入;目标序列 = Decoder 输入(训练时整句送入,推理时逐步拼接)

Embedding:查表得到连续向量

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

🎯 架构功能讲解 架构图里 Embedding 层紧挨着输入。它的作用是把离散 ID 变成 可学习的连续向量:每个词一个 4 维向量,初始随机,训练中不断调整,最终让语义相近的词在向量空间里距离更近。
这里直接给出固定嵌入矩阵(手算可复现)——取 E 的第 i 行 = 第 i 个词的嵌入向量

嵌入矩阵 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]连续向量
💡 类比:嵌入矩阵就像一本「词典」,每个词占一行;查表就是把词换成那一行的数字。全书所有计算都在这 4 维数字上做。

Positional Encoding:给 token 加上位置信息

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

🎯 架构功能讲解 架构图中 Embedding 之后紧接 Positional Encoding。自注意力本身没有顺序概念——把「我爱水课」倒过来,注意力计算完全一样。要让模型理解「水」在「课」前面,必须显式注入位置。
原始 Transformer 用 正弦/余弦函数 生成位置向量,再和词嵌入逐元素相加。不同维度用不同频率,形成独一无二的「位置指纹」。
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/cos?① 有界(-1~1)数值稳定;② 不同 pos 的编码唯一可区分;③ 相对位置信息(pos+k 与 pos 的差)可通过三角恒等式被模型学到。sin(1)=0.8415、cos(1)=0.5403 可查表验证。

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]
💡 位置编码的作用边界:相加后,同一个词在不同位置有不同的向量(「我」在 pos0 和 pos3 的向量不同)——模型从此「看得见顺序」。

Encoder Layer 1:Self-Attention(Q/K/V)

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

🎯 架构功能讲解 这是 Transformer 的核心创新。自注意力让每个 token 与句内所有 token 交互:
Q(Query 查询):「我在找什么?」
K(Key 键):「我是什么?可以被谁匹配?」
V(Value 值):「我携带什么内容?」
用 Q 与所有 K 点积得到「相关性分数」,softmax 后变成权重,再对 V 加权求和——输出 = 全句信息的加权混合。多头(h=2)让模型同时从多个「视角」看句子。

Q/K/V 投影(对「我」手算演示)

Q = X · W^Qᵀ    K = X · W^Kᵀ    V = X · W^Vᵀ
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
💡 类比:Q 是「问题」(我想找谁),K 是「名片」(我是谁),V 是「内容」(我能提供什么)。先拿问题去匹配名片,再用匹配度对内容加权。

全部 token 的 Q/K/V(Head1)

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

(行 = token:我/爱/水/课;每行 2 维 = 该头视角下的表示)

Attention Score:谁该关注谁?

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

🎯 架构功能讲解 有了 Q/K/V,下一步算 注意力分数:第 i 个 token 对第 j 个 token 的关注强度 = Qi·Kj
除以 √dk 是为了数值稳定(维度越大点积越大,softmax 会饱和)。softmax 把每行变成 和为 1 的概率分布——这就是注意力权重 A。
热力图里颜色越亮 = 关注越强。「我」最关注「爱」(0.53),这就是模型学到的「主语后面接动词」的依赖关系。

Head1:score = Q·Kᵀ/√2(先看原始分数)

score(未缩放前 Q·Kᵀ)
1.583
2.935
-0.048
1.621
0.835
2.258
0.131
0.991
0.798
0.416
-0.257
0.614
-2.313
-2.182
0.531
-1.965
÷√2 → score
1.1193
2.0750
-0.0337
1.1462
0.5902
1.5964
0.0923
0.7006
0.5645
0.2941
-0.1819
0.4339
-1.6355
-1.5430
0.3756
-1.3895
✍ 手算第一行:Q=[1.10,-0.79] × K=[0.57,-1.21] = 1.10×0.57+(-0.79)×(-1.21) = 1.583 → ÷1.4142 = 1.1193;对 K → 2.935→2.0750(最高,最关注「爱」)。

Softmax 后 = 注意力权重热力图(每行和为 1)

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% 看「水」(「水课」强绑定)。这就是注意力:每个字自主决定看谁、看多重。

加权求和:输出 = A·V

用权重混合所有 V → 每个 token 的新表示

🎯 架构功能讲解 注意力权重 A 只是「配方」,真正的内容在 V 里。输出 Outi = Σj Aij·Vj
把全句每个 token 的 V 按关注度加权混合。比如「我」的输出 = 20.2%·V + 52.6%·V + 6.4%·V + 20.8%·V
这就是「上下文感知」的根源——输出不再只是自己的词向量,而是带着全句信息的混合向量。

Head1 输出(「我」的手算)

Out[0][0] = 0.2023×(-0.72) + 0.5261×(-0.8076) + 0.0639×(-0.7756) + 0.2078×(-0.5293)
= -0.1457 - 0.4249 - 0.0496 - 0.1100 = -0.7300
Out[0][1] = 0.2023×(-1.04) + 0.5261×(-1.0539) + 0.0639×0.0052 + 0.2078×(-0.1106) = -0.7874
Head1 输出
-0.7300,-0.7874
-0.7311,-0.7405
-0.6962,-0.6225
-0.7447,-0.2119
Head2 输出
0.1849,-0.2549
0.3665,-0.1155
0.0635,-0.4402
-0.0333,-0.4348
H1 输出2维 H2 输出2维 拼接 4维[H1|H2] × WOᵀ Attn 输出4维
💡 多头意义:两个头用不同的投影矩阵,学到不同的关注模式(一个抓「我→爱」的强依赖,一个抓更均匀的全局关联)。拼接后经 WO 融合成最终 4 维输出,信息互补。

残差连接 + LayerNorm(架构中 Add & Norm)

X1 = X_in + AttnOut → LayerNorm(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] → LN → [-0.0286,-1.6227,0.7878,0.8634]
(每行标准化:减去均值、除以标准差,输出均值≈0、方差≈1)
💡 残差的作用:把原始信息「抄近道」加到注意力输出上,梯度可以直接流回输入层,深层网络才不会退化;LayerNorm 稳定每层数值分布,训练更快更稳。

Feed-Forward Network:逐 token 加工

4 → 8 → 4 维,ReLU 非线性

🎯 架构功能讲解 注意力负责「搬运信息」(跨 token 交互),FFN 负责「加工信息」(对每个 token 独立 做非线性变换)。
先升维到 8(放大表达空间),ReLU 激活(把负数清零、引入非线性),再降回 4。残差+LayerNorm 收尾。这一层没有跨 token 通信——4 个 token 各自独立变换,但都带着注意力混合好的上下文。

FFN 计算(对「我」)

LN 输出4维 ×W1ᵀ 8维中间4→8 ReLU 激活后负数清零 ×W2ᵀ 4维输出8→4
我: [-0.0286,-1.6227,0.7878,0.8634] ×W1ᵀ → [-0.1842,-1.7211,-0.1982,0.5190,-1.3580,1.1511,-0.1454,0.2173]
ReLU(max(0,x)) → [0, 0, 0, 0.5190, 0, 1.1511, 0, 0.2173](4 个负数变 0)
×W2ᵀ → [-0.0699,-0.8135,-0.3056,-0.3782]
💡 为什么 4→8→4?中间维度越大,能拟合的函数越复杂(类似「先展开再压缩」)。这是架构图中 FFN 的 dff=8 的含义。每层的 FFN 权重不同,逐层加深对句子的理解。

Encoder Layer 2:再读一遍 + 最终源表示

第二层自注意力让理解更全局,输出喂给 Decoder

🎯 架构功能讲解 Encoder 堆叠 2 层:Layer 1 学局部(相邻词的依赖),Layer 2 在 Layer 1 的输出上学更全局的语义
对比热力图:Layer 1 的「我→爱」极强(0.53),Layer 2 的分布更均匀(0.30~0.36)——高层的注意力更「抽象」、更分散地吸收全句信息。
最终输出 = Encoder 的记忆:4 个向量,每个都包含整句「我爱水课」的信息,将作为 Decoder 交叉注意力的 K 和 V。

Layer 2 注意力更均匀(对比)

L1
0.20
0.53
0.06
0.21
L2
0.30
0.11
0.42
0.17

「我」行的注意力:L1 尖峰(0.53 盯爱)→ 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 翻译时反复「查阅」的源句记忆就在这里。

Decoder Masked Self-Attention:不许偷看未来

上三角掩码 = -∞,softmax 后权重归零

🎯 架构功能讲解 Decoder 的第一个注意力是 带掩码 的自注意力。为什么?生成是自回归的:
写「I」的时候,绝对不能看到后面的「love easy courses」——否则训练时模型学会了「抄答案」,推理时却无答案可抄。
做法:score 矩阵的上三角位置加 -1e9(≈-∞),softmax 后 e^(-∞)=0,被遮位置的权重强制归零。
结果:第 i 个 token 只能看到 0..i(自己及之前的词),下三角(绿)可见,上三角(红)禁止。

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

I✓
I✓
love✓
I✓
love✓
easy✓
I✓
love✓
easy✓
courses✓
🔍 读图:行 = 当前生成位置,列 = 能看到谁。对角线及左下(绿)= 允许;右上(红)= 禁止。第 3 行生成 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](只关注 I 和 love)

掩码后 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
💡 关键:掩码只作用于 Decoder 的自注意力;交叉注意力(下一步)不掩码——它本来就要看完整的源句。

⭐ Cross-Attention:翻译的关键桥梁

Q 来自 Decoder(目标),K/V 来自 Encoder(源)——实现「对齐」

🎯 架构功能讲解 这是 Encoder-Decoder 架构的灵魂,也是翻译的本质:
Q = Decoder 的当前表示(想表达什么)
K = V = Encoder 最终输出(源句有哪些信息可用)
Decoder 用 Q 去匹配源句所有 K,softmax 得到「我应该参考源句的哪些词」,再对源句 V 加权求和。
训练好后,生成「I」时权重会集中在「我」上——这就是词对齐(attention alignment)。本例随机权重未训练,分布较均匀,但机制完全相同:没有掩码、自由看全源句

Cross-Attention 计算(生成第 1 个词「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+),这就是「翻译对齐」——机器翻译的可解释性来源。

输出层:Linear + Softmax → 预测下一个词

4 维向量 → 8 个词的 logits → 概率分布

🎯 架构功能讲解 架构图最顶部:Decoder 输出(4 维)经 Linear(W_out:4→8)映射回词表维度,得到每个词的 logits(未归一化分数),再 Softmax 变成概率。
概率最高的词就是模型预测。训练时用交叉熵损失 -log P(目标词) 反向传播更新所有权重。
本步骤展示 Decoder 最终输出 4 个位置的完整概率分布,以及「模型现在猜得怎么样」。

位置 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」的概率一步步推高。

四个位置的概率 + 预测结果

位置目标词预测(argmax)目标概率交叉熵损失
0Icourses (0.284)0.239-ln0.239 = 1.43
1loveI (0.302)0.032-ln0.032 = 3.44
2easy水 (0.385)0.096-ln0.096 = 2.34
3coursescourses (0.563) ✅0.563-ln0.563 = 0.57
位置 3 已猜对(courses, 0.563)——说明 Encoder→Decoder 的信息流已经部分起作用。训练若干轮后 4 个位置都会收敛到目标词。

全流程回顾:数据一步步变成什么

13 步动画的浓缩总结

① Tokenization:我爱水课 → [0,1,2,3];I love easy courses → [4,5,6,7]每字/每词一个 token ② Embedding:ID → E 查表(4 维连续向量)词嵌入 ③ + Positional Encoding:sin/cos 注入位置X_in = E+PE ④ Encoder ×2:Self-Attn(Q/K/V→score→softmax→A·V)→ FFN → 残差+LN源句理解 ↓ K/V ⑤ Decoder ×2:Masked Self-Attn(只看左边)→ Cross-Attn(Q=解码,K/V=编码)→ FFN目标生成+对齐 ⑥ Linear + Softmax:4维 → 8 词概率,argmax = 预测输出层
🎯 三个注意力的分工(全文核心) Encoder Self-Attn:源词互相看 → 理解「我爱水课」
Decoder Masked Self-Attn:目标词只看左边 → 保证自回归不泄密
Cross-Attn:目标词看全部源词 → 翻译对齐
掩码只在 Decoder 自注意力;Cross-Attn 永不掩码。

复现指南

权重:seed=42 均匀随机 [-1,1] 取 1 位小数(嵌入矩阵手设)。
运算:矩阵乘法 = 行×列点积;softmax 每行归一化;LayerNorm 每行 (x-μ)/√(σ²+ε);掩码 = score 上三角加 -1e9。
交叉验证:`transformer_calc.py` 生成全部 755 行中间量,与本文档逐位一致。