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):实际内容

检索过程:

  1. 用你的查询和每本书的键计算相似度
  2. 相似度高的书,你花更多时间阅读
  3. 最终你综合各本书的内容形成你的理解
数学表达:
  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)

性质

  1. 输出在(0,1)之间,且所有输出之和=1 → 概率分布
  2. 保持相对大小关系:若z_i > z_j,则softmax(z_i) > softmax(z_j)
  3. 温度控制:实际中有时用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训练更稳定

参考文献

  1. Vaswani et al. (2017). “Attention Is All You Need”. NeurIPS. — Transformer原始论文
  2. Devlin et al. (2019). “BERT: Pre-training of Deep Bidirectional Transformers”. NAACL.
  3. Radford et al. (2019). “Language Models are Unsupervised Multitask Learners”. GPT-2.
  4. Brown et al. (2020). “Language Models are Few-Shot Learners”. GPT-3. NeurIPS.
  5. Raffel et al. (2020). “Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer”. T5. JMLR.
  6. Kaplan et al. (2020). “Scaling Laws for Neural Language Models”. arXiv.
  7. Ba et al. (2016). “Layer Normalization”. arXiv.
  8. Xiong et al. (2020). “On Layer Normalization in the Transformer Architecture”. ICML.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Together_CZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值