Appearance
第4章 · 字符语言模型与概率目标
本地 fixture 通过不等于学习者独立推导或真实模型质量。
先修:第3章的梯度、标量 loss 和基础数组运算。
本章目标
- 从计数 Bigram 过渡到神经 Bigram 和上下文 MLP。
- 从最大似然推导 NLL/交叉熵,区分 logits、概率、目标 token 和采样。
- 在 train/validation 分离的数据上证明参数更新,并解释过拟合、temperature、top-k 的影响。
公式与 shape
自回归语言模型分解序列概率:
对 logits z 的 temperature 采样为
| 对象 | shape | 说明 |
|---|---|---|
| token ids | (B, T) | 整数,范围 [0, V) |
| embedding table | (V, D) | 只按 token id 查行 |
| hidden/context | (B, T, D) 或 (B, D) | context 长度必须固定或显式 padding |
| logits | (B, T, V) | 最后一维是词表 |
| targets | (B, T) | 与 logits 的时间位置对齐 |
| loss | () | reduction 规则要写入 evidence |
数学桥接:第2章 → 第4章
第2章 §6 的 neg_log_likelihood(p) = -\log_2(p) 就是本章的 per-token loss:一个 token 的概率越小,惩罚越大。对整条序列取平均就得到 NLL:
第2章的 cross_entropy(p_dist, q_dist) 正是本章 CE loss 的二维版本——p_dist 是 one-hot 目标,q_dist 是模型 softmax 输出。两者的关系是
第2章 §5 的 categorical_sample(probs, rng) 实现了 temperature 采样的核心:先归一化,再按累积分布抽样。mle_bernoulli(trials) 则对应 Bigram 中
前端类比:cross_entropy([1,0], [0.8,0.2]) ≈ 0.322 就像你写了一个组件期望输出 [1,0](100% 选 A),但模型给了 [0.8, 0.2]——CE 衡量的是"期望 vs 实际"的信息差距。
交互观察
交互:Softmax + Temperature
把 logits 看成「未归一化分数」。Temperature 越高分布越平,越低越尖。
固定一组 logits,分别观察低温、常温和高温;把浏览器里的概率和 Python 训练/采样 evidence 分开记录。
从零实践
在小语料上统计 Bigram 计数和条件概率,加入明确的未见 pair 策略。
实现神经 Bigram/MLP:embedding → hidden → logits → CE;先过拟合一个极小 batch,再观察 held-out loss。
比较
T=0.2/1.0/2.0和固定top_k的样本变化;seed 只能约束本地采样,不能把随机输出当事实。当前入口与基础测试:
bashPYTHONPATH=python python -m pytest python/tests/test_bigram_mlp.py -q
当前 bigram.py、mlp_lm.py 已提供可复现的 temperature 分布、参数更新、时间切分和 held-out loss fixture;evidence/04-runtime-v1.json 记录本地实测值。完整 gate 仍要求学习者独立重建、故障注入和口述,不能把 fixture 直接当作掌握证明。
故障注入与预期信号
| 注入 | 预期失败信号 | 修复后证据 |
|---|---|---|
| 把 softmax 作用在 batch 维 | 每个 token 的概率和不为 1 | 最后一维逐行和为 1 |
| target 未右移 | 模型学成复制当前 token | shifted target 检查和受控 loss 下降 |
| temperature 乘而非除 logits | 低温更随机或概率尖锐度反转 | 固定 logits 的单元测试通过 |
论文与延伸
- A Neural Probabilistic Language Model(Bengio 等,2003)
- 选读:Karpathy 的 makemore;只抽取概念和实验问题,不把成品复制为答案。
前端/Agent 迁移
把 logits 看作候选动作的未归一化评分,把采样看作策略层;它输出的是条件分布,不是事实数据库。Agent 中要把“模型建议”与“系统允许的动作”分离,后者仍需 schema、状态和结果校验。
口述与自测(不看资料,5–10 分钟)
- 从自回归联合概率分解推到平均 negative log-likelihood,再说明为什么训练中通常把它写成 cross entropy。
- 比较 Bigram 与上下文 MLP:各自能表达什么、会在哪种 held-out 现象上失败;temperature 改变的又是什么而不是什么。
- 用第2章的
cross_entropy([1,0], [0.8,0.2]) ≈ 0.322解释为什么 one-hot 标签的 CE 就等于-log(p_correct);说明 temperature 高时这条公式的变化趋势。 - 口算 Bigram 的 MLE:如果训练集中 "th" 出现 50 次、"t" 出现 200 次,P(h|t) 的 MLE 估计是多少?这和第2章
mle_bernoulli的思想有什么同异?
实验与参考
动手实验
证明字符级语言模型的两条最短路径都是通的:查表式的 bigram 计数矩阵能直接采样出像样的“人名”,参数化的 MLP 语言模型会在受控训练数据上让交叉熵 loss 真的下降。
环境准备
bash
cd <仓库根>
export PYTHONPATH="$PWD/python"命令与预期输出
bash
# 一键总览:打印 bigram 生成样本和 MLP loss 首末(test_bigram_mlp.py 已在「从零实践」跑过)
python -m labs.run_alltext
bigram anna
mlp losses 3.2xx -> 2.1xx
# 判定条件:
# - final loss 明显小于 initial loss(数值示例:3.2xx -> 2.1xx)
# - bigram 采样输出在训练词表中存在或形态相近
# - 训练 seed 固定后,两次运行的首末 loss 一致概念图
概念路线 · CONCEPT ROADMAP
第4章核心
forward → backward → 采样- 01
字符序列
(B, T)原始语料的 token ids,整数范围 [0, V)
- 02
切分为输入/输出对
target 右移一位,模型学的是"预测下一个字符"
- 03
Embedding 查找表
(V, D)只按 token id 查行
- 04
隐藏层激活
MLP tanh把 embedding 组合成上下文向量
- 05
输出层
logits (B, T, V)最后一维是词表
←概率论· 词分布第8章 TinyGPT· logits → vocab→ - 06
log_softmax
数值稳定的 log 概率
- 07
交叉熵 loss
NLLper-token 平均,惩罚低概率目标
- 08
反向传播,更新权重
沿计算图回传梯度
- 09
采样生成
temperature先除 T 再 softmax,控制随机性
路线图:第4章 Bigram MLP 语言模型主链 — 自上而下即学习顺序。第2章的交叉熵和采样函数在本章首次组合为"预测下一个字符"的完整训练闭环:字符 → embedding → MLP隐藏层 → logits → CE loss → backward → 采样生成。绿色 ← 徽章标记本章复用的前置概念,紫色 → 徽章指向后续去处:第5章用 BPE 把字符升级为子词,第6章引入 SGD/AdamW 优化器,第8章把 MLP 隐藏层替换为 Transformer FFN + Attention。
故障注入清单
| 故障 | 表现 | 修复 |
|---|---|---|
| 训练集和验证集用同一份数据 | val loss 看起来在降,其实只是复读训练集 | 严格切分 train/val,val 上 loss 第一次迭代后应上升再缓降 |
| 采样温度设为 0 | 永远输出同一个字符,看起来"训练失败" | 温度大于 0,且与训练温度一致时质量更稳 |
| 采样温度非常大 | 输出完全乱码,几乎不可读 | 把温度夹在 [0.5, 1.5] 区间,超出则告警 |
| 对 logits 直接做 softmax 而没有 log-softmax | loss 在数值上变成负数或 NaN | 用 log_softmax 或在 softmax 后显式 log,并加 nan_to_num |
训练时没有固定 seed | 同一份脚本两次跑出的 loss 不同 | 入口处 random.seed 与 numpy.random.seed 同时固定 |
资源 / 成本 / 隐私
本地 CPU/NumPy 即可,固定小语料不需要云资源;预计 gross cost 为 0。语料使用合成或已确认公开许可内容,输出不得包含个人经历原文。
Evidence
仓库当前机器证据(只读快照)
evidence/module-manifest-v1.json 中 04.evidence 指向当前文件:evidence/04-runtime-v1.json。这是当前 checkout 的脱敏机器运行记录,只覆盖该 JSON 记录的命令、指标、产物和已知失败;它不是学习者提交,也不能推出学习者已完成本章。
学习者提交模板(待填写,不是当前机器证据)
复制下面模板并填写自己的真实运行结果。所有 <...> 都是未填写状态;actual 和 artifacts 尤其不能被当作已运行或已通过。artifacts 必须替换为本次提交中真实存在的仓库相对路径。
yaml
schema: learn-llm.evidence.v1
module: 04-language-model
commit: <learner-commit-sha>
verified_at: <iso-date>
environment: <sanitized-python-device>
seed: 2
commands:
- PYTHONPATH=python python -m pytest python/tests/test_bigram_mlp.py -q
- PYTHONPATH=python python <learner-controlled-language-model-run>
metrics:
- name: parameter_update_norm
expected: '>0'
actual: <recorded-value>
- name: held_out_loss_delta
expected: <versioned-threshold>
actual: <recorded-value>
artifacts:
- <learner-repo-relative-artifact-path>
cost:
gross_usd: 0
credit_usd: 0
licenses:
- source: <source>
version: <version>
license: <license>
attribution: <attribution>
redistribution: <redistribution>
known_failures:
- <sanitized-failure-or-none>若没有 held-out 指标、seed 和参数更新证据,本章只能标记为 gate。