Skip to content

评估与文本生成

训练好的模型要能「开口」才算成功。这一页加载 checkpoint,动手生成文本,并实测 temperature、top_k 对输出的影响——把 GPT架构 里讲的生成策略真正玩明白。

1. 加载模型并生成(sample.py)

python
# sample.py
import pickle, torch
from model import GPT, Config

device = "cuda" if torch.cuda.is_available() else ("mps" if torch.backends.mps.is_available() else "cpu")
meta = pickle.load(open("data/meta.pkl", "rb"))
itos = meta["itos"]
encode = lambda s: [stoi[c] for c in s]           # 需再从 meta 建 stoi, 见下

# 读取训练时保存的 checkpoint
# 安全提示: torch.load 会反序列化对象, 只加载你自己信任的 checkpoint;
# 这里 ckpt 仅含张量与纯 dict, 用 weights_only=True 更稳妥。
ckpt = torch.load("out/ckpt_final.pt", map_location=device, weights_only=True)
cfg = Config(**ckpt["cfg"])
stoi = {v: k for k, v in itos.items()}            # 由 itos 反推 stoi

model = GPT(cfg).to(device)
model.load_state_dict(ckpt["model"])
model.eval()

# 给一个开头, 续写
context = "KING:"                                  # 换成你语料里出现过的开头效果更好
idx = torch.tensor([encode(context)], dtype=torch.long, device=device)
out = model.generate(idx, max_new_tokens=300, temperature=0.8, top_k=40)
print("".join(itos[i] for i in out[0].tolist()))

若嫌 stoi 麻烦,可把 encode 也一并 pickle 进 meta.pklprepare.pypickle.dump({..., "stoi": stoi}, f))。

2. temperature:控制「稳」还是「野」

回忆 softmax:probs = softmax(logits / T)

T 很小(0.3) → 分布被"削尖" → 总挑大概率词 → 保守、重复、语法稳
T = 1.0     → 原样采样
T 很大(2.0) → 分布被"压平" → 小概率词也有机会 → 有创意、但易胡言乱语

对比实验(同一开头):

temp=0.4 : "KING: My lord, the army is prepared for the battle..."  (规整但略平)
temp=1.5 : "KING: My dragon whispers purple thunder across the..."  (天马行空)

实践常用 0.6 ~ 0.9

3. top_k / top_p:给采样加个「安全网」

top_k=40 : 每步只在概率最高的 40 个候选里采样, 过滤掉长尾乱码
top_p=0.9: 按概率从高到低累加, 取累计刚达 0.9 的最小集合再采样(动态候选数)
python
# 在 generate 里加 top-p(核采样)示意
probs = F.softmax(logits, dim=-1)
if top_p is not None:
    sorted_p, sorted_i = torch.sort(probs, descending=True, dim=-1)
    cum = torch.cumsum(sorted_p, dim=-1)
    mask = cum > top_p
    mask[:, 1:] = mask[:, :-1].clone(); mask[:, 0] = False  # 保留刚越过阈值的那个
    probs = probs.scatter(-1, sorted_i.masked_fill(mask, 0), 0.0)  # 集合外清零
  • top-k:候选数固定,简单。
  • top-p:候选数随分布自适应,分布尖时少取、分布平时多取,更聪明,是大模型默认。

4. 用 loss 科学地评估质量

除了「看着像不像」,还要量化:

困惑度(Perplexity) = exp(验证集平均交叉熵损失)
   PPL=5  → 模型平均在 5 个候选里犹豫(很强)
   PPL=50 → 要在 50 个里蒙(一般)
   PPL↑   → 越接近瞎猜
python
import math
val_losses = [float(model(x, y)[1]) for x, y in val_batches]
mean_loss = sum(val_losses) / len(val_losses)
print("验证 loss:", mean_loss, "| 困惑度 PPL:", math.exp(mean_loss))

5. 训练过程「进化」实录(预期)

阶段生成样子val loss
初期aaaa....,,,, 单字符循环~2.6
中期出现英语单词/中文常用字组合~1.8
后期结构合理的句子/对白/诗句~1.4

看到输出逐步从乱码走向「像那么回事」,就是自注意力 + 交叉熵在你眼前生效的证据。

6. 效果不好怎么办

  • 复读机/总是重复:调低 temperature、加 repetition_penalty、或多训练。
  • 输出驴唇不对马嘴:语料太少/太杂,或训练不足;换更连贯的语料。
  • 一开始就不像:确认开头是训练集中见过的字符(字符级模型遇到词表外字符会崩)。
  • 中文输出怪:字符级中文需要更充足语料,可考虑增大 n_layer/n_embd 与训练步数。

小结

  • 加载 checkpoint 后用 generate 自回归续写。
  • temperature 调「稳/野」,top_k/top_p 加安全网,top-p 更自适应。
  • 困惑度 PPL = exp(loss) 量化语言模型质量。
  • 生成质量的进化,是检验训练是否成功的最好证据。

下一步

自己从零训是小样本练兵;真实项目更多是在预训练模型上微调。最后一实战 → 微调预训练模型