Skip to content

第3章 · 从导数到自动微分

本地测试证据与学习者 clean-room/故障口试保持分离。 先修:会读 Python 函数、类和 NumPy 数组。

本章目标

  • 能从空白接口写出标量 Value、基础算子、激活函数和拓扑反向传播。
  • 能解释局部导数、链式法则、叶子节点、梯度累加和反向顺序。
  • 用有限差分和一个小型 MLP 证明梯度正确、参数确实更新、loss 在受控数据上下降。

公式与 shape

对计算图中的 u = f(x)L = g(u),反向传播使用

Lx=Luux.

若一个节点同时被两条边使用,梯度是所有路径贡献之和,而不是最后一条路径的覆盖:

Lx=ppaths(xL)Lp.
对象shape约束
标量值 Value.data()一个节点携带一个数值和一个梯度
MLP 输入(D,)(B, D)batch 维不能混入参数维
MLP 输出(C,)(B, C)loss 必须有明确 reduction
有限差分与输入相同f(x+h)-f(x-h) 使用同一参数快照

数学桥接:第2章 → 第3章

第2章 §3 的 chain_rule_2layer(f2, f1, x, y) 实现了最简链式法则:z=f2(f1(x,y))z/x=f2(f1)f1/x。本章的反向传播就是这个规则的图论推广——每个节点对所有下游路径的梯度贡献求和,而不是覆盖:

Lx=ppaths(xL)Lp

第2章的 gradient_vector(f, x, y) 返回 (f/x,f/y),这正是本章 Value.backward() 对所有叶节点最终要算的东西。difference_quotient(f, x, h) 是本章 gradcheck 的数值基础——当解析梯度实现有 bug 时,有限差分是唯一的 arbiter。

前端类比:Vue computed 的依赖追踪 = 前向传播(数据流向下),重新计算 = 反向传播(变化向上传播)。共享节点梯度累加 = computed A 依赖 B 和 C,B 变了算一次,C 变了再算一次,两次变化叠加才是 A 的真正变化。

交互观察

交互:最小自动微分

y = tanh(x * w + b)

y = 0.000000 ∂y/∂x = 2.000000 ∂y/∂w = 0.500000 ∂y/∂b = 1.000000

拖动滑块时,浏览器里即时重算前向与反向。对应 Python: python/llm_core/autograd.py

拖动 xwb 后,用链式法则解释输出和三个局部梯度的变化。浏览器演示只用于建立直觉;真正证据仍来自 Python 数值对拍和故障题。

从零实践

  1. 只依据接口和公式实现 Value.__add__、乘法、幂、tanh/relu、父节点集合和局部梯度闭包。

  2. 反向时先收集从输出可达的节点,再按反向拓扑序执行;重复调用前明确梯度是否清零。

  3. python/llm_core/autograd.py 的实现旁写一个独立的 clean-room 小模型,不直接复制参考实现。

  4. 运行现有基础回归:

    bash
    PYTHONPATH=python python -m pytest python/tests/test_autograd.py -q
  5. python/llm_core/gradcheck.py(若接口已在当前 checkout 中存在)做多点有限差分;否则该步骤保持 gate,不以手算样例代替。

当前仓库已有 ValueNeuronLayerMLP 和基础测试,但现有绿测不自动证明 clean-room、共享节点专项题和故障注入已完成。通过条件是:梯度误差低于冻结阈值、共享节点累加正确、MLP 参数改变且受控 loss 下降。

Clean-room submission contract(学习者本地提交)

clean_room/autograd.py 是 blank interface 起点,合同冻结了 Value.__init__(self, data: float)Value.backward(self) 的参数签名。学习者应在私有提交中填写 submission.jsonmodules[]test_evidence[]failure_records[]oral_records[]human_review;实现或测试不得导入 clean_roomllm_corepython.llm_corellm_trainpython.llm_train。故障记录至少要有注入、预期/实际信号、修复前后观察和证据路径;口述记录至少要有 prompt、主题、回答摘要、局限、审核备注和状态。

bash
pnpm clean-room:contract
node scripts/validate_clean_room.mjs --submission /path/to/private-submission

第二条命令只读提交结构和已经保存的测试证据,不执行学习者代码,也不判断 clean-room provenance、故障定位或口述质量。缺少提交、测试/故障记录未通过或口述未审核时明确保持 gate;合同校验成功也只是 human-review-required,不能宣称掌握。

论文与延伸

前端/Agent 迁移

把计算图看成响应式依赖图:节点是状态,边是派生关系,反向传播是从最终观测沿依赖边回传影响。迁移到 Agent 时,先记录 event → state transition → derived output,再决定重试或恢复;不能只记录最后一条日志。

口述与自测(不看资料,5–10 分钟)

  • z = (xw+b)^2 画出计算图并口述 xwb 的梯度如何沿拓扑逆序累加;解释共享节点为何不能覆盖梯度。
  • 选一个有限差分不一致或 loss 不下降的故障,说明你先看哪个局部导数/参数,再如何用数值对拍确认修复。
  • 用第2章的 chain_rule_2layer 思路手算 z=(xw+b)2x 的链式展开 z/x=2(xw+b)w,说明这和 autograd 的反向路径完全一致。
  • 解释 gradient_vector(f, x, y)Value.backward() 的关系——两者都在算梯度,但前者一次只输出一个点,后者要把整张图的梯度都算出来。

资源 / 成本 / 隐私

本地 Python/NumPy、CPU 即可;预计 gross cost 为 0。只用合成数值和公开论文链接,不上传履历、飞书资料或账号信息。

实验与参考

动手实验

把标量计算图、链式法则、共享节点的梯度累加这三个概念,从口头说法变成可复现的数值证据:先跑通 Value/Neuron/Layer/MLP 的基础测试确认实现不是空壳,再用 gradcheck.py 对同一份参数做有限差分对拍,确认 ∂L/∂θ 在小数点后 4 位与真值对齐。

环境准备

bash
cd <仓库>
export PYTHONPATH="$PWD/python"

命令与预期输出

bash
# 1) 基础正确性:算子、MLP、激活函数的有限差分对拍(已在「从零实践」跑过,此处补充另外两条)
python -m pytest python/tests/test_clean_room_faults.py -q

# 2) 一键总览:打印 autograd 数值与梯度
python -m labs.run_all
text
clean_room_faults: 4 cases injected, 4 expected signals reproduced
autograd 0.693147 0.9999998

# 判定条件:
# - 共享节点样例的梯度与有限差分误差 < 1e-5
# - MLP 在受控数据上 final loss < initial loss

概念图

图:第3章 autograd 知识流 — 从第2章的数学基础(闭包、偏导、链式法则)到本章的 Value.backward(),再到后续第4章训练循环、第6章 attention 投影、第8章 nn.Module 的自动微分。每个箭头代表"本章概念直接消费的前提"。

故障注入清单

故障表现修复
反向时用赋值覆盖梯度而非累加共享节点数值梯度偏小,多路径样例与有限差分不一致self.grad = ... 改为 self.grad += ...,多路径误差回到阈值内
调用 backward 之前忘记 zero_grad第二次反向结果随调用次数漂移,loss 看起来在降其实是累加训练循环开头先清零,并用重复调用测试覆盖
反向按节点创建顺序执行而非拓扑逆序依赖较深的叶子节点梯度为 0 或符号反掉先 DFS 收集从输出可达的节点,再逆序遍历 backward
有限差分步长 h 取太小(如 1e-12)与解析梯度比较时全是数值噪声h ≈ 1e-5,同时用相对误差而非绝对误差

Evidence

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

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

clean-room 合同的脱敏索引见 evidence/clean-room-submission-index-v1.json。当前索引只证明 7 个 blank interface、提交字段和自动化边界存在,status: gatelearner_submission.status: not-provided;它不是学习者实现、测试结果或毕业判定。

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

复制下面模板并填写自己的真实运行结果。所有 <...> 都是未填写状态;actualartifacts 尤其不能被当作已运行或已通过。artifacts 必须替换为本次提交中真实存在的仓库相对路径。

yaml
schema: learn-llm.evidence.v1
module: 03-autograd
commit: <learner-commit-sha>
verified_at: <iso-date>
environment: <sanitized-python-device>
seed: 1
commands:
  - PYTHONPATH=python python -m pytest python/tests/test_autograd.py -q
  - PYTHONPATH=python python -m pytest <learner-clean-room-gradcheck> -q
metrics:
  - name: finite_difference_relative_error
    expected: <versioned-threshold>
    actual: <recorded-value>
  - name: controlled_mlp_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>

没有 clean-room 测试、故障前失败/修复后通过和实际指标时,本章状态为 gate,不是完成。

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