Transformer架构深度详解 —— 从零基础入门到精通
📅 2026/7/21 6:37:22
👁️ 阅读次数
📝 编程学习
目录
- 第一章:序列建模的历史演进
- 第二章:注意力机制的数学原理(超详细推导)
- 第三章:多头注意力的深层设计哲学
- 第四章:位置编码的完整数学推导
- 第五章: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, 直接看x31.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 = 40009.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.19.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.
编程学习
技术分享
实战经验