Transformer 架构详解
2017 年 Google 论文 Attention Is All You Need 提出,是 GPT、BERT 等大模型的基石。
目录
- [1. 核心概念](#1. 核心概念)
- [2. Token:文本的最小单位](#2. Token:文本的最小单位)
- [3. 整体架构](#3. 整体架构)
- [4. 词嵌入 + 位置编码](#4. 词嵌入 + 位置编码)
- [5. 自注意力机制(Self-Attention)](#5. 自注意力机制(Self-Attention))
- [6. 多头注意力(Multi-Head Attention)](#6. 多头注意力(Multi-Head Attention))
- [7. 前馈网络 + 残差连接 + 层归一化](#7. 前馈网络 + 残差连接 + 层归一化)
- [8. 掩码机制(Masking)](#8. 掩码机制(Masking))
- [9. 交叉注意力(Cross-Attention)](#9. 交叉注意力(Cross-Attention))
- [10. 自回归生成过程](#10. 自回归生成过程)
- [11. 输出层:Logits → Softmax → 选词](#11. 输出层:Logits → Softmax → 选词)
- [12. 训练 vs 推理](#12. 训练 vs 推理)
- [13. Transformer 家族](#13. Transformer 家族)
- [14. 面试高频问题速答](#14. 面试高频问题速答)
- [15. 总结](#15. 总结)
1. 核心概念
一句话概括
Transformer 是一种不依赖循环(RNN),完全靠**注意力机制(Attention)**来理解上下文关系的深度学习架构。
与 RNN 的对比
| 对比项 | RNN/LSTM | Transformer |
|---|---|---|
| 处理方式 | 逐词阅读,顺序处理 | 所有词同时处理,并行计算 |
| 长距离依赖 | 信息会衰减 | 注意力直接连接任意位置,无衰减 |
| 训练速度 | 慢(无法并行) | 快(GPU 友好,可并行) |
| 可扩展性 | 堆层效果有限 | 参数越多能力越强(Scaling Law) |
2. Token:文本的最小单位
Token ≠ 词,Token ≠ 词 ID
| 概念 | 本质 | 例子 |
|---|---|---|
| 文本 | 原始字符串 | "unbelievable" |
| Token | 切分后的最小单元(字符串) | ["un", "##believ", "##able"] |
| Token ID | Token 在词表中的编号(整数) | [489, 23871, 3450] |
为什么要用子词(Subword)级别
| 方案 | 词表大小 | 问题 |
|---|---|---|
| 词级别 | 50万+ | 词表太大,罕见词无法覆盖 |
| 字符级别 | ~100 | 序列太长,语义信息弱 |
| 子词级别 | 3万~10万 | 平衡词表大小和序列长度 ✅ |
主流分词算法
| 算法 | 使用模型 | 原理 |
|---|---|---|
| BPE | GPT 系列 | 从字符开始,反复合并最高频的相邻对 |
| WordPiece | BERT | 类似 BPE,但合并标准是最大化语言模型似然 |
| SentencePiece | T5/LLaMA | 不依赖空格,直接把整句话当字节流处理 |
在 Transformer 中的流程
"我爱NLP"
→ 分词器: ["我", "爱", "N", "LP"] ← Token(字符串)
→ 查词表: [5, 12, 308, 456] ← Token ID(整数)
→ 查嵌入表: [[0.2,0.5,...], ...] ← 嵌入向量
→ 送入 Transformer
3. 整体架构
Transformer 由 Encoder(编码器) 和 Decoder(解码器) 两大部分组成,原始论文中各堆叠 6 层,每层结构相同但参数不同。
3.1 完整架构图

输入: "我爱NLP" 输出: "I love NLP"
│ ▲
▼ │
┌────────────────┐ ┌────────────────┐
│ 词嵌入 │ │ 线性层+Softmax │
│ (Input Embedding)│ │ (输出层,不属于 │
└───────┬────────┘ │ 6层之内) │
│ └────────▲───────┘
▼ │
┌────────────────┐ │
│ 位置编码 │ │
│ (Positional │ │
│ Encoding) │ │
└───────┬────────┘ │
│ │
▼ 相加(⊕) │
┌──────────┐ │
│ X (5×4) │ Encoder最终输出 H (5×4) │
└────┬─────┘ 传给Decoder每一层的 │
│ Cross-Attention的 K 和 V │
▼ (6层都用同一个H) │
┌──────────────────────────────┐ ┌───────────▼───────────────────
│ Encoder Layer 1 │ │ Decoder Layer 1 │
│ ┌────────────────────────┐ │ │ ┌────────────────────────┐ │
│ │ ① 多头自注意力(无掩码) │ │ │ │ ① 掩码多头自注意力 │ │
│ │ 残差 + LayerNorm │ │ │ │ 残差 + LayerNorm │ │
│ ├────────────────────────┤ │ │ ├────────────────────────┤ │
│ │ ② 前馈网络 FFN │ │ │ │ ② 交叉注意力 │ │
│ │ 残差 + LayerNorm │ │ │ │ Q←Decoder K,V←Encoder│ │
│ └────────────────────────┘ │ │ │ 残差 + LayerNorm │ │
│ │ │ ├────────────────────────┤ │
│ (2个子层) │ │ │ ③ 前馈网络 FFN │ │
│ │ │ │ 残差 + LayerNorm │ │
│ │ │ └────────────────────────┘ │
│ │ │ │
│ │ │ (3个子层) │
└──────────────┬───────────────┘ └──────────────▲───────────────┘
│ │
┌──────┴──────┐ ┌──────┴──────┐
│ │ │ │
▼ │ │ │
┌──────────────────────┐ ┌──────┴──────────────┐
│ Encoder Layer 2~5 │ │ Decoder Layer 2~5 │
│ (结构同Layer 1) │ │ (结构同Layer 1) │
│ 每层参数不同 │ │ 每层参数不同 │
└──────────┬───────────┘ └──────▲──────────────┘
│ │
▼ │
┌──────────────────────┐ ┌──────┴──────────────┐
│ Encoder Layer 6 │ │ Decoder Layer 6 │
│ (结构同Layer 1) │ │ (结构同Layer 1) │
└──────────┬───────────┘ └──────▲──────────────┘
│ │
└─────────────────────────────────────┘
Encoder最终输出 H (5×4) 传给Decoder每一层的Cross-Attention的 K 和 V (6层都用同一个H)
在Input Embedding词嵌入前还有一步数据预处理: 分词使用tokenizer分词获取token 和 查词表从词表中查找token id。数据预处理不属于transformer架构中的所以transformer架构图从Input Embedding开始,Input Embedding根据token id从嵌入表中查看相应token的向量。
3.2 Encoder 每层结构(2个子层)
Encoder Layer:
① 多头自注意力(无掩码)+ 残差 + LayerNorm
② 前馈网络 FFN + 残差 + LayerNorm
数据流:
"我爱NLP"
→ 分词 + 嵌入 + 位置编码 → X (5×4)
→ Layer 1: 自注意力 → FFN → 输出
→ Layer 2: 自注意力 → FFN → 输出
→ ...
→ Layer 6: 自注意力 → FFN → 最终输出 H (5×4)
H = 源语言的上下文表示,传给 Decoder 每一层的 Cross-Attention
3.3 Decoder 每层结构(3个子层)
Decoder Layer:
① 掩码自注意力(Masked Self-Attention)+ 残差 + LayerNorm
② 交叉注意力(Cross-Attention)+ 残差 + LayerNorm
③ 前馈网络 FFN + 残差 + LayerNorm
输出层(不属于6层之内):
线性层 + Softmax → 选词
三个子层各自的作用:
| 子层 | Q/K/V 来源 | 作用 |
|---|---|---|
| ① 掩码自注意力 | Q,K,V 都来自 Decoder 输入 | 理解已生成词之间的关系,掩码防止看到未来 |
| ② 交叉注意力 | Q 来自 Decoder,K,V 来自 Encoder | 从源语言中提取相关信息,翻译的依据 |
| ③ FFN | 上一步的输出 | 非线性变换,增强表达能力 |
3.4 Encoder vs Decoder 对比
| 对比项 | Encoder 每层 | Decoder 每层 |
|---|---|---|
| 自注意力 | ✅(无掩码) | ✅(有掩码) |
| 交叉注意力 | ❌ | ✅ |
| 前馈网络 FFN | ✅ | ✅ |
| 子层数 | 2 个 | 3 个 |
| 计算量 | 较小 | 较大(多两次注意力) |
3.5 关键设计要点
- Encoder 只跑一次:输入源语言后,6层处理完输出 H,结果固定不变
- Decoder 循环跑:每生成一个词跑一次,自回归逐词生成
- 6层共用同一个 H:Decoder 每一层的 Cross-Attention 都直接从 Encoder 的同一个输出 H 取 K/V,不是层间传递
- 输出层在6层之外:线性层 + Softmax 是独立的"输出头",不属于 Decoder 的任何一层
4. 词嵌入 + 位置编码
4.1 词嵌入
每个 Token ID 查嵌入表,得到一个 d_model 维向量(原始论文 d_model=512)。
4.2 为什么需要位置编码
Transformer 同时处理所有词,没有顺序概念 。"我打你"和"你打我"如果没有位置编码,模型看到的是完全相同的向量集合。所以需要给每个位置一个独特的指纹。
4.3 位置编码公式
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
pos= 词在句子中的位置(1, 2, 3, ...)i= 维度对的索引(0, 1, 2, ...)d_model= 嵌入维度总数
4.4 为什么需要两个公式(sin + cos)
sin 和 cos 配对 = 单位圆上的一个点 (x, y) = 唯一确定一个角度状态。
单独用 sin 会有歧义:sin(30°) = sin(150°) = 0.5,加上 cos 就能区分:cos(30°) = 0.866 ≠ cos(150°) = -0.866。
4.5 多频率的意义
不同维度对使用不同频率,就像时钟的秒针、分针、时针:
维度对0 (i=0): 频率 = 1/10000^(0/d) ← 高频(秒针),区分相邻位置
维度对1 (i=1): 频率 = 1/10000^(2/d) ← 中频(分针)
...
维度对N (i=N): 频率 = 1/10000^(2N/d) ← 低频(时针),区分远距离位置
- 高频:能精确区分相邻位置,但周期短,远距离会重复
- 低频:无法区分相邻位置,但周期长,能区分远距离位置
- 组合:近看靠高频,远看靠低频
4.6 相对位置的表达
sin((pos+k)·ω) = sin(pos·ω)·cos(k·ω) + cos(pos·ω)·sin(k·ω)
cos((pos+k)·ω) = cos(pos·ω)·cos(k·ω) - sin(pos·ω)·sin(k·ω)
→ 偏移量 k 对应一个固定的旋转矩阵,不依赖绝对位置 pos
→ 位置3→5 和 位置100→102 的变换完全相同
→ 模型能学到"相隔k个位置"的一致语义关系
4.7 计算示例(d_model=4,5个位置)
维度对0: ω₀ = 1/10000^(0/4) = 1.0
维度对1: ω₁ = 1/10000^(2/4) = 0.01
sin(ω₀) cos(ω₀) sin(ω₁) cos(ω₁)
pos 1 [ 0.84, 0.54, 0.01, 1.00 ]
pos 2 [ 0.91, -0.42, 0.02, 1.00 ]
pos 3 [ 0.14, -0.99, 0.03, 1.00 ]
pos 4 [ -0.76, -0.65, 0.04, 1.00 ]
pos 5 [ -0.96, 0.28, 0.05, 1.00 ]
前两列(高频)变化剧烈 → 区分相邻位置
后两列(低频)变化缓慢 → 区分远距离位置
4.8 最终输入 = 词嵌入 + 位置编码
最终输入 = Embedding(token_id) + PE(pos)
5. 自注意力机制(Self-Attention)
5.1 三个角色
| 角色 | 含义 | 类比 |
|---|---|---|
| Query (Q) | "我在找什么信息?" | 图书馆搜索的关键词 |
| Key (K) | "我能提供什么信息?" | 每本书的标签/标题 |
| Value (V) | "我的实际内容是什么" | 书的正文内容 |
5.2 计算步骤
① 生成 Q, K, V: X × W_Q → Q, X × W_K → K, X × W_V → V
(三个权重矩阵 W_Q, W_K, W_V 各不相同,训练学习)
② 计算注意力分数: Score = Q × Kᵀ / √d_k
(除以 √d_k 防止分数过大)
③ Softmax 归一化: 把分数变成概率分布(0~1,和为1)
④ 加权求和 V: Output = Attention权重 × V
5.3 为什么除以 √d_k
当 d_k 较大时,Q·K 的点积值会很大,导致 softmax 梯度变得很小(进入饱和区)。除以 √d_k 把分数缩回到合理范围,保证梯度稳定。
5.4 Softmax 归一化
softmax(x_i) = e^(x_i) / Σ(e^(x_j))
为什么需要 Softmax 而不是直接用原始分数:
| 原因 | 说明 |
|---|---|
| 变成权重 | 分数可负、无界,Softmax 后全为正数,和为1,天然是权重 |
| 尺度稳定 | 不归一化时序列越长输出越大,Softmax 后无论多长都稳定 |
| 放大差异 | e^x 指数放大,让强信号更突出(差异大时"赢家通吃") |
5.5 加权求和 V 的含义
每个词不再只代表自己,而是变成了"我 + 我关注的那些词的信息"的融合体。
"自然"的输出 = 24%×V(自然) + 22%×V(语言) + 21%×V(我) + 18%×V(处理) + 16%×V(爱)
= "自然语言处理"这个整体概念的上下文表示
→ 同一个词在不同句子中,因为上下文不同,会得到不同的输出向量
→ 模型因此能理解多义词、指代关系、长距离依赖
5.6 为什么用 V 而不是原始输入 X
X = 词嵌入 + 位置编码 → "我是谁,我在哪"(原始身份)
V = X × W_V(线性变换) → "我能提供什么信息"(投影后的内容)
类比: X = 一个人的身份证, V = 这个人的专业技能
5.7 计算示例("我"的注意力)
分数: [0.25, 0.33, 0.66, 0.42, 0.03]
e^x: [1.28, 1.39, 1.93, 1.52, 1.03] Sum=7.16
softmax: [0.18, 0.19, 0.27, 0.21, 0.14] ← "自然"权重最高
加权求和: Out(我) = 0.18×V(我) + 0.19×V(爱) + 0.27×V(自然) + 0.21×V(语言) + 0.14×V(处理)
6. 多头注意力(Multi-Head Attention)
核心思想
一个注意力头只关注一种关系。多个头可以关注不同维度的关系:
Head 1: 关注语法关系(主谓宾)
Head 2: 关注语义关系(同义/反义)
Head 3: 关注指代关系("他"指代谁)
...
Head 8: 关注长距离依赖
→ 拼接所有头的结果 → 线性变换 → 最终输出
计算流程
① 把 d_model 维向量分成 h 个头,每个头 d_k = d_model / h 维
② 每个头独立计算 Q·Kᵀ/√d_k → softmax → 加权V
③ 拼接所有头的输出: Concat(head₁, head₂, ..., headₕ) → (seq_len × d_model)
④ 乘以输出权重矩阵 W_O → 最终输出
7. 前馈网络 + 残差连接 + 层归一化
7.1 前馈网络(FFN)
FFN(x) = max(0, x × W₁ + b₁) × W₂ + b₂
第一层: d_model → 4×d_model(扩展4倍)+ ReLU 激活
第二层: 4×d_model → d_model(压缩回来)
→ 对每个位置独立做非线性变换,模型能学到复杂的特征变换,增强表达能力
7.2 残差连接
输出 = LayerNorm(x + SubLayer(x))
→ 把输入直接加回来,保证"原始信息不丢失"
→ 解决深层网络梯度消失问题
→ 注意力/FFN 只是"额外添加上下文"
7.3 层归一化(LayerNorm)
层归一化的含义: 残差连接后,训练模型时不同位置的向量数值范围差异很大(有的均值 1.15,有的均值 0.12),直接传入下一层会导致训练不稳定。LayerNorm 把每个位置的向量独立地归一化到"均值≈0、方差≈1"的统一尺度,让所有位置在同一数值范围内被下一层处理,从而稳定训练、加速收敛;推理时维持数值稳定性(和训练时一致的归一化操作)
对每一行(每个词的向量)独立计算:
① 均值: μ = (x₁ + x₂ + ... + x_d) / d
② 方差: σ² = Σ(x_i - μ)² / d
③ 归一化: x̂_i = (x_i - μ) / √(σ² + ε)
④ 缩放偏移: y_i = γ × x̂_i + β (γ, β 是可学习参数)
计算示例("我"的向量 1.28, 1.18, 0.09, 2.05):
μ = (1.28 + 1.18 + 0.09 + 2.05) / 4 = 1.15
σ² = [(0.13)² + (0.03)² + (-1.06)² + (0.90)²] / 4 = 0.488
σ = √0.488 = 0.699
归一化: [(1.28-1.15)/0.699, (1.18-1.15)/0.699, (0.09-1.15)/0.699, (2.05-1.15)/0.699]
= [0.19, 0.04, -1.52, 1.29]
→ 均值≈0,方差≈1,所有行统一到相同尺度
归一化前后对比:
归一化前(数值范围不统一):
"我": [1.28, 1.18, 0.09, 2.05] → 均值1.15,范围 [0.09, 2.05]
"爱": [1.92, -0.75, 0.55, 1.34] → 均值0.77,范围 [-0.75, 1.92]
"处理":[-0.61,-1.13, 0.38, 1.83] → 均值0.12,范围 [-1.13, 1.83]
→ 每行数值范围差异大,下一层处理时不稳定
归一化后(统一到相同尺度):
"我": [0.19, 0.04, -1.52, 1.29] → 均值≈0,方差≈1
"爱": [1.15, -1.51, -0.21, 0.57] → 均值≈0,方差≈1
"处理":[0.27, -0.89, 0.51, 0.11] → 均值≈0,方差≈1
→ 所有行统一尺度,训练稳定
为什么用 LayerNorm 而不是 BatchNorm:
| 对比 | BatchNorm | LayerNorm |
|---|---|---|
| 归一化方向 | 对同一维度跨样本归一化(列方向) | 对同一样本的所有维度归一化(行方向) |
| 适用场景 | CNN(图像) | Transformer/RNN |
| batch size 依赖 | 依赖,变化时不稳定 | 不依赖,每个样本独立计算 |
γ 和 β 的作用: 归一化后所有维度被强制拉到均值0、方差1,但并非每个维度都需要相同的分布。γ(缩放)和 β(偏移)让模型自己决定每个维度的最佳数值分布(初始 γ=1, β=0,训练中学习调整)。
8. 掩码机制(Masking)
8.1 为什么需要掩码
训练时: Decoder 一次性处理整个目标序列,"love" 能看到 "NLP"
推理时: 逐词生成,生成 "love" 时 "NLP" 还不存在
→ 如果训练时不加掩码,"love" 会依赖 "NLP" 的信息
→ 推理时没有 "NLP",模型就"懵了"
→ 必须用掩码强制切断未来信息
8.2 掩码矩阵
输入: [<START>, I, love, NLP, <END>]
掩码矩阵 M (5×5):
START I love NLP END
START [ 0, -∞, -∞, -∞, -∞ ]
I [ 0, 0, -∞, -∞, -∞ ]
love [ 0, 0, 0, -∞, -∞ ]
NLP [ 0, 0, 0, 0, -∞ ]
END [ 0, 0, 0, 0, 0 ]
0 = 允许看(不遮挡)
-∞ = 遮挡(不允许看未来的词)
规律: 下三角矩阵,当前位置只能看自己和之前的位置
8.3 掩码如何参与计算
第1步: 算注意力分数 Q·Kᵀ/√d_k
START I love
I [ 0.48, 0.82, 0.65]
第2步: 加掩码(未来位置变 -∞)
START I love
I [ 0.48, 0.82, -∞ ]
第3步: Softmax(-∞ → 0,未来词权重为0)
START I love
I [ 0.42, 0.58, 0.00]
第4步: 加权求和 V(未来词的 V 贡献为0)
Out(I) = 0.42×V(START) + 0.58×V(I) + 0.00×V(love)
8.4 推理时掩码的作用
推理时虽然没有"未来词",掩码仍然必须加,目的是保持和训练时一致。
不加掩码: <START> 会看到 I 和 love → 它的表示和训练时不一致 → 预测出错
加掩码: 每个位置只看该看的 → 和训练时一致 → 预测正确
本质: 掩码 = 训练和推理之间的"一致性保证"
8.5 只有一个 <START> 时的掩码
序列长度=1,掩码矩阵 M (1×1) = [0]
→ 没有未来词可遮,掩码不起作用
→ 注意力权重 = [1.0](100%关注自己)
→ 输出 = V 本身
9. 交叉注意力(Cross-Attention)
9.1 Q/K/V 来源
Q 来自 Decoder 当前输出: "我需要什么信息"
K 来自 Encoder 最终输出 H: "源文本各词的标签"
V 来自 Encoder 最终输出 H: "源文本各词的内容"
9.2 一个 Encoder 输出如何变成 K 和 V
Encoder 输出只有一个矩阵 H (5×4)
K = H × W_K^cross ← 乘以一个权重矩阵,提取"可被匹配的标签"
V = H × W_V^cross ← 乘以另一个权重矩阵,提取"可被提取的内容"
类比: 同一份档案,用"关键词提取器"得到标签(K),用"内容提取器"得到详情(V)
9.3 6层的 K/V 都来自同一个 Encoder 输出
✅ 正确理解:
Layer 1~6 的 Cross-Attention 中,K 和 V 每层都直接来自 Encoder 的同一个输出 H
不同的是每层有自己的 W_K 和 W_V,从同一个 H 中提取不同维度的信息
❌ 错误理解:
Layer 2 的 K,V 来自 Layer 1(不是!)
9.4 计算示例
Decoder的Q: [0.45, 0.30, 1.35, 0.50]
Encoder的K (5×4):
我=[0.72,0.48,0.15,1.20], 爱=[1.55,-0.35,0.62,0.88], ...
注意力分数:
Score(<START>, 我) = Q·K(我)/2 = 0.635
Score(<START>, 爱) = Q·K(爱)/2 = 0.935
Score(<START>, 自然) = Q·K(自然)/2 = 1.012 ← 最高
Score(<START>, 语言) = Q·K(语言)/2 = 0.346
Score(<START>, 处理) = Q·K(处理)/2 = 0.498
softmax → [0.184, 0.249, 0.269, 0.138, 0.161]
→ <START> 最关注 "自然"(0.269) 和 "爱"(0.249)
9.5 三种注意力对比
| 注意力类型 | Q 来源 | K 来源 | V 来源 |
|---|---|---|---|
| Encoder 自注意力 | Encoder 输入 X | Encoder 输入 X | Encoder 输入 X |
| Decoder 掩码自注意力 | Decoder 输入 Y | Decoder 输入 Y | Decoder 输入 Y |
| 交叉注意力 | Decoder 输出 | Encoder 输出 H | Encoder 输出 H |
10. 自回归生成过程
10.1 核心机制
把上一步的输出,当作下一步的输入:
自己生成的结果 → 回头喂给自己 → 生成下一个结果 → 再喂回去 → ...
就像接龙:
看到 <START> → 接 "I"
看到 <START> I → 接 "love"
看到 <START> I love → 接 "NLP"
看到 <START> I love NLP → 接 <END>(停止)
10.2 完整流程("我爱NLP" → "I love NLP")
Encoder(只跑1次): "我爱NLP" → H (5×4)
Decoder(循环跑):
步骤1: 输入 [<START>] → 预测 "I"
步骤2: 输入 [<START>, I] → 预测 "love"
步骤3: 输入 [<START>, I, love] → 预测 "NLP"
步骤4: 输入 [<START>, I, love, NLP] → 预测 <END> → 停止
最终输出: "I love NLP"
10.3 每步输入的变化
时间步 Decoder输入 Encoder参考 预测输出
───────────────────────────────────────────────────────────
1 [<START>] "我爱NLP" → "I"
2 [<START>, I] "我爱NLP" → "love"
3 [<START>, I, love] "我爱NLP" → "NLP"
4 [<START>, I, love, NLP] "我爱NLP" → "<END>"
每次都把上一步的输出加到输入末尾
10.4 为什么每步只取最后一个位置的输出
输入: [<START>, I, love]
Decoder 输出3个向量:
位置0 (<START>) → 向量A ← 预测"I"时用的,现在不需要了
位置1 (I) → 向量B ← 预测"love"时用的,现在不需要了
位置2 (love) → 向量C ← 用这个预测下一个词 "NLP"
→ 每次只需要最后一个位置的输出
→ 因为它包含了"看完所有已生成词后,下一个词应该是什么"的信息
10.5 停止条件
- 模型预测出
<END>标记 → 停止 - 如果一直不输出
<END>,设置最大长度强制停止
11. 输出层:Logits → Softmax → 选词
11.1 线性层
Decoder 6层最终输出: [1.85, 0.32, 0.15, 0.92] (4维)
│
▼ 乘以权重矩阵 W (4×10000[嵌入表有10000各token]),W 是训练学习的
│
Logits (1×10000): [8.5, 2.1, 1.8, 0.3, -0.5, ...]
I love the NLP <END> ...
→ 每个词的分数 = Decoder输出向量 和 该词权重向量的点积
→ 点积越大,说明 Decoder 输出和该词越"匹配"
Logits 的维度(训练 vs 推理):
训练时: n×10000,每个位置都有 logits,并行预测所有词
推理时: n×10000,但只用最后一个位置的 logits 预测下一个词
11.2 Softmax → 概率分布
P(I) = e^8.5 / Σ = 99.67% ← 最高
P(love) = e^2.1 / Σ = 0.17%
P(the) = e^1.8 / Σ = 0.12%
...
11.3 选词策略
| 策略 | 方式 | 适用场景 |
|---|---|---|
| Greedy(贪心) | 选概率最高的 | 简单快速,翻译任务 |
| Beam Search(束搜索) | 保留 top-k 条路径,综合选最优 | 翻译任务常用 |
| Top-k / Top-p Sampling | 从 top-k 中随机采样 | ChatGPT 等生成任务,增加多样性 |
12. 训练 vs 推理
12.1 核心区别
| 对比项 | 训练 | 推理 |
|---|---|---|
| Decoder 输入 | 完整目标序列(一次性输入) | 逐词输入(自回归) |
| 计算方式 | 并行计算所有位置 | 串行,逐词生成 |
| 掩码作用 | 防止看到未来词(模拟自回归) | 保持和训练一致 |
| 输出 | 所有位置的 logits 都用 | 只用最后一个位置的 logits |
| Encoder | 每次重新算 | 只算一次,结果固定 |
| 速度 | 快(并行) | 慢(串行,越长越慢) |
12.2 训练时的 Teacher Forcing
训练时不用模型自己生成的词作为输入,而是用真实答案(Ground Truth):
输入: [<START>, I, love, NLP]
目标: [I, love, NLP, <END>]
位置1: <START> → 预测 I (和目标 I 比较,算 loss)
位置2: I → 预测 love (和目标 love 比较,算 loss)
...
好处: 不依赖前一步的预测结果,避免错误累积,训练效率高
12.3 推理效率优化:KV Cache
朴素推理的问题:
步骤1: 输入 [<START>] → Decoder 处理 1 个词 → 预测 "I"
步骤2: 输入 [<START>, I] → Decoder 处理 2 个词 → 预测 "love"
步骤3: 输入 [<START>, I, love] → Decoder 处理 3 个词 → 预测 "NLP"
步骤4: 输入 [<START>, I, love, NLP] → Decoder 处理 4 个词 → 预测 <END>
问题: 步骤2 重新计算了 <START> 的 K,V;步骤3 又重新计算了 <START>, I 的 K,V...
大量重复计算!序列越长浪费越严重
KV Cache 的核心思想:
缓存已计算过的 K, V,每步只计算新词的 K, V:
步骤1: 输入 [<START>]
→ 计算 K₁,V₁ → 存入缓存
→ 缓存: K=[K₁], V=[V₁]
→ 用完整 K,V 做 attention → 预测 "I"
步骤2: 输入 [I](只输入新词!)
→ 计算 K₂,V₂ → 追加到缓存
→ 缓存: K=[K₁,K₂], V=[V₁,V₂]
→ 用缓存中完整的 K,V 做 attention → 预测 "love"
步骤3: 输入 [love](只输入新词!)
→ 计算 K₃,V₃ → 追加到缓存
→ 缓存: K=[K₁,K₂,K₃], V=[V₁,V₂,V₃]
→ 用缓存中完整的 K,V 做 attention → 预测 "NLP"
→ 每步只处理 1 个新词,不重复计算之前的词
朴素推理 vs KV Cache 对比:
| 对比项 | 朴素推理 | KV Cache |
|---|---|---|
| 每步输入 | 完整序列(n 个词) | 只输入新词(1 个词) |
| 每步计算量 | O(n) | O(1)(只算新词的 K,V) |
| 重复计算 | 大量重复 | 无重复 |
| 额外开销 | 无 | 需要存储 K,V 缓存(显存换速度) |
| 总计算量(n 步) | O(n²) | O(n) |
输出层也只算最后一个位置:
Decoder 6层最终输出: n × d_model 矩阵
线性层: 输出 × W → n × vocab_size 的 logits
朴素做法: 计算所有 n 行的 logits → 只用最后一行做 softmax
优化做法: 只取最后一行 → 只计算 1 × d_model × W → 1 × vocab_size
→ 和 KV Cache 配合,每步只处理 1 个词,从输入到输出全程无浪费
13. Transformer 家族
| 模型 | 类型 | 特点 | 适用任务 |
|---|---|---|---|
| BERT | 仅 Encoder | 双向理解 | 分类、问答、NER |
| GPT 系列 | 仅 Decoder | 自回归生成 | 对话、写作、代码生成 |
| T5 / BART | Encoder + Decoder | 通用 seq2seq | 翻译、摘要 |
| ViT | Encoder 变体 | 图像分 patch 当"词" | 图像分类 |
14. 面试高频问题速答
Q1: Transformer 为什么比 RNN 好?
并行计算(所有位置同时处理)、长距离依赖无衰减、可扩展性强(参数越多能力越强)。
Q2: 自注意力的 Q/K/V 分别是什么?
Q(Query)= "我在找什么信息",K(Key)= "我能提供什么信息",V(Value)= "我的实际内容"。通过 Q·Kᵀ 算关联度,softmax 归一化后加权求和 V,得到上下文感知的表示。
Q3: 为什么除以 √d_k?
防止点积值过大导致 softmax 进入饱和区,梯度消失。√d_k 是点积方差的标准化因子。
Q4: 位置编码为什么用 sin/cos?
三个原因:①有界(值在 -1,1);②每个位置有唯一编码(sin+cos 配对确定单位圆上的点);③能表达相对位置(偏移 k 对应固定旋转矩阵,不依赖绝对位置)。多频率设计让高频区分近距离、低频区分远距离。
Q5: Decoder 的掩码是什么?为什么需要?
上三角矩阵,把未来位置的注意力分数设为 -∞,softmax 后变成 0。训练时防止"偷看"未来词,推理时保持和训练一致。本质是训练和推理之间的"一致性保证"。
Q6: 交叉注意力的 K/V 从哪来?
来自 Encoder 的最终输出 H。H 乘以 W_K 得到 K,乘以 W_V 得到 V。6 层 Decoder 的每一层都直接从同一个 H 取 K/V(不是层间传递),但每层的 W_K/W_V 不同,提取不同维度的信息。
Q7: Decoder 是怎么逐词生成的?
自回归:把上一步的输出加到输入末尾,作为下一步的输入。每次只用最后一个位置的输出预测下一个词。遇到
<END>停止。
Q8: 训练和推理有什么区别?
训练时并行处理完整序列(Teacher Forcing),所有位置的 logits 都用;推理时串行逐词生成,只用最后一个位置的 logits。训练快(并行),推理慢(串行)。
Q9: LayerNorm 和 BatchNorm 的区别?
BatchNorm 对同一维度跨样本归一化(列方向),依赖 batch size;LayerNorm 对同一样本的所有维度归一化(行方向),不依赖 batch size。Transformer 用 LayerNorm 因为序列长度可变且 batch size 不稳定。
Q10: 多头注意力为什么有用?
不同的头可以关注不同维度的关系(语法、语义、指代、长距离依赖等),拼接后得到更丰富的表示。
Q11: Logits 是什么?
线性层输出的每个词的"原始分数"(未归一化)。Decoder 输出 d_model 维向量,乘以权重矩阵 W (d_model × vocab_size) 得到 vocab_size 维的 logits,每个维度对应词表中一个词的得分。
Q12: Transformer 的参数量主要在哪?
注意力层的 Q/K/V 权重矩阵、FFN 的两层全连接(占参数量最大,因为扩展 4 倍)、嵌入矩阵(vocab_size × d_model)。层越深、d_model 越大、词表越大,参数越多。
附:关键数字对照
| 参数 | 教学示例 | GPT-2 Small | GPT-3 |
|---|---|---|---|
| d_model | 4 | 768 | 12,288 |
| 注意力头数 | 1 | 12 | 96 |
| 层数 | 6 | 12 | 96 |
| 词表大小 | ~100 | 50,257 | 50,257 |
| 参数量 | ~几千 | 1.17亿 | 1,750亿 |
核心机制完全一样,只是规模不同。这就是 Scaling Law 的魅力------同一架构,参数越多能力越强。
15 总结
text
- 数据预处理用户输入的文本
- 分词: Tokenizer 将原始文本切分为子词级别的 Token,如 "unbelievable" → "un", "##believ", "##able"
- 查词表: 根据 Token 字符串在词表中查找对应的整数编号(Token ID),如 "我","爱" → 5, 12
- 编码器
- Input Embedding:用 Token ID 查嵌入表,把整数 ID 映射为 d_model 维稠密向量,让模型能在连续空间中处理文本
- +位置编码
- 位置编码意义:Transformer 并行处理所有词没有顺序概念,位置编码给每个位置一个独特的"指纹",让模型区分"我打你"和"你打我"
- 6层
- 多头自注意力
- 计算QKV
- Q: 输入 X 乘以权重矩阵 W_Q,表示"我在找什么信息"
- K: 输入 X 乘以权重矩阵 W_K,表示"我能提供什么信息"
- V: 输入 X 乘以权重矩阵 W_V,表示"我的实际内容是什么"
- 计算 softmax(QK^T/√d_k)V
- QK^T的意义:计算每个词的 Query 和所有词的 Key 的点积,得到词与词之间的关联度分数矩阵,点积越大说明两个词越相关
- /√d_k的意义:当 d_k 较大时点积值会很大,导致 softmax 进入饱和区梯度消失;除以 √d_k 把分数缩回合理范围,保持梯度稳定
- softmax的意义:QK^T 算出的注意力分数有三个问题------可正可负(负数无法作为权重)、无上界(某个词分数特别大时会完全压制其他词)、总和不确定(序列越长总分越大,导致加权求和后的输出幅度不稳定)。Softmax 通过 e^x 指数运算一次性解决这三个问题:所有值变成正数、归一化到和为1(无论序列多长输出幅度都稳定)、指数放大差异让强信号更突出,从而得到一组公平、可比较的注意力权重
- softmax()V的意义:用归一化后的注意力权重对 V 加权求和,让每个词的输出变成"自己 + 关注的那些词的信息"的融合体,实现上下文感知
- 残差连接+层归一化
- 残差连接的操作:把子层输入直接加到子层输出上,即 Output = x + SubLayer(x)
- 残差连接意义:保证原始信息不丢失,解决深层网络梯度消失问题,让注意力/FFN 只是"额外添加上下文"
- 层归一化操作:对每个词的向量独立计算均值和方差,归一化到均值≈0、方差≈1,再用 γ 缩放、β 偏移
- 层归一化意义:统一各位置向量的数值范围,稳定训练、加速收敛;推理时维持和训练一致的计算流程
- 前馈网络
- 操作:两层全连接,先 d_model→4×d_model 扩展,经 ReLU 激活,再 4×d_model→d_model 压缩回来
- 意义:对每个位置独立做非线性变换;没有 ReLU 多层线性变换会坍缩成一层,模型丧失非线性表达能力。自注意力本质是加权求和(线性操作),只能决定"看哪些词",无法改变特征本身的表达。FFN 通过 ReLU 引入非线性,让模型能学到"如果特征A强且特征B弱,则激活特征C"这类复杂的条件逻辑。如果全程都是线性变换,无论堆多少层都等价于一层 y = Wx + b,只能画直线、切平面,无法拟合语言中大量的非线性规律(如多义词在不同上下文含义不同、语法规则的条件触发等)。一句话:注意力负责"信息路由",FFN 的非线性负责"特征加工",缺一不可
- 残差连接+层归一化
- 数据预处理已经生成的文本
- 分词: Tokenizer 将已生成的文本切分为 Token,与编码器使用同一个分词器
- 查词表: 查找每个 Token 对应的 Token ID,与编码器共用同一个词表
- 解码器
- Output Embedding:用 Token ID 查嵌入表得到向量,与编码器的 Input Embedding 共用同一张嵌入表
- +位置编码
- 6层
- 掩码多头自注意力
- 操作:和普通自注意力一样算 QK^T/√d_k,但加上三角掩码矩阵把未来位置的分数设为 -∞,softmax 后权重变 0
- 意义:防止当前位置"偷看"未来的词,保证训练时的计算方式和推理时逐词生成一致
- 残差连接+层归一化
- 交叉多头自注意力:
- KV 来自于编码器输出 H×W_K、H×W_V,Q 来自于解码器掩码多头自注意力的输出×W_Q
- 残差连接+层归一化
- 前馈网络
- 残差连接+层归一化
- Linear
- 操作:Decoder 6层最终输出 × 权重矩阵 W (d_model × vocab_size) 得到 logits,每个维度对应词表中一个词的得分
实际工程中的做法:
- 训练时
不用 KV Cache
所有位置并行计算(本来就需要所有位置的 logits 算 loss)
线性层 n×vocab_size 全算,没有浪费
- 推理时
用 KV Cache
每次只输入1个新词
Decoder 6层只处理1个位置
线性层只算1行
→ 几乎零浪费
- 意义:把 Decoder 的抽象向量映射到词表空间,给每个词打一个"匹配分数",充当"抽象向量→选词"的桥梁
- Softmax:将 logits 转成概率分布(0~1、和为1),分数越高的词概率越大
- 选词策略
- temperature:控制概率分布的"陡峭程度",值越低分布越尖锐(更确定),值越高分布越平坦(更随机)
- top_k:只从概率最高的 k 个词中采样,直接截断低概率词,k 越小越保守
- top_p:从概率最高的词开始累加,直到累计概率达到 p(如 0.9)为止,只在这个范围内采样,比 top_k 更灵活