01-全景与张量基础 核心 预计 25 分钟 ★ 最小路径

自动微分与计算图

自动微分(Automatic Differentiation)是把程序的计算过程记录为有向无环计算图(Computational Graph),再用链式法则沿图反向自动算出损失对每个参数梯度的技术,是所有深度学习框架的心脏。

一句话定义

自动微分(Automatic Differentiation)是把程序的计算过程记录为有向无环计算图(Computational Graph),再用链式法则沿图反向自动算出损失对每个参数梯度的技术,是所有深度学习框架的心脏。

直觉

计算图像「工厂流水线的监控录像」:前向传播时每个工位(运算)都录下了自己的输入和输出;反向传播时倒放录像,每个工位拿着「下游送来的总误差」按自己的加工规则拆分出「上游各原料该背多少责任」(局部梯度),逐级传回原料仓库(参数)。

为什么重要

深度模型动辄百万到千亿参数,手工推导每个参数的梯度不可行。自动微分把「会写前向计算」直接升级为「自动获得训练能力」,是深度学习工程门槛在 2015 年后骤降的核心原因。理解它,才能解释梯度消失的来源(kp-023)、看懂 kp-014 批归一化在图中插入的节点、并正确使用 kp-004 中的 requires_grad 与 detach。

前置知识

  • kp-002(张量与形状)。
  • 库外前置:链式法则 d/dx f(g(x)) = f'(g(x))·g'(x)。

核心概念

  • 计算图:以运算为节点、张量为边的有向无环图(DAG),前向时构建,反向时遍历。
  • 前向传播(forward):按拓扑序执行运算,同时缓存中间结果。
  • 反向模式自动微分(reverse-mode AD):从输出端出发,按链式法则逆向累乘局部导数,一次反向得到对所有输入的梯度。
  • 局部梯度:单个运算节点自己的导数,如乘法节点 ∂(ab)/∂a = b。
  • 叶子节点与参数:requires_grad=True 的张量(通常是权重)是梯度累积的终点。
  • 梯度缓存与清零:框架默认把梯度累加到 .grad,每次迭代前需手动 zero_grad(见 kp-016 代码)。

原理与机制

设有复合函数 L = (a·b + c)²,其中 a、b、c 为输入。前向构建图:u1 = a·b,u2 = u1 + c,L = u2²。反向传播从顶端开始:∂L/∂u2 = 2u2;u2 是加法节点,∂u2/∂u1 = 1、∂u2/∂c = 1,故责任原样下发;u1 是乘法节点,∂u1/∂a = b、∂u1/∂b = a,于是 ∂L/∂a = 2u2·1·b,∂L/∂b = 2u2·1·a。整条链就是「下游总梯度 × 本节点局部梯度」反复相乘:这正是链式法则的图上实现。反向模式之所以成为深度学习标准,是因为损失是标量、参数有百万个——一次反向即可拿到全部 ∂L/∂θᵢ,代价与一次前向同阶;若是「一次前向求一个输入导数」的正向模式,参数多时开销不可接受。框架实现要点有三:其一,前向时把每个运算的输入与输出缓存下来(反向要用);其二,梯度在分支节点处要累加而不是覆盖(多下游共享一个上游时各自贡献相加);其三,不需要梯度的分支(如评估阶段)用 no_grad 关闭记录以省内存。

公式或模型

链式法则的张量形式(单条路径与多路径汇合):

单路径:  ∂L/∂x = ∂L/∂u · ∂u/∂x
多路径:  ∂L/∂x = Σ_paths ∂L/∂path末端 · … · ∂…/∂x   (对各分支求和)
一般形式:∂L/∂θ_i = ∂L/∂a_L · ∂a_L/∂a_{L-1} · … · ∂a_1/∂θ_i

变量说明:a_l 为第 l 层输出,L 为损失(标量),θ_i 为任一参数。kp-007 将把该式在 MLP 上逐层展开成矩阵形式。

图示

前向:    a ──┐
              ×──► u1 ──┐
          b ──┘         +──► u2 ──► ² ──► L
          c ────────────┘

反向:    ∂L/∂L = 1
          ∂L/∂u2 = 2·u2
          ∂L/∂u1 = ∂L/∂u2 · 1        (加法:直通)
          ∂L/∂a  = ∂L/∂u1 · b        (乘法:换同伴)
          ∂L/∂b  = ∂L/∂u1 · a
          ∂L/∂c  = ∂L/∂u2 · 1

直观类比

多路径累加像「年终绩效考核」:公司利润(损失)出问题,追责沿组织树往下拆分;一个部门被两条业务线同时依赖时,它的责任是两条线贡献之和——这正是梯度累加规则。

实例或案例

PyTorch 最小自动微分示例(完整可运行):

import torch
a = torch.tensor(2.0, requires_grad=True)
b = torch.tensor(3.0)
L = (a * b + 1.0) ** 2      # 前向:框架暗中记录计算图
L.backward()                 # 反向:一次调用得到所有梯度
print(a.grad)                # ∂L/∂a = 2·(a·b+1)·b = 2·7·3 = 42

反向传播的理论推导见 kp-007,把 a.grad 用到参数更新的是 kp-010 的优化器;kp-016 的训练循环里 L.backward() 就是本知识点的一行化身。

常见误区

  • 忘记 optimizer.zero_grad():梯度默认累加,跨批次污染导致训练莫名发散(kp-016 有完整代码示范)。
  • 在循环里反复 backward 同一张量而不 retain_graph:图已被释放,报「Trying to backward through the graph a second time」。
  • 以为 .grad 与参数同形状是巧合:它是自动微分按 kp-002 形状规则算出的,必然同形状。
  • 用 eval 模式推理仍带着计算图:应使用 torch.no_grad() 包裹,否则显存随推理增长。

与其他知识点的关系

本知识点是 kp-007(反向传播逐层公式)的机制底座、kp-010(优化器消费梯度)的上游、kp-016(训练循环五件套之一)的实现依赖;kp-023 梯度消失/爆炸本质是本知识点「逐层相乘」结构在深网络下的数值后果。

自测题

  1. 为什么深度学习用反向模式而不是正向模式自动微分?

答案要点:损失是标量而参数海量化,反向模式一次遍历得到全部参数梯度,开销与参数量无关(同前向同阶)。

  1. 计算图中一个节点有两条出边(被两个下游使用)时,反向如何处理?

答案要点:两条路径传回的梯度必须相加后再乘本节点局部梯度(多变量链式法则求和项)。

  1. 手算 L=(a·b+1)² 在 a=2、b=3 处的 ∂L/∂b。

答案要点:u=a·b+1=7,∂L/∂u=2u=14,∂u/∂b=a=2,故 28。

延伸阅读

  • Goodfellow 等《Deep Learning》6.5 节「反向传播和其他微分算法」。
  • Baydin、Pearlmutter、Radul、Siskind《Automatic Differentiation in Machine Learning: a Survey》JMLR 2018,自动微分全景综述。

学习状态

状态保存在浏览器本地,用于首页与路径页的进度统计。