主题
第 5 章 从计数得到概率、采样和损失
一张计数表可以用来预测,也可以用来生成。预测时,给定当前字符,查看下一字符的整行分布;生成时,从这行分布抽一个字符,再把抽到的结果作为新的当前字符。两种用法共享同一张概率表,区别只是怎样使用输出。
5.1 每一行单独归一化
令
aba、ada,当前字符是 a 时,后面是结束符的概率为 b 或 d 的概率各为
先取一行做除法最容易看懂:
python
p = N[0].to(torch.float64)
p = p / p.sum()再扩展到整个矩阵:
python
P = N.to(torch.float64)
P = P / P.sum(dim=1, keepdim=True)dim=1 表示沿第二维,也就是每行内部求和。keepdim=True 保留长度为 1 的第二维,使分母形状为
5.2 广播不会替你判断数学意图
如果去掉 keepdim=True,行和的形状变成
| 操作数 | 形状 | 实际作用 |
|---|---|---|
| 计数矩阵 | 每行一个当前字符 | |
sum(1, keepdim=True) | 每行除以自己的总数 | |
sum(1) | 右对齐到列,产生错误归一化 |
因此,形状检查还要配合性质检查:
python
assert torch.allclose(P.sum(dim=1), torch.ones(V, dtype=P.dtype))
assert torch.isfinite(P).all()
assert (P >= 0).all()本书 language_lab.py 故意计算了一份缺少 keepdim 的错误结果,并断言它的行和不全为 1。方阵上静默通过的广播错误,在神经网络里远比直接抛异常更难发现。
5.3 从概率抽样,为什么要带随机数生成器
torch.multinomial(p,1) 按非负权重抽一个类别 ID。对单个样本来说,出现低概率类别并不说明函数出错;要看重复抽样的频率是否接近概率。权重不必预先归一化,但必须有限、非负且总和为正。本书为解释方便传入已归一化概率。PyTorch 2.6 multinomial 文档源码
python
g = torch.Generator().manual_seed(42)
current = 0
result = []
for _ in range(100):
current = torch.multinomial(P[current], 1, generator=g).item()
if current == 0:
break
result.append(itos[current])
else:
raise RuntimeError('Generation reached the safety length limit')从起始符 0 开始,生成 0 时停止。这里给生成加了长度保护;它防止异常模型让程序运行过久,不等于模型本身学习了长度限制。若触发上限,应明确标记截断,不能把它当成自然结束。
创建一次 Generator,多次抽样会依次消耗它的状态。若在每个名字开头重新创建同种子的生成器,可能反复得到同一个名字。固定种子是为了复现实验,不是提高文本质量。跨 PyTorch 版本、设备或算法,不能仅凭种子承诺逐字符相同。
如果把每行都换成均匀分布,程序仍然会正常生成字符,但失去数据学到的转移结构。这个基线能帮助区分 “采样循环正确” 和 “模型确实使用了数据”。
5.4 给整份数据打分
生成几个看起来顺眼的名字不足以评价模型。更稳定的办法是检查它给真实后继字符分配了多少概率。
以 aba 为例,它连同结束符的概率,在 bigram 假设下是
真实序列的链式分解本来可以条件于全部过去。bigram 用最后一个字符代替全部历史,这是模型假设,不是概率恒等式要求历史只能有一个字符。
许多小概率相乘容易下溢,且不便优化。对数把乘法变加法,并保留大小顺序。把数据中的转移编号为
“似然” 指把数据固定,比较不同模型给这些数据分配的概率;“负” 号让目标变成越小越好;除以
若模型给正确类别概率 1,该项损失是 0;概率
5.5 没见过不等于不可能:加法平滑
原始计数为零的转移对应概率零,一旦评估集出现它,
python
P = N.to(torch.float64) + alpha
P /= P.sum(dim=1, keepdim=True)平滑不是免费提升。它给未见事件留出了概率,必然从已见事件那里拿走一部分概率。本书六字符、40 条自编样本上,未平滑的训练集最大似然损失为 0.915590,加一后是 0.998357。训练损失变高符合预期;是否改善新数据表现,要看验证集,而不能从训练损失独自得出结论。
在一个行总数
练习与答案
补充题:在有 6 个类别、行计数总数 4 的情况下,加一平滑给每个未见事件多少概率?答案是
官方 bigram E02、E03 要先切分训练、验证、测试集,再选择平滑强度。第 9 章会给出完整可运行网格实验。先写出一条原则:验证集用来选
来源与运行边界
本章覆盖 bigram notebook 的单行分布、随机抽样、广播探索、全表归一化、生成、似然与平滑。notebook 保留了交互式运行痕迹,部分形状查询位于完整赋值前;本书代码按可顺序运行的依赖整理。真实检查在 language_lab.py,完整日志在 qa/part1/language.log。