
目录
- 第一章:序列建模的历史演进
- 第二章:注意力机制的数学原理(超详细推导)
- 第三章:多头注意力的深层设计哲学
- 第四章:位置编码的完整数学推导
- 第五章:Encoder的逐层深度拆解
- 第六章:Decoder的逐层深度拆解
- 第七章:三种架构范式的本质区别
- 第八章:经典模型深度剖析
- 第九章:Transformer的训练与优化
- 第十章:面试高频问题深度解析
第一章:序列建模的历史演进
1.1 为什么需要序列建模?
自然语言、语音、时间序列、DNA序列——现实世界中充满了序列数据。序列数据的核心特点是:元素之间存在顺序依赖关系。
"我 吃 苹果" vs "苹果 吃 我"
→ 同样的词,顺序不同,含义完全不同
→ 这就是序列建模的核心挑战:如何捕捉顺序和上下文信息
1.2 RNN的工作原理(详细版)
RNN通过隐藏状态的递推来处理序列:
时间步t的计算:
h_t = tanh(W_hh × h_{t-1} + W_xh × x_t + b)
其中:
x_t : 当前时刻的输入向量(如词嵌入)
h_{t-1} : 上一时刻的隐藏状态("记忆")
W_hh : 隐藏层到隐藏层的权重矩阵(捕捉时序依赖)
W_xh : 输入到隐藏层的权重矩阵(处理当前输入)
b : 偏置项
tanh : 激活函数(将值压缩到[-1,1])
展开来看:
h_1 = tanh(W_hh × h_0 + W_xh × x_1 + b) ← 只看x1
h_2 = tanh(W_hh × h_1 + W_xh × x_2 + b) ← 间接看了x1, 直接看x2
h_3 = tanh(W_hh × h_2 + W_xh × x_3 + b) ← 间接看了x1,x2, 直接看x3
1.3 RNN的梯度消失问题(数学推导)
这是RNN最致命的问题。展开递推关系:
h_t = tanh(W_hh × h_{t-1} + ...)
∂h_t / ∂h_1 = ∏(k=2 to t) ∂h_k / ∂h_{k-1}
= ∏(k=2 to t) W_hh^T × diag(tanh'(z_k))
其中 tanh'(z) ∈ (0, 1],最大值为1(当z=0时)
当t很大时,这个连乘会:
- 如果W_hh的特征值 < 1:连乘趋近于0 → 梯度消失
- 如果W_hh的特征值 > 1:连乘趋近于∞ → 梯度爆炸
直觉理解:信息像接力赛一样传递,每一棒都有损耗。跑10棒后,第一棒的信息几乎完全丢失。
1.4 LSTM和GRU的改进(仍不够)
LSTM引入了门控机制和细胞状态:
遗忘门: f_t = σ(W_f × [h_{t-1}, x_t] + b_f) ← 决定忘记什么
输入门: i_t = σ(W_i × [h_{t-1}, x_t] + b_i) ← 决定记住什么
输出门: o_t = σ(W_o × [h_{t-1}, x_t] + b_o) ← 决定输出什么
细胞状态: C_t = f_t ⊙ C_{t-1} + i_t ⊙ tanh(W_C × [h_{t-1}, x_t] + b_C)
隐藏状态: h_t = o_t ⊙ tanh(C_t)
关键改进:细胞状态C_t的更新是"加法"而非"乘法"
→ 梯度可以沿着C_t几乎无损地传播很远
→ 但仍需按顺序计算,无法并行
1.5 Transformer的革命性突破
2017年Google的论文《Attention Is All You Need》提出了三个革命性思想:
| 创新点 | 解决的问题 | 效果 |
|---|---|---|
| 自注意力(Self-Attention) | 长距离依赖 | 任意两个位置直接交互,O(1)路径长度 |
| 并行计算 | RNN的串行瓶颈 | 所有位置同时计算,GPU利用率100% |
| 位置编码 | 没有循环就没有位置信息 | 显式注入位置信息,可学习或固定 |
第二章:注意力机制的数学原理(超详细推导)
2.1 从信息检索的角度理解注意力
想象你在图书馆查资料:
- 你有一个查询(Query):“什么是量子计算?”
- 每本书有一个键(Key):标题/摘要/关键词
- 每本书有一个值(Value):实际内容
检索过程:
- 用你的查询和每本书的键计算相似度
- 相似度高的书,你花更多时间阅读
- 最终你综合各本书的内容形成你的理解
数学表达:
attention_output = Σ(similarity(query, key_i) × value_i)
其中 similarity 需要归一化(权重之和=1)
2.2 Q、K、V的线性变换
输入序列X(n×d矩阵,n个词,每个词d维),通过三个可学习的权重矩阵变换:
Q = X × W_Q (n × d_k) W_Q: d × d_k
K = X × W_K (n × d_k) W_K: d × d_k
V = X × W_V (n × d_v) W_V: d × d_v
为什么需要三个不同的矩阵?
→ 因为"查询"、"被查询"、"信息内容"是三个不同的语义空间
→ 类比:搜索引擎中,搜索词(Query)、网页标题(Key)、网页内容(Value)
是三种不同的东西,需要不同的表示方式
如果不区分Q/K/V会怎样?
→ 那就是最简单的"自相关"计算,表达能力有限
→ 通过独立的投影矩阵,模型可以学习到更丰富的交互模式
2.3 相似度计算:为什么用点积?
有多种计算相似度的方式:
方式1: 加性注意力 (Bahdanau, 2015)
score(q, k) = v^T × tanh(W_1 × q + W_2 × k)
→ 需要额外参数v, W1, W2
→ 计算较慢
方式2: 点积注意力 (Luong, 2015)
score(q, k) = q^T × k
→ 不需要额外参数
→ 可以用矩阵乘法高效计算
方式3: 缩放点积注意力 (Vaswani, 2017) ← Transformer使用的方式
score(q, k) = q^T × k / √d_k
→ 点积 + 缩放因子
为什么Transformer选择点积?
→ 点积可以用高度优化的矩阵乘法(GEMM)实现
→ GPU上的矩阵乘法是高度优化的,速度远快于加性注意力
→ 实际测试中,点积注意力比加性注意力快2-4倍
2.4 为什么要除以√d_k?(面试高频问题!)
这是很多人忽略的关键细节。
假设Q和K的每个分量都是独立的均值为0、方差为1的随机变量
那么 q·k = Σ(q_i × k_i),i=1到d_k
每个q_i × k_i的期望:E[q_i × k_i] = E[q_i] × E[k_i] = 0
每个q_i × k_i的方差:Var[q_i × k_i] = 1
q·k的期望:E[q·k] = 0
q·k的方差:Var[q·k] = d_k ← 方差随维度线性增长!
当d_k很大时(如512或1024):
→ 点积值的方差很大
→ 点积值可能非常大(如±20甚至±50)
→ softmax的输入值很大时,输出会趋近于one-hot(梯度几乎为0)
数值示例:
d_k = 512, 假设点积值为 [20, -15, 10, -8, 5, ...]
softmax([20, -15, 10, -8, 5]) ≈ [1.0, 0.0, 0.0, 0.0, 0.0]
→ 梯度消失!注意力变成了"硬选择",无法学习
除以√d_k后:
点积值变为 [20/√512, -15/√512, ...] ≈ [0.88, -0.66, ...]
→ 方差被归一化为1
→ softmax输出更均匀,梯度可以正常流动
一句话总结:除以√d_k是为了稳定softmax的梯度,防止高维空间中点积值过大导致梯度消失。
2.5 softmax的作用与性质
softmax(z_i) = e^(z_i) / Σ_j e^(z_j)
性质:
- 输出在(0,1)之间,且所有输出之和=1 → 概率分布
- 保持相对大小关系:若z_i > z_j,则softmax(z_i) > softmax(z_j)
- 温度控制:实际中有时用softmax(z/τ),τ越大分布越均匀
为什么用softmax而不是sigmoid?
- sigmoid独立地将每个值映射到(0,1),但输出之和不等于1
- 注意力权重需要表示一个概率分布(分配给每个位置的权重之和应为1)
- softmax天然满足这个约束
为什么不用ReLU/线性?
- 注意力权重必须是非负的(权重表示"关注度",不能是负数)
- 还需要归一化(所有权重之和为1,表示分配了100%的注意力)
- softmax同时满足这两个要求
2.6 完整的注意力计算流程(逐步推导)
输入: X ∈ R^(n×d) (n=序列长度, d=模型维度)
参数: W_Q ∈ R^(d×d_k), W_K ∈ R^(d×d_k), W_V ∈ R^(d×d_v)
Step 1: 线性投影
Q = X × W_Q ∈ R^(n×d_k) 每个词变成一个"查询向量"
K = X × W_K ∈ R^(n×d_k) 每个词变成一个"键向量"
V = X × W_V ∈ R^(n×d_v) 每个词变成一个"值向量"
Step 2: 计算注意力分数矩阵
S = Q × K^T ∈ R^(n×n)
S_ij = q_i^T × k_j / √d_k
→ S_ij表示第i个词对第j个词的"关注程度"
Step 3: 缩放
S = S / √d_k
Step 4: softmax归一化(按行)
A = softmax(S) ∈ R^(n×n)
A_ij = e^(S_ij) / Σ_k e^(S_ik)
→ 每一行是一个概率分布,表示该词对所有词的注意力分配
Step 5: 加权求和
O = A × V ∈ R^(n×d_v)
O_i = Σ_j A_ij × v_j
→ 第i个词的输出 = 所有词的值向量的加权平均
→ 权重由注意力矩阵A的第i行决定
第三章:多头注意力的深层设计哲学
3.1 为什么需要多头?
单头注意力的局限:
单头注意力只能学习一种注意力模式
但在自然语言中,一个词需要同时关注多种不同类型的信息:
"那只 猫 坐在 垫子 上,因为 它 很 舒服"
"它"需要同时关注:
- 语法关系:谁是"它"的指代对象?→ 关注"猫"(主语)
- 语义关系:为什么舒服?→ 关注"垫子"(地点)
- 位置关系:最近的名词是什么?→ 关注"垫子"(相邻)
一个注意力头很难同时捕捉所有这些关系!
3.2 多头注意力的计算
MultiHead(Q, K, V) = Concat(head_1, ..., head_h) × W_O
其中:
head_i = Attention(X×W_Q_i, X×W_K_i, X×W_V_i)
参数:
W_Q_i ∈ R^(d×d_k), W_K_i ∈ R^(d×d_k), W_V_i ∈ R^(d×d_v) (每个头一组)
W_O ∈ R^(h×d_v × d) (输出投影矩阵)
通常设置:
d_k = d_v = d_model / h
例如: d_model=512, h=8 → d_k = d_v = 64
每个头看到的是输入的不同"视角":
head_1: 512维 → 投影到64维的子空间1
head_2: 512维 → 投影到64维的子空间2
...
head_8: 512维 → 投影到64维的子空间8
→ 8个头在8个不同的子空间中独立计算注意力
→ 最终拼接起来,信息更丰富
3.3 多头注意力的计算复杂度分析(面试常问!)
单头注意力的时间复杂度:
Q×K^T: O(n × n × d_k) = O(n² × d_k)
softmax: O(n²)
A×V: O(n × n × d_v) = O(n² × d_v)
总计: O(n² × d)
多头注意力的时间复杂度:
h个头,每个头维度d/h
每个头: O(n² × d/h)
h个头: O(h × n² × d/h) = O(n² × d)
+ 输出投影: O(n × d × d) = O(n × d²)
总计: O(n² × d + n × d²) ≈ O(n² × d) (当n > d时)
空间复杂度:
注意力矩阵: O(n²) ← 这是Transformer的主要瓶颈!
当序列长度n很大时(如n=10000),注意力矩阵需要100M个元素
3.4 不同头学到了什么?
通过可视化分析,不同头确实学到了不同的语言知识:
头1: 主语-谓语关系
"猫" ←→ "坐" 高注意力
头2: 修饰关系
"漂亮的" ←→ "花" 高注意力
头3: 指代关系
"它" ←→ "猫" 高注意力
头4: 位置偏好
每个词倾向于关注相邻的词
头5: 句法结构
关注标点符号和句子边界
第四章:位置编码的完整数学推导
4.1 为什么需要位置编码?
自注意力的计算是"排列不变(permutation invariant)"的:
Attention(π(Q), π(K), π(V)) = π(Attention(Q, K, V))
其中π是任意排列
这意味着:
"我 爱 中国" 和 "中国 爱 我" 在自注意力看来是一样的!
因为只是矩阵行列的重排,不影响结果
但自然语言中,词序至关重要:
"我 爱 中国" ≠ "中国 爱 我"
所以必须显式注入位置信息!
4.2 正弦位置编码的设计
Transformer使用固定的正弦/余弦位置编码:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
其中:
pos : 词在序列中的位置(0, 1, 2, ...)
i : 维度索引(0, 1, 2, ..., d_model/2-1)
d_model : 模型维度
4.3 为什么选择sin/cos?(面试高频!)
原因1:可以表示相对位置
对于任意固定偏移k,存在线性变换矩阵M_k使得:
PE(pos+k) = M_k × PE(pos)
证明:
考虑一对维度(2i, 2i+1):
[PE(pos+k, 2i) ] [cos(kω_i) sin(kω_i)] [PE(pos, 2i) ]
[PE(pos+k, 2i+1)] = [-sin(kω_i) cos(kω_i)] [PE(pos, 2i+1)]
其中 ω_i = 1/10000^(2i/d_model)
这是一个旋转矩阵!→ 旋转角度 = k × ω_i
→ 模型可以通过学习来捕捉相对位置关系
原因2:可以泛化到更长的序列
训练时见过的最长序列是512个词
但sin/cos函数是周期性的,可以计算任意位置的编码
→ 理论上可以泛化到任意长度(虽然效果会下降)
原因3:值域有界
PE(pos) ∈ [-1, 1](因为sin和cos的值域)
→ 不会因为位置太远而导致编码值过大
→ 与词嵌入(通常也在[-1,1]范围内)可以合理相加
4.4 位置编码的可视化
假设d_model = 128,绘制PE矩阵:
位置 0: [sin(0), cos(0), sin(0), cos(0), ...] = [0, 1, 0, 1, ...]
位置 1: [sin(ω₁), cos(ω₁), sin(ω₂), cos(ω₂), ...]
位置 2: [sin(2ω₁), cos(2ω₁), sin(2ω₂), cos(2ω₂), ...]
...
低维度(i小)→ 频率高(ω大)→ 变化快 → 捕捉细粒度位置
高维度(i大)→ 频率低(ω小)→ 变化慢 → 捕捉粗粒度位置
→ 不同维度关注不同粒度的位置信息
4.5 可学习位置编码 vs 固定位置编码
固定编码(原始Transformer):
PE由sin/cos公式计算,不可学习
优点:实现简单,可泛化
缺点:可能不是最优的
可学习编码(BERT, GPT):
PE作为可学习参数,通过训练优化
优点:可以学到更适合任务的位置表示
缺点:不能泛化到训练时没见过的长度
实践发现:
在大规模预训练中,两者性能差异很小
BERT使用可学习编码
原始Transformer和部分模型使用固定编码
第五章:Encoder的逐层深度拆解
5.1 Encoder的完整数据流
输入序列: "我 爱 中国"
Step 1: 词嵌入(Embedding)
"我" → [0.2, -0.5, 0.8, ...] (d_model维)
"爱" → [0.1, 0.3, -0.7, ...]
"中国" → [-0.4, 0.6, 0.2, ...]
矩阵: X_emb ∈ R^(3×512)
Step 2: 加入位置编码
X = X_emb + PE
→ 现在每个词既有语义信息又有位置信息
Step 3: N层Encoder Block(逐层处理)
for layer = 1 to N:
X = EncoderLayer(X)
Step 4: 输出
H = X ∈ R^(3×512) (上下文化的词表示)
→ "我"的表示已经融合了"爱"和"中国"的信息
5.2 单层Encoder Block的内部计算(每一步都详细)
输入: X ∈ R^(n×d)
━━━━━━━━ 子层1: 多头自注意力 ━━━━━━━━
1. 线性投影生成Q, K, V:
Q = X × W_Q ∈ R^(n×d_k)
K = X × W_K ∈ R^(n×d_k)
V = X × W_V ∈ R^(n×d_v)
2. 计算注意力分数:
S = Q × K^T / √d_k ∈ R^(n×n)
对于输入"我 爱 中国":
S = [[s11, s12, s13], ← "我"对"我","爱","中国"的注意力分数
[s21, s22, s23], ← "爱"对"我","爱","中国"的注意力分数
[s31, s32, s33]] ← "中国"对"我","爱","中国"的注意力分数
3. softmax归一化:
A = softmax(S) ∈ R^(n×n)
注意:Encoder中是全连接注意力(没有掩码)
→ 每个词可以看到所有其他词
4. 加权求和:
Attn_output = A × V ∈ R^(n×d_v)
5. 输出投影:
MultiHead_output = Attn_output × W_O ∈ R^(n×d)
━━━━━━━━ 残差连接 + 层归一化 ━━━━━━━━
6. 残差连接:
X' = X + MultiHead_output
→ 保留原始输入信息,防止梯度消失
7. 层归一化(LayerNorm):
X'' = LayerNorm(X')
LayerNorm的计算(比BatchNorm更适合NLP):
μ = mean(X') (对每个样本的所有维度求均值)
σ² = var(X') (对每个样本的所有维度求方差)
X'' = γ × (X' - μ) / √(σ² + ε) + β
其中γ和β是可学习的缩放和偏移参数
ε是防止除零的小常数(通常1e-5)
━━━━━━━━ 子层2: 前馈网络(FFN) ━━━━━━━━
8. 两层全连接 + 激活函数:
FFN(X'') = max(0, X'' × W_1 + b_1) × W_2 + b_2
W_1 ∈ R^(d×d_ff), W_2 ∈ R^(d_ff×d)
d_ff通常 = 4 × d(如d=512, d_ff=2048)
为什么d_ff = 4d?
→ 先扩展到4倍维度(增加表达能力)
→ 再压缩回原始维度
→ 类似于"先展开思考,再压缩总结"
━━━━━━━━ 残差连接 + 层归一化 ━━━━━━━━
9. 残差连接 + LayerNorm:
output = LayerNorm(X'' + FFN(X''))
→ output送入下一层Encoder(如果是最后一层,送入Decoder)
5.3 层归一化 vs 批归一化(面试常问!)
BatchNorm: 对同一个特征维度,跨batch归一化
μ, σ²是在batch维度上计算的
问题:NLP中序列长度不等,batch统计不稳定
LayerNorm: 对同一个样本,跨所有特征维度归一化
μ, σ²是在特征维度上计算的
优点:不依赖batch大小,每个样本独立归一化
为什么Transformer选择LayerNorm?
1. NLP序列长度可变,BatchNorm统计不稳定
2. LayerNorm在推理时不需要维护running mean/var
3. 小batch时BatchNorm统计噪声大,LayerNorm不受影响
5.4 Pre-Norm vs Post-Norm
Post-Norm(原始Transformer):
output = LayerNorm(x + SubLayer(x))
→ 先计算子层,再加残差,最后归一化
Pre-Norm(后来的改进):
output = x + SubLayer(LayerNorm(x))
→ 先归一化,再计算子层,最后加残差
Pre-Norm的优势:
- 训练更稳定(梯度流更平滑)
- 可以训练更深的模型
- 不需要warmup学习率
为什么Pre-Norm更稳定?
Post-Norm: 梯度需要经过LayerNorm,可能导致梯度消失
Pre-Norm: 残差连接直接传递梯度,不经过LayerNorm
→ 梯度可以无损地从最后一层传到第一层
第六章:Decoder的逐层深度拆解
6.1 Decoder与Encoder的核心区别
Encoder: "理解输入"
- 双向注意力:每个词可以看到所有其他词
- 一次性处理整个输入序列
Decoder: "生成输出"
- 单向注意力(掩码):只能看到已生成的词
- 交叉注意力:可以看Encoder的全部输出
- 自回归:一个词一个词地生成
6.2 掩码自注意力的详细实现
为什么需要掩码?
训练时(Teacher Forcing):
输入: [<BOS>, 我, 爱, 中国]
目标: [我, 爱, 中国, <EOS>]
一次性输入所有已知token,但必须防止"偷看"未来
推理时:
自然就是逐词生成,不存在偷看问题
掩码矩阵M(-∞表示遮挡,0表示可见):
<BOS> 我 爱 中国
<BOS> [ 0, -∞, -∞, -∞ ]
我 [ 0, 0, -∞, -∞ ]
爱 [ 0, 0, 0, -∞ ]
中国 [ 0, 0, 0, 0 ]
应用掩码:
S_masked = Q × K^T / √d_k + M
被遮挡位置加上-∞后:
softmax(-∞) = 0 → 注意力权重为0,完全忽略
数学表达:
A_ij = softmax(S_ij + M_i)
当j > i时,M_i = -∞ → A_ij = 0
当j ≤ i时,M_i = 0 → A_ij = softmax(S_ij)
6.3 交叉注意力的详细机制
交叉注意力是Encoder和Decoder之间的桥梁:
Q = Decoder当前层的输出 × W_Q ← "我在找什么?"
K = Encoder最终输出 × W_K ← "输入中有什么?"
V = Encoder最终输出 × W_V ← "输入的实际内容"
CrossAttention(Q_dec, K_enc, V_enc) = softmax(Q_dec × K_enc^T / √d_k) × V_enc
直觉理解:
在翻译"I love China"→"我爱中国"时:
生成"我"时:
Q_dec = [当前decoder状态的查询]
与K_enc中的"I"、"love"、"China"分别计算相似度
→ 可能"I"的注意力最高 → 获取"I"的V信息 → 输出"我"
生成"爱"时:
→ 可能"love"的注意力最高 → 输出"爱"
生成"中国"时:
→ 可能"China"的注意力最高 → 输出"中国"
6.4 Decoder的自回归生成过程
推理时,Decoder逐词生成:
Step 1:
输入: <BOS>
掩码自注意力: <BOS> → <BOS>
交叉注意力: <BOS> → Encoder输出
FFN
输出层: P(我) = 0.6, P(你) = 0.2, ...
→ 选择"我"
Step 2:
输入: <BOS>, 我
掩码自注意力: <BOS>→我, 我→我
交叉注意力: <BOS>→Encoder, 我→Encoder
FFN
输出层: P(爱) = 0.7, P(喜欢) = 0.15, ...
→ 选择"爱"
Step 3:
输入: <BOS>, 我, 爱
...
→ 选择"中国"
Step 4:
输入: <BOS>, 我, 爱, 中国
...
→ 选择<EOS>,生成结束
第七章:三种架构范式的本质区别
7.1 Encoder-Only(以BERT为代表)
架构: 只有Encoder,没有Decoder
注意力: 双向自注意力(每个词看到所有词)
┌──────────────────────────────────┐
│ 输入: [CLS] 我 爱 中国 [SEP] │
│ ↓ ↓ ↓ ↓ ↓ │
│ ┌────────────────────────────┐ │
│ │ 双向自注意力 × 12层 │ │
│ │ 每个词都能看到所有其他词 │ │
│ └────────────────────────────┘ │
│ ↓ ↓ ↓ ↓ ↓ │
│ 输出: h_CLS h_我 h_爱 h_中国 h_SEP │
│ ↓ │
│ [CLS]的表示 → 分类头 → 类别 │
└──────────────────────────────────┘
预训练: 掩码语言模型(MLM)
随机遮挡15%的词,让模型预测被遮挡的词
"我 爱 [MASK] 国" → 预测"中"
为什么用MLM而不是NTP?
→ MLM迫使模型利用双向上下文来预测
→ 这就是BERT"理解能力强"的原因
7.2 Decoder-Only(以GPT为代表)
架构: 只有Decoder(带掩码),没有Encoder
注意力: 单向掩码自注意力(只看前面的词)
┌──────────────────────────────────┐
│ 输入: 今天 天气 真 好 │
│ ↓ ↓ ↓ ↓ │
│ ┌────────────────────────────┐ │
│ │ 掩码自注意力 × 96层 │ │
│ │ 每个词只能看到前面的词 │ │
│ └────────────────────────────┘ │
│ ↓ ↓ ↓ ↓ │
│ 输出: → → → → │
│ ↓ │
│ 预测下一个词: "我们" │
└──────────────────────────────────┘
预训练: 下一个词预测(NTP/Causal LM)
给定前文,预测下一个词
为什么GPT只用Decoder也能做理解任务?
→ 通过prompt将理解任务转化为生成任务
→ "这部电影好看吗?正面还是负面?" → "正面"
→ 足够大的模型可以通过生成来"理解"
7.3 Encoder-Decoder(以T5为代表)
架构: Encoder + Decoder,通过交叉注意力连接
注意力: Encoder双向 + Decoder单向 + 交叉
┌─────────────────┐ ┌─────────────────────┐
│ Encoder │ │ Decoder │
│ │ │ │
│ 输入: I love China│ │ 输出: <BOS> │
│ ↓ ↓ ↓ │ │ ↓ │
│ 双向自注意力×12 │ │ 掩码自注意力 │
│ ↓ ↓ ↓ │ │ ↓ │
│ 编码: h1 h2 h3 │────│→ 交叉注意力(Q来自Dec) │
│ │ │ ↓ │
│ │ │ FFN │
│ │ │ ↓ │
│ │ │ 输出概率: P("我") │
└─────────────────┘ └─────────────────────┘
推理过程:
Encoder一次性编码"I love China" → [h1, h2, h3]
Decoder自回归生成:
<BOS> + 交叉注意力(h1,h2,h3) → "我"
<BOS> 我 + 交叉注意力(h1,h2,h3) → "爱"
<BOS> 我 爱 + 交叉注意力(h1,h2,h3) → "中国"
<BOS> 我 爱 中国 + 交叉注意力(h1,h2,h3) → <EOS>
7.4 三种架构的本质区别总结
核心区别在于"注意力的方向和范围":
Encoder-Only (BERT):
双向全连接注意力
"我"看→ [我, 爱, 中国]
"爱"看→ [我, 爱, 中国]
"中国"看→ [我, 爱, 中国]
→ 每个位置都能看到完整上下文
→ 适合"理解"任务
Decoder-Only (GPT):
单向因果注意力(下三角掩码)
"今天"看→ [今天]
"天气"看→ [今天, 天气]
"真"看→ [今天, 天气, 真]
→ 每个位置只能看到之前的
→ 适合"生成"任务
Encoder-Decoder (T5):
Encoder: 双向全连接(同BERT)
Decoder: 单向因果 + 交叉注意力
→ 最灵活,但参数最多
→ 适合"输入→输出"的转换任务
第八章:经典模型深度剖析
8.1 BERT的训练细节
预训练任务1: 掩码语言模型(MLM)
策略(很重要!):
- 随机选择15%的token
- 80%替换为[MASK]
- 10%替换为随机词
- 10%保持不变
为什么这样分配?
→ 如果100%用[MASK],模型可能只学会了处理[MASK]
→ 10%随机词迫使模型对每个位置都做判断
→ 10%不变让模型知道"正确答案有时候就在这里"
预训练任务2: 下一句预测(NSP)
输入: [CLS] 今天天气好 [SEP] 我们出去玩 [SEP]
标签: IsNext / NotNext
后续研究(RoBERTa)发现NSP没啥用,去掉了
→ 说明这个任务设计不够好
8.2 GPT的规模定律(Scaling Law)
GPT-3的175B参数分布在96层Transformer Decoder中:
d_model = 12288
n_heads = 96
d_ff = 4 × 12288 = 49152
n_layers = 96
Kaplan et al. (2020)发现的Scaling Law:
L(N) ∝ N^(-0.076) 模型参数量N
L(D) ∝ D^(-0.095) 数据量D
L(C) ∝ C^(-0.050) 计算量C
含义:
→ 模型越大、数据越多、计算越多,损失就越低
→ 且这种关系是幂律(对数坐标下是直线)
→ 这就是为什么大模型一直在变大
8.3 T5的统一框架
T5将所有NLP任务统一为"文本到文本":
分类: "sentiment: I love it" → "positive"
翻译: "translate English to German: That is good" → "Das ist gut"
摘要: "summarize: <长文本>" → "<摘要>"
问答: "question: Who is CEO? context: Tim Cook is CEO..." → "Tim Cook"
这种统一使得一个模型可以处理所有任务
→ 这是后来prompt engineering的思想基础
第九章:Transformer的训练与优化
9.1 学习率调度(Warmup + Decay)
原始Transformer使用特殊的学习率调度:
lr = d_model^(-0.5) × min(step^(-0.5), step × warmup_steps^(-1.5))
前warmup_steps步:学习率线性增长
之后:学习率按平方根衰减
为什么需要warmup?
→ Transformer的初始参数是随机的
→ 如果一开始就用大学习率,梯度可能很大且不稳定
→ warmup让模型先用小学习率"热身",等参数稳定后再加大
典型设置:warmup_steps = 4000
9.2 标签平滑(Label Smoothing)
标准交叉熵:目标是one-hot向量 [0, 0, 1, 0, ...]
标签平滑:将一小部分概率分配给其他词 [0.01, 0.01, 0.97, 0.01, ...]
L = -(1-ε)×log(p_target) - ε/V × Σ log(p_i)
其中ε通常=0.1,V是词表大小
为什么用标签平滑?
→ 防止模型过度自信(输出接近1.0的概率)
→ 提高泛化能力
→ 原始Transformer论文中使用了ε=0.1
9.3 Dropout策略
Transformer在三个地方使用Dropout:
1. 注意力权重上:A = dropout(softmax(S))
2. 每个子层的输出上
3. 嵌入层上
Dropout rate通常为0.1
作用:
→ 防止过拟合
→ 训练时随机丢弃一些连接,迫使模型学习更鲁棒的表示
→ 推理时关闭Dropout,所有连接都使用
第十章:面试高频问题深度解析
Q1: 为什么Transformer用缩放点积而不是加性注意力?
答:主要出于计算效率考虑。
点积注意力可以用矩阵乘法(GEMM)高效实现,GPU上有专门的硬件加速。
加性注意力需要逐元素计算tanh,无法充分利用GPU并行。
实验表明,点积注意力比加性注意力快2-4倍,且效果相当。
Q2: 为什么除以√d_k?
答:防止高维空间中点积值过大导致softmax梯度消失。
当d_k很大时,Q和K的点积方差为d_k,导致softmax输出趋近one-hot。
除以√d_k将方差归一化为1,使softmax输出更均匀,梯度可以正常流动。
Q3: 多头注意力中不同头学到了什么?
答:不同头学习不同类型的语言关系。
研究表明:有的头关注语法结构(主谓关系),有的关注语义(指代消解),
有的关注位置(相邻词),有的关注特殊符号(标点)。
多头机制让模型能同时捕捉多种关系模式。
Q4: 位置编码为什么用sin/cos?
答:三个原因:
1. 可以表示相对位置(通过线性变换,即旋转矩阵)
2. 值域有界([-1,1]),不会因为位置远而值过大
3. 可以泛化到训练时没见过的序列长度
Q5: LayerNorm和BatchNorm的区别?为什么用LayerNorm?
答:BatchNorm在batch维度归一化,LayerNorm在特征维度归一化。
NLP中序列长度可变,batch统计不稳定;小batch时统计噪声大。
LayerNorm不依赖batch大小,每个样本独立归一化,更适合NLP。
Q6: 为什么GPT只用Decoder就能做理解任务?
答:GPT通过prompt将理解任务转化为生成任务。
例如情感分析:"这部电影好看吗?正面还是负面?" → 生成"正面"。
足够大的语言模型通过海量文本预训练,已经隐式地学会了"理解"。
这种方式的灵活性和通用性远超专门设计的理解模型。
Q7: Transformer的时间和空间复杂度?
答:
时间复杂度: O(n² × d),n是序列长度,d是模型维度
空间复杂度: O(n² + n × d),n²来自注意力矩阵
这是Transformer处理长序列的主要瓶颈。
当n=10000时,注意力矩阵需要100M个元素。
这也是后来各种高效Transformer(Linformer, Performer等)的研究动机。
Q8: Pre-Norm和Post-Norm的区别?
答:
Post-Norm: output = LN(x + SubLayer(x)) ← 原始Transformer
Pre-Norm: output = x + SubLayer(LN(x)) ← 后来改进
Pre-Norm训练更稳定,因为残差连接直接传递梯度,不经过LayerNorm。
但有研究发现Post-Norm在充分训练后效果可能更好(梯度更集中)。
大部分现代模型(GPT、LLaMA)使用Pre-Norm。
附录:核心公式速查表
| 公式 | 含义 | 关键点 |
|---|---|---|
Attention(Q,K,V) = softmax(QK^T/√d_k)V | 缩放点积注意力 | √d_k防止梯度消失 |
MultiHead = Concat(heads)W_O | 多头注意力 | 多视角理解 |
FFN(x) = max(0, xW₁+b₁)W₂+b₂ | 前馈网络 | d_ff=4d扩展再压缩 |
LN(x) = γ(x-μ)/√(σ²+ε) + β | 层归一化 | 特征维度归一化 |
PE(pos,2i) = sin(pos/10000^(2i/d)) | 位置编码 | 相对位置可线性表示 |
output = x + SubLayer(LN(x)) | Pre-Norm | 训练更稳定 |
参考文献
- Vaswani et al. (2017). “Attention Is All You Need”. NeurIPS. — Transformer原始论文
- Devlin et al. (2019). “BERT: Pre-training of Deep Bidirectional Transformers”. NAACL.
- Radford et al. (2019). “Language Models are Unsupervised Multitask Learners”. GPT-2.
- Brown et al. (2020). “Language Models are Few-Shot Learners”. GPT-3. NeurIPS.
- Raffel et al. (2020). “Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer”. T5. JMLR.
- Kaplan et al. (2020). “Scaling Laws for Neural Language Models”. arXiv.
- Ba et al. (2016). “Layer Normalization”. arXiv.
- Xiong et al. (2020). “On Layer Normalization in the Transformer Architecture”. ICML.
1182

被折叠的 条评论
为什么被折叠?



