第 5 章 文本生成与推理策略
本章目标:理解训练好的模型如何生成新文本,重点掌握自回归生成、隐藏状态传递和温度采样。
5.1 训练 vs. 推理的差异
训练和推理在数据输入方式上有本质区别:
| 阶段 | 输入方式 | 目的 |
|---|---|---|
| 训练 | 一次输入完整序列 (N, L),整体计算 loss | 让模型学会预测下一个字 |
| 推理 | 每次只输入一个字 (1, 1),逐字生成 | 利用学到的概率分布生成新诗 |
训练时我们"作弊"地把真实序列一次喂给模型(Teacher Forcing),而推理时只有一个起始字,后续每个字都由模型自己生成。
5.2 自回归生成
自回归(Auto-regressive) 是文本生成的核心范式:
第 1 步:输入"春" → 模型预测"眠"
第 2 步:输入"眠" → 模型预测"不"
第 3 步:输入"不" → 模型预测"觉"
...每一步的输出立刻作为下一步的输入,循环往复,直到生成完整的诗。这与人类逐字写诗的方式高度类似。
5.3 为什么必须传递隐藏状态
自回归生成时,RNN 的隐藏状态 hidden 扮演着至关重要的角色——它是模型对"已生成内容"的记忆。
错误做法(原始代码的 bug):
python
while current_len > 0:
output, _ = model(input) # ❌ 丢弃 hidden,每步都从空白记忆开始
...每次生成新字时都把 hidden 丢掉,模型对之前写了什么毫无记忆,生成的诗前后完全不连贯。
正确做法:
python
hidden = None # 初始为空
while current_len > 0:
logits, hidden = model(input, hidden) # ✅ 传入并更新 hidden
...hidden 在整首诗的生成过程中持续传递,让模型在生成每个字时都能"记住"前面已经写了什么。
5.4 贪心解码 vs. 随机采样
得到每个字的概率分布后,有多种方式选取下一个字:
贪心解码(Greedy Decoding)
每次取概率最高的字:
python
next_id = torch.argmax(proba)结果确定,但非常单调——同一个起始字每次生成的诗完全相同,且容易陷入重复。
随机采样(Multinomial Sampling)
按概率分布随机抽取:
python
next_id = torch.multinomial(proba, num_samples=1)同一个起始字每次能生成不同的诗,更有"创意感",这是本项目采用的方式。
5.5 温度(Temperature)采样
温度是一个用来调节生成多样性的参数
三种情况的直观对比:
原始 logits: [3.0, 1.5, 0.8, 0.2]
T = 0.5(冷):概率 ≈ [0.93, 0.06, 0.01, 0.00] → 集中在高概率字,保守
T = 1.0(正常):概率 ≈ [0.63, 0.28, 0.08, 0.01] → 原始分布
T = 2.0(热):概率 ≈ [0.41, 0.28, 0.20, 0.11] → 趋于均匀,大胆- 低温(
):分布变尖,倾向高概率字,生成更"正统"、更符合诗词规范 - 高温(
):分布变平,低概率字也有机会被选中,生成更"有创意"但可能不通顺
python
proba = torch.softmax(last_logit / temperature, dim=-1)
next_id = torch.multinomial(proba, num_samples=1)小结
- 推理时逐字自回归生成,每次只输入一个字
hidden必须在整首诗的生成中持续传递,以保持上下文记忆- 随机采样比贪心解码生成结果更多样
- 温度参数控制生成的"保守性"与"创意性":
保守, 大胆
至此,理论篇全部完成。实践篇将把上述所有概念落地到代码中,逐模块拆解实现细节。