Skip to content

第4章 · 字符语言模型与概率目标

本地 fixture 通过不等于学习者独立推导或真实模型质量。

先修:第3章的梯度、标量 loss 和基础数组运算。

本章目标

  • 从计数 Bigram 过渡到神经 Bigram 和上下文 MLP。
  • 从最大似然推导 NLL/交叉熵,区分 logits、概率、目标 token 和采样。
  • 在 train/validation 分离的数据上证明参数更新,并解释过拟合、temperature、top-k 的影响。

公式与 shape

自回归语言模型分解序列概率:

P(x1:T)=t=1TP(xtx<t),L=1Ni=1Nlogpi,yi.

对 logits z 的 temperature 采样为

pi=softmax(z/T)i.
对象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:

LNLL=1Ni=1Nlogp(xi)

第2章的 cross_entropy(p_dist, q_dist) 正是本章 CE loss 的二维版本——p_dist 是 one-hot 目标,q_dist 是模型 softmax 输出。两者的关系是 H(P,Q)=H(P)+DKL(PQ),当 P 是确定分布(one-hot)时,H(P)=0,CE 直接等于 KL 散度。

第2章 §5 的 categorical_sample(probs, rng) 实现了 temperature 采样的核心:先归一化,再按累积分布抽样。mle_bernoulli(trials) 则对应 Bigram 中 P(xi|xi1)=count(xi1,xi)count(xi1) 的 MLE 估计。

前端类比cross_entropy([1,0], [0.8,0.2]) ≈ 0.322 就像你写了一个组件期望输出 [1,0](100% 选 A),但模型给了 [0.8, 0.2]——CE 衡量的是"期望 vs 实际"的信息差距。

交互观察

交互:Softmax + Temperature

把 logits 看成「未归一化分数」。Temperature 越高分布越平,越低越尖。

tok0
8.5%
tok1
23.2%
tok2
5.2%
tok3
63.1%

固定一组 logits,分别观察低温、常温和高温;把浏览器里的概率和 Python 训练/采样 evidence 分开记录。

从零实践

  1. 在小语料上统计 Bigram 计数和条件概率,加入明确的未见 pair 策略。

  2. 实现神经 Bigram/MLP:embedding → hidden → logits → CE;先过拟合一个极小 batch,再观察 held-out loss。

  3. 比较 T=0.2/1.0/2.0 和固定 top_k 的样本变化;seed 只能约束本地采样,不能把随机输出当事实。

  4. 当前入口与基础测试:

    bash
    PYTHONPATH=python python -m pytest python/tests/test_bigram_mlp.py -q

当前 bigram.pymlp_lm.py 已提供可复现的 temperature 分布、参数更新、时间切分和 held-out loss fixture;evidence/04-runtime-v1.json 记录本地实测值。完整 gate 仍要求学习者独立重建、故障注入和口述,不能把 fixture 直接当作掌握证明。

故障注入与预期信号

注入预期失败信号修复后证据
把 softmax 作用在 batch 维每个 token 的概率和不为 1最后一维逐行和为 1
target 未右移模型学成复制当前 tokenshifted target 检查和受控 loss 下降
temperature 乘而非除 logits低温更随机或概率尖锐度反转固定 logits 的单元测试通过

论文与延伸

前端/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_all
text
bigram anna
mlp losses 3.2xx -> 2.1xx

# 判定条件:
# - final loss 明显小于 initial loss(数值示例:3.2xx -> 2.1xx)
# - bigram 采样输出在训练词表中存在或形态相近
# - 训练 seed 固定后,两次运行的首末 loss 一致

概念图

概念路线 · CONCEPT ROADMAP

第4章核心

forward → backward → 采样
  1. 01

    字符序列(B, T)

    原始语料的 token ids,整数范围 [0, V)

  2. 02

    切分为输入/输出对

    target 右移一位,模型学的是"预测下一个字符"

  3. 03

    Embedding 查找表(V, D)

    只按 token id 查行

  4. 04

    隐藏层激活MLP tanh

    把 embedding 组合成上下文向量

  5. 05

    输出层logits (B, T, V)

    最后一维是词表

  6. 06

    log_softmax

    数值稳定的 log 概率

  7. 07

    交叉熵 lossNLL

    per-token 平均,惩罚低概率目标

  8. 08

    反向传播,更新权重

    沿计算图回传梯度

  9. 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-softmaxloss 在数值上变成负数或 NaNlog_softmax 或在 softmax 后显式 log,并加 nan_to_num
训练时没有固定 seed同一份脚本两次跑出的 loss 不同入口处 random.seednumpy.random.seed 同时固定

资源 / 成本 / 隐私

本地 CPU/NumPy 即可,固定小语料不需要云资源;预计 gross cost 为 0。语料使用合成或已确认公开许可内容,输出不得包含个人经历原文。

Evidence

仓库当前机器证据(只读快照)

evidence/module-manifest-v1.json04.evidence 指向当前文件:evidence/04-runtime-v1.json。这是当前 checkout 的脱敏机器运行记录,只覆盖该 JSON 记录的命令、指标、产物和已知失败;它不是学习者提交,也不能推出学习者已完成本章。

学习者提交模板(待填写,不是当前机器证据)

复制下面模板并填写自己的真实运行结果。所有 <...> 都是未填写状态;actualartifacts 尤其不能被当作已运行或已通过。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

私有学习站 · 原理从零构建 · 勿提交个人隐私或密钥