主题
第 6 章 用神经网络重写 bigram
计数法已经能得到 bigram 模型,为什么还要写神经网络?这一章不是为了用更复杂的代码胜过相对频率,而是用同一个简单任务接通张量、自动求导、损失与训练。确认两种写法到底哪里相同、哪里不同,后面增加上下文时就不容易把实现变化误当成模型能力变化。
6.1 一批训练样本是什么形状
把每个字符对拆成输入 ID 与标签 ID。若一批有 xs 和 ys 的形状都为 torch.long。例如名字 aba 的四对样本可写为输入 [0,1,2,1],标签 [1,2,1,0],这里词表中的 a=1,b=2。
不直接把 ID 当连续数值,而把每个 ID 改成长度 xenc 的形状为
python
xenc = F.one_hot(xs, num_classes=V).to(W.dtype)
logits = xenc @ W这里的 @ 是矩阵乘法,结果形状为
因为求和只有一个位置不为零,所以乘法实际就是选行。可以直接写 logits=W[xs]。省掉的是临时 one-hot 矩阵和相应乘法,模型表达的分布完全没变。这是官方 bigram E04 的答案,本书测试同时核对了前向值和重复索引的梯度累加。
原 notebook 先建立
6.2 从任意打分到概率
网络输出
这就是 softmax。指数保证分子为正,归一化保证每行和为 1。所有类别相互竞争:提高一个打分,会让它的概率增加,同时压低其他类别的概率。
课程把指数结果叫作 “counts”,便于与计数表对照。这些数一般不是整数,也不是模型真正见过的频次,而是可学习的正权重。称 logit 为 “log-count” 是这个类比下的解释,不要把它理解成唯一可辨识的真实计数。
给一行所有 logits 加同一常数,分布不变,因此存在多组
再做 softmax,就精确得到加
6.3 用正确标签选出概率,再求平均
若 probs 形状为 ys 形状为
python
probs[torch.arange(B), ys]选的是每行各自真实标签的概率,得到
但实际运行推荐直接用
python
loss = F.cross_entropy(logits, ys)这里传 logits,不要先传 softmax 后的概率。对整数类别目标,默认平均交叉熵正是上述 NLL。在数学上可以写成
稳定计算令
减最大值后,最大的指数只为 1,避免直接 exp(1000) 溢出。先 softmax 再 log 也可能因某类概率下溢为零而出问题,合并实现能更稳妥地算 log-probability。PyTorch 2.6 交叉熵接口源码
官方 E05 要替换为交叉熵并解释原因。本书测试用了包含 1000、1001 的 logits,确认直接指数出现无穷,而稳定交叉熵为有限值 0.23129559991076382,并与 logsumexp 表达式一致。这是稳定性的具体反例,不只是速度偏好。
6.4 为什么梯度是 “预测概率减目标”
对一条样本,
输入为字符 language_lab.py 检查了这个性质,也用 torch.autograd.gradcheck 核对了交叉熵的梯度。
6.5 PyTorch 训练循环中的几个具体动作
python
W = torch.randn((V, V), generator=g, requires_grad=True)
for step in range(400):
loss = F.cross_entropy(W[xs], ys)
W.grad = None
loss.backward()
with torch.no_grad():
W -= learning_rate * W.gradrequires_grad=True 让前向运算记录求导关系。W.grad=None 表示下一次反向重新建立梯度;不清空则会与先前结果相加。参数更新放进 no_grad,使 “如何改参数” 本身不再进入下一张求导图。课程常用 .data 更新,为了教学短小直观;本书用 no_grad 使边界更明确。
六字符小实验以学习率 1.0 跑 400 次全批量更新,训练 NLL 从 2.139029 降到 0.928397。同一份数据的未经平滑计数最优值为 0.915590,神经网络训练还没有精确达到它。这个差距体现有限优化步数,并不表示 bigram 神经网络额外知道了更多历史。
6.6 平滑与权重惩罚相似,但不相等
课程给损失增加 0.01*(W**2).mean()。若总共有
它把 logits 拉向零,而一行全零的 softmax 是均匀分布。这与加法平滑都有 “不让训练频次独占判断” 的效果。但是二者的目标函数、最优解和参数尺度不同,没有普遍的 “某个
| 比较维度 | 计数 bigram | 神经网络 bigram |
|---|---|---|
| 使用的历史 | 一个字符 | 一个字符 |
| 表示 | 每行频数或概率 | 每行 logits |
| 拟合方式 | 一次统计与归一化 | 多次梯度更新 |
| 平滑/约束 | 加伪计数等 | 可加参数惩罚等 |
| 向复杂模型扩展 | 上下文表快速增大 | 可加入嵌入与共享隐藏层 |
练习与解答
补充题:证明 W[xs] 与 one-hot 乘法不仅前向相同,反向也相同。对
官方 E06 要自己设计一道有趣的实验。本书选择温度采样:把 logits 除以正数 examples/part1/exercises.py 用 [2,1,0] 实测 0.4411,0.8324,1.0202。这个小例子展示分布变平;它没有说明提高温度一定改善名字质量。
来源与边界
本章覆盖 bigram notebook 的训练对、one-hot、矩阵乘法、softmax、逐条与向量化损失、反向更新、全数据训练、正则化和神经网络采样。官方 E04、E05 在此解答,E06 是按官方开放题要求选取的原创实验。全部测试与损失数值来自 qa/part1/language.log、exercises.log,并非课程全数据训练成绩。