LESSON 1.3 · 代码 · 120 分钟
让梯度沿计算图倒着走
反向传播是从标量损失出发,沿计算图反向计算向量—雅可比积。一个节点只有在所有下游路径的梯度贡献都到齐后,才能把完整的上游梯度继续传给父节点,所以需要先拓扑排序、再逆序执行。若同一变量被多条路径使用,总导数等于各路径贡献之和,因此梯度必须累加而不能覆盖。
DIRECT ANSWER · VERIFIED SOURCES ·
反向传播为什么要按逆拓扑顺序,并且用 += 累加梯度?
反向传播是从标量损失出发,沿计算图反向计算向量—雅可比积。一个节点只有在所有下游路径的梯度贡献都到齐后,才能把完整的上游梯度继续传给父节点,所以需要先拓扑排序、再逆序执行。若同一变量被多条路径使用,总导数等于各路径贡献之和,因此梯度必须累加而不能覆盖。
关键结论
- 前向传播记录数值与依赖关系;反向传播只执行每个运算的局部导数规则。
- 输出节点的梯度设为1,因为损失对自身的导数是1。
- 共享参数、分支与重复使用都会产生多条梯度路径,必须求和。
边界与常见误解
拓扑顺序保证依赖正确,但不能保证导数实现正确;仍需用中心差分和可信框架对拍。有限差分步长过大或过小都会造成误判。
一手来源
学完你应该能够
- 能解释 reverse-mode 为什么从标量输出沿逆拓扑顺序传播。
- 能为 add、mul、tanh 写局部反向规则。
- 能说明共享节点的梯度为何必须累加而不能覆盖。
- 能用有限差分和共享节点测试验证实现。
核心概念
动态 DAG
每次运算创建一个新 Value,并记录父节点和局部反向函数。图由真实执行路径生成,因此分支和重复使用会直接改变依赖关系。
局部反向规则
add 把上游梯度原样传给两个输入;mul 把上游梯度分别乘以另一个输入的前向值;tanh 乘以 1-t²。每条规则只负责一条局部边。
拓扑依赖
节点只有在所有下游贡献都到齐后才能继续向父节点传播。先通过 DFS 建立父节点在前的拓扑序,再逆序执行 _backward。
梯度累加
同一个节点可能经多条路径影响 loss。链式法则要求把各路径贡献相加;使用赋值号会静默丢掉先到的贡献。
标量种子
对最终标量 y 求 dy/dy=1,因此从输出 grad=1 开始。若输出不是标量,需要显式提供向量—雅可比积的上游向量。
实践任务
实现可测试的 Value、运算符与 backward
- 实现 Value(data, parents, op),并让 add、mul、tanh 返回新节点。
- 为每个运算写只处理局部贡献的 _backward,所有父梯度使用 +=。
- DFS 建立拓扑序,从输出 grad=1 开始逆序执行。
- 用 y=x*x+x、中心差分和 PyTorch 标量结果做三重对拍。