前言
GPT类模型本质上是自回归语言模型,通过预测下一个词来逐步生成文本。不同的解码策略会显著影响生成文本的质量、多样性和创造性。
一、GPT-2 的核心流程
1.1 基本工作流程
text
输入 → 预测下一个词 → 拼接到输入 → 重复直到完成
1.2 生成案例
生成5个词的过程:
text
输入:"I have a dream"
↓
第1次预测 → "about" → "I have a dream about"
第2次预测 → "my" → "I have a dream about my"
第3次预测 → "future" → "I have a dream about my future"
第4次预测 → "in" → "I have a dream about my future in"
第5次预测 → "the" → "I have a dream about my future in the"
1.3 关键特性
| 特点 | 说明 |
|---|---|
| 自回归 | 每次生成1个词,依赖前面所有词 |
| 贪心 | 每次都选概率最高的词(基本策略) |
| 递推 | 新词立即加入上下文,继续预测 |
1.4 理论基础
理论上,预测下一个词需要计算所有已知序列的联合概率:
text
P(w₁w₂...wₙ) = P(w₁) × P(w₂|w₁) × P(w₃|w₁w₂) × ... × P(wₙ|w₁...wₙ₋₁)
GPT的简化机制:
- 最后一个token的隐藏状态已经编码了所有前面token的信息
- 预测时只需使用最后一个token的表示,无需重新处理整个序列
- 这大幅提升了计算效率
二、解码策略详解
2.1 贪心搜索(Greedy Search)
核心思想
每次从词表(vocab)中选择概率最大的一个token,只考虑当前步的局部最优。
示例流程
| 步骤 | 输入 | 最可能的token |
|---|---|---|
| Step 1 | “I have a dream” | ” of” |
| Step 2 | “I have a dream of” | ” being” |
| Step 3 | “I have a dream of being” | ” a” |
| Step 4 | “I have a dream of being a” | ” doctor” |
| Step 5 | “I have a dream of being a doctor” | ”.” |
优缺点
| 优点 | 缺点 |
|---|---|
| 计算速度快 | 短视问题:只考虑局部最优,忽略全局 |
| 实现简单 | 容易产生重复、平庸的文本 |
| 确定性输出 | 缺乏创造力,无法生成多样化内容 |
核心问题
每一步选择概率最高的词,但概率最高的单步选择不一定导致全局最优的句子。
示例:
- 第1步:”a” 的概率 0.5 vs “the” 的概率 0.45
- 贪心选”a”,但后续”beautiful”概率很高 → “a beautiful…”
- 但如果选”the”,后续”most important”概率极高 → “the most important…”
- 贪心选择了当下的0.5,错失了未来的更高概率组合
2.2 集束搜索(Beam Search)
核心思想
维护K个最优候选路径(K = beam size),每一步都保留K个最可能的token组合,最终选择联合概率最高的完整序列。
工作原理
text
Beam Size = 2 的示例:
第1步:从所有token中选概率最高的2个
"I have a" → "dream"(0.4), "vision"(0.3)
第2步:每条路径各扩展2个候选,共4条路径
路径1: "dream" → "about"(0.3), "of"(0.25)
路径2: "vision" → "for"(0.2), "of"(0.15)
排序后保留综合概率最高的2条
保留: "dream about"(0.12), "dream of"(0.10)
第3步:继续扩展,重复直到结束
最终从所有路径中选择得分最高的完整序列
路径数量对比
| 时间步 | Greedy路径数 | Beam Search路径数(beam=2) |
|---|---|---|
| 第1步 | 1 | 2 |
| 第2步 | 1 | 4 (2²) |
| 第3步 | 1 | 8 (2³) |
| 第k步 | 1 | 2ᵏ |
优缺点
| 优点 | 缺点 |
|---|---|
| 考虑全局最优,比贪心更优 | 计算成本高(2ᵏ倍) |
| 能找到概率更高的序列 | 仍然倾向于重复、无创造力的文本 |
| 适合翻译、摘要等任务 | 无法产生多样性,容易陷入”平庸陷阱” |
2.3 Top-k 采样
核心思想
引入随机性,打破确定性选择,提升生成文本的创造力和多样性。
工作流程
text
Step 1: 计算所有token的概率
Step 2: 按概率排序,只保留概率最高的k个token
Step 3: 对保留的k个token重新归一化(重新计算概率)
Step 4: 从重归一化后的分布中随机采样
具体案例(k=5)
假设原始概率分布:
| Token | 原始概率 | 排名 |
|---|---|---|
| “the” | 0.45 | 1 |
| “a” | 0.25 | 2 |
| “an” | 0.12 | 3 |
| “my” | 0.08 | 4 |
| “our” | 0.05 | 5 |
| “some” | 0.03 | 6 ← 丢弃 |
| “any” | 0.02 | 7 ← 丢弃 |
重新归一化后:
| Token | 原始概率 | 重归一化概率 |
|---|---|---|
| “the” | 0.45 | 0.45/0.95 ≈ 0.474 |
| “a” | 0.25 | 0.25/0.95 ≈ 0.263 |
| “an” | 0.12 | 0.12/0.95 ≈ 0.126 |
| “my” | 0.08 | 0.08/0.95 ≈ 0.084 |
| “our” | 0.05 | 0.05/0.95 ≈ 0.053 |
关键效果:
- 原本概率很小的token(如”our” 0.05 → 0.053)概率略微提升
- 所有保留的token都有机会被选中,而不是只选最大的
随机采样
python
# 使用 torch.multinomial 根据概率随机选择
next_token = torch.multinomial(probabilities, 1)
# 概率高的被选中的概率大,但不保证一定被选中
优缺点
| 优点 | 缺点 |
|---|---|
| 引入随机性,输出更丰富 | k值选择主观,没有统一标准 |
| 避免完全重复和平庸 | k值固定,不能自适应 |
| 比贪心更有创造力 | 可能偶尔选到不合理的词(如果k设太大) |
k值的影响:
- k太小 → 确定性高,多样性不足
- k太大 → 可能选到低质量token,文本质量下降
2.4 核采样(Nucleus Sampling / Top-p Sampling)
核心思想
不固定选择前k个token,而是动态选择累积概率超过阈值p的最小token集合,从这组token中采样。
为什么优于Top-k?
| Top-k的问题 | Nucleus的解决方案 |
|---|---|
| k值固定,无法自适应 | 根据概率分布动态调整候选集大小 |
| 概率集中时,k可能太大(包含不合理词) | 自动缩小候选集 |
| 概率分散时,k可能太小(漏掉合理词) | 自动扩大候选集 |
详细工作流程
text
Step 1: 按概率降序排序所有token
Step 2: 计算累积概率
Step 3: 找到累积概率首次超过p的位置,作为截断点
Step 4: 只保留截断点之前的token
Step 5: 对保留的token重新归一化
Step 6: 从新分布中随机采样
完整案例(p=0.7)
场景:概率集中分布
| 排名 | Token | 概率 | 累积概率 | 是否保留 |
|---|---|---|---|---|
| 1 | “the” | 0.50 | 0.50 | ✅ 保留 (0.50 < 0.7) |
| 2 | “a” | 0.25 | 0.75 | ❌ 停止 (0.75 > 0.7) |
结果:只保留1个token,”the”被选中概率≈100%,几乎变成贪心。
场景:概率分散分布
| 排名 | Token | 概率 | 累积概率 | 是否保留 |
|---|---|---|---|---|
| 1 | “the” | 0.20 | 0.20 | ✅ 保留 (0.20 < 0.7) |
| 2 | “a” | 0.18 | 0.38 | ✅ 保留 (0.38 < 0.7) |
| 3 | “an” | 0.15 | 0.53 | ✅ 保留 (0.53 < 0.7) |
| 4 | “my” | 0.12 | 0.65 | ✅ 保留 (0.65 < 0.7) |
| 5 | “our” | 0.10 | 0.75 | ❌ 停止 (0.75 > 0.7) |
结果:保留4个token,候选集自动扩大,保持多样性。
重归一化和采样
保留token后,重新归一化并随机采样:
python
# 重新归一化
new_probs = original_probs[top_p_indices] / sum(original_probs[top_p_indices])
# 随机采样
next_token = torch.multinomial(new_probs, 1)
参数影响
| 参数 | p=0.9(大) | p=0.3(小) |
|---|---|---|
| 候选集大小 | 较大(包含更多token) | 较小(只包含最高概率token) |
| 多样性 | 高 | 低 |
| 确定性 | 低 | 高 |
| 风险 | 可能选到不合理词 | 可能过于保守 |
常用设置:
- 创意写作:p=0.9-0.95,temperature=0.8-1.0
- 事实性任务:p=0.5-0.7,temperature=0.6-0.8
三、策略对比总结
| 策略 | 选择方式 | 多样性 | 质量 | 计算成本 | 适用场景 |
|---|---|---|---|---|---|
| 贪心搜索 | 选最大概率 | 极低 | 中等(可能重复) | 最低 | 确定性任务 |
| Beam Search | 保留K条最优路径 | 低 | 高(但可能平庸) | 高 | 翻译、摘要 |
| Top-k | 从Top-k中随机采样 | 中等 | 中等 | 中等 | 故事生成 |
| Nucleus | 动态选择累积概率<p的token | 高 | 高 | 较高 | 创意写作、对话 |
四、Temperature的作用
Temperature影响所有采样策略的概率分布平滑度:
python
logits = logits / temperature
probabilities = softmax(logits)
| Temperature | 效果 | 适用场景 |
|---|---|---|
| T < 1 | 概率分布更集中,高概率token概率更高 | 需要确定性的任务 |
| T = 1 | 保持原始分布 | 标准采样 |
| T > 1 | 概率分布更平坦,低概率token概率提升 | 需要创造力的任务 |
示例(Top-k=3):
| Token | 原始概率 | T=0.5后 | T=1.5后 |
|---|---|---|---|
| “the” | 0.50 | 0.75 | 0.40 |
| “a” | 0.30 | 0.22 | 0.33 |
| “an” | 0.20 | 0.03 | 0.27 |
五、实用经验
- 起点用贪心:先跑贪心,看模型的基本输出质量
- 逐步增加随机性:从Top-k开始,再尝试Nucleus
- 温度配合使用:temperature+Top-p是工业界最常用组合
- 根据任务调整:
- 新闻/事实 → 低温度 + 小p值
- 故事/创意 → 高温度 + 大p值
- 多样性惩罚:可额外使用repetition_penalty减少重复