Skip to content

第 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)采样

温度是一个用来调节生成多样性的参数 T,它在 softmax 之前缩放 logits:

P(xt=k)=exp(zk/T)jexp(zj/T)

三种情况的直观对比:

原始 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]  → 趋于均匀,大胆
  • 低温T<1):分布变尖,倾向高概率字,生成更"正统"、更符合诗词规范
  • 高温T>1):分布变平,低概率字也有机会被选中,生成更"有创意"但可能不通顺
python
proba = torch.softmax(last_logit / temperature, dim=-1)
next_id = torch.multinomial(proba, num_samples=1)

小结

  • 推理时逐字自回归生成,每次只输入一个字
  • hidden 必须在整首诗的生成中持续传递,以保持上下文记忆
  • 随机采样比贪心解码生成结果更多样
  • 温度参数控制生成的"保守性"与"创意性":T<1 保守,T>1 大胆

至此,理论篇全部完成。实践篇将把上述所有概念落地到代码中,逐模块拆解实现细节。

基于 MIT 协议发布