想象你是一位甜品研发师,正在调出一款完美的芝士蛋糕。配方里有面粉、糖、黄油、烘烤时间等变量,而你的“试味官”会给每款蛋糕打分。现在你最想知道的是一件事:如果某个原料多加 5%,口感分数会涨还是跌?涨多少? 这个问题本质上是在求“梯度”——找出每个变量对最终结果的影响方向和大小。 蛋糕从原料到成品要经过十几道工序:混合、打发、水浴、烘烤、冷藏……你不可能为每道工序都手写“影响公式”。这时候你需要的是一张配方流程图:从原料到成品的每一步都画成节点和连线。框架(PyTorch、TensorFlow)就是你的“智能烘焙助理”——它在烘焙的同时自动记账,等蛋糕出炉、打完分,它已经把每一道工序该往哪个方向调多少算得明明白白。这套自动计算梯度的技术,就是自动微分(Autodiff)。

岗位关联

掌握自动微分,你就能理解深度学习框架的求导魔法:无论网络多复杂,backward() 一次调用就能拿到全部梯度。这对你调试模型、写自定义算子、优化显存都至关重要——不再是“调参侠”,而是真正懂内功的工程师。

目录
  1. 可视化演示
  2. 计算图构建
  3. 链式法则
  4. 前向模式
  5. 反向模式
  6. 雅可比矩阵
  7. 梯度检查点
  8. 框架实现
  9. 三种微分对比
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态演示自动微分的核心机制,通过入门/进阶/实战/优化四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

自动微分:自动微分 自动微分与数值微分、符号微分的 前向模式自动微分的计算原理 核心机制可视化 自动微分整体架构与关键组件

计算图构建

甜品老师傅在教你做蛋糕时,第一步不是动手,而是把整个流程画成一张图:面粉 → 混合 → 打发 → 水浴 → 烘烤 → 成品。每一步谁先谁后、谁需要哪个中间产物,一目了然。深度学习里的“烘焙流程”同样复杂:输入经过线性变换、激活、归一化、卷积……层与层之间互相依赖。自动微分要做的第一件事,就是把这套流程画成一张计算图。

计算图是一种有向无环图(DAG):节点代表一个张量(中间计算结果),边代表一次运算(加、乘、卷积等)。比如函数 $y = x^2 + 1$,可以拆成三个节点——输入 $x$、中间结果 $x^2$、最终输出 $y$,中间用“平方”和“加一”两条运算边连接。框架执行前向计算时,会先对图做拓扑排序,保证每个节点计算时,它的所有输入都已经算好。

y=f3(f2(f1(x)))\displaystyle y = f_3\big(f_2(f_1(x))\big)

1. 最内层函数 $f_1$:输入 $x$ 首先经过第一个变换,得到中间量 $a$。

a=f1(x)\displaystyle a = f_1(x)

2. 中间层函数 $f_2$:上一步的中间量 $a$ 继续作为输入,得到新的中间量 $b$。

b=f2(a)=f2(f1(x))\displaystyle b = f_2(a) = f_2(f_1(x))

3. 最外层函数 $f_3$:最后输出 $y$ 等于第三层函数作用于 $b$。

y=f3(b)=f3(f2(f1(x)))\displaystyle y = f_3(b) = f_3(f_2(f_1(x)))

有了这张“配方流程图”,烘焙助理才能知道:从成品倒推回去时,每一步该走哪条路、该找哪个中间值。计算图,就是自动微分的地图——有了地图,导航(求导)才成为可能。

链式法则

蛋糕的甜度变化像玩“悄悄话”游戏:你给第一个人递一张纸条,他按自己的理解加工后传给下一个,每传一环,内容都会被“放大”或“缩小”一点点。最终蛋糕的口感变化,等于每一环变化率的乘积。数学上,这就是复合函数求导的核心——链式法则。

假设口感分数 $z$ 由焦糖层质量 $y$ 决定,而 $y$ 由白砂糖用量 $x$ 决定。你想知道“多加一勺糖,口感分数变多少”,不能只看 $x \to y$ 这一段,还得乘上 $y \to z$ 这一段。中间的每一环影响都要乘进去,就像传话游戏的每一环折扣都要叠乘。

dzdx=dzdy⋅dydx\displaystyle \frac{dz}{dx} = \frac{dz}{dy} \cdot \frac{dy}{dx}

1. 先求“输出对中间量”的导数:衡量 $y$ 变化一个单位时,$z$ 变化多少。

dzdy\displaystyle \frac{dz}{dy}

2. 再求“中间量对输入”的导数:衡量 $x$ 变化一个单位时,$y$ 变化多少。

dydx\displaystyle \frac{dy}{dx}

3. 两者相乘:把两段影响串联起来,就得到 $x$ 对最终输出 $z$ 的总影响。

dzdx=dzdy×dydx\displaystyle \frac{dz}{dx} = \frac{dz}{dy} \times \frac{dy}{dx}

链式法则像是传话游戏里的“传递系数”:每一环的放大或缩小比例相乘,最后得到总效应。自动微分之所以能处理几百层的网络,就是靠这条规则把每层的局部导数逐环串起来。

前向模式

如果配方里只有一种原料不确定,你会怎么做?最笨但可靠的办法是:每次只改这一种原料,重新做一遍蛋糕,观察分数变化。自动微分里的前向模式就像这个“一次只盯一个变量”的做法——它给某一个输入$x_i$带上一个“导数种子”,然后沿着计算图从输入往输出正向传播,每经过一个运算,就同步更新这个导数。

前向模式的核心是“原始-对偶”思想:每个值不只记本身,还额外记一个“对某个输入的导数”。比如对 $v = x^2$,它不仅算出结果 $v$,还同时算出 $\dot{v} = 2x\dot{x}$,这个 $\dot{x}$ 就是种子(初始设为 1)。这样一路走完,就得到了所有中间量对 $x$ 的导数。

v˙=2x⋅x˙(v=x2 时的前向导数)\displaystyle \dot{v} = 2x \cdot \dot{x} \quad \text{($v=x^2$ 时的前向导数)}

1. 设置种子:令 $\dot{x}=1$,表示我们正在对输入 $x$ 求导。

x˙=1\displaystyle \dot{x} = 1

2. 逐运算传播:每个中间节点 $v=x^2$ 的导数由数学求导规则得到。

v˙=2x⋅x˙\displaystyle \dot{v} = 2x \cdot \dot{x}

3. 替换并计算:把当前的 $x$ 值和 $\dot{x}$ 值代入,即可得到 $\dot{v}$ 的数值。

v˙=2×2×1=4\displaystyle \dot{v} = 2 \times 2 \times 1 = 4

前向模式“每改一个输入就要重走一遍图”,所以它适合输入维度很小的场景。如果你的模型只有几个输入、却有成千上万的输出,前向模式反而高效。但反过来说,深度学习里输入通常是百万级参数,前向模式就力不从心了。

反向模式

真正高明的烘焙师不会一次次重做蛋糕。他等蛋糕出炉、试味打分之后,从成品口感一路往回倒推:口感偏甜了?那问题可能出在糖量;糖量没问题?再往前查焦糖时间……每一环只需要查一次,所有原料的影响就都能算清楚。这就是反向模式——也是深度学习里大名鼎鼎的“反向传播”。

反向模式分两步走:第一步,正向执行计算图,算出每个中间值并缓存;第二步,从输出出发,利用链式法则把“损失 $L$ 对每个节点”的偏导从后往前逐层回传。无论中间有多少个节点,整个过程只需一次正向 + 一次反向,就能得到损失对所有参数的梯度。

∂L∂x=∂L∂v⋅∂v∂x\displaystyle \frac{\partial L}{\partial x} = \frac{\partial L}{\partial v} \cdot \frac{\partial v}{\partial x}

1. 正向缓存:先走一遍计算图,保存每个中间值 $v$,以备反向使用。

v=x2,L=v+1\displaystyle v = x^2,\quad L = v + 1

2. 反向起步:令输出对自身的导数为 $1$,即 $\bar{L}=1$,作为回传的起点。

Lˉ=∂L∂L=1\displaystyle \bar{L} = \frac{\partial L}{\partial L} = 1

3. 逐层回传:用链式法则把梯度乘到上游节点的局部导数上,例如对 $v$ 传回 $\bar{v} = \bar{L}\cdot\frac{\partial L}{\partial v}$,再对 $x$ 传回 $\bar{x} = \bar{v}\cdot\frac{\partial v}{\partial x}$。

xˉ=Lˉ⋅∂L∂v⋅∂v∂x\displaystyle \bar{x} = \bar{L}\cdot\frac{\partial L}{\partial v}\cdot\frac{\partial v}{\partial x}

反向模式的高明之处在于“一次倒推,全部算清”。训练神经网络时,损失 $L$ 是一个标量(单个数值),输出维度为 1——完美契合反向模式“输出少、输入多”的长处。这也是 PyTorch 中 loss.backward() 一次调用就能算出全部参数梯度的秘密。

雅可比矩阵

当配方变得复杂——比如同时改变面粉、糖、黄油三种原料(三个输入),又要同时观察蛋糕的甜度、硬度、湿润度(三个输出)——单条链式法则就不够用了。这时你需要一张大表:行是每个输出,列是每个输入,表格里每一格填“这个输入变化一点点,那个输出变化多少”。这张表在数学上就叫雅可比矩阵。

雅可比矩阵把前向和反向模式统一了起来。前向模式一次遍历,相当于“矩阵乘以一个向量”——得到的是某一列(某个输入对所有输出的影响);反向模式一次遍历,相当于“一个向量乘以矩阵”——得到的是某一行(所有输入对某个输出的影响)。因此,选哪种模式,本质上是看你想填充矩阵的列还是行。

J=∂(y1,…,ym)∂(x1,…,xn)=(∂y1∂x1⋯∂y1∂xn⋮⋱⋮∂ym∂x1⋯∂ym∂xn)\displaystyle J = \frac{\partial(y_1,\dots,y_m)}{\partial(x_1,\dots,x_n)} = \begin{pmatrix} \frac{\partial y_1}{\partial x_1} & \cdots & \frac{\partial y_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial y_m}{\partial x_1} & \cdots & \frac{\partial y_m}{\partial x_n} \end{pmatrix}

1. 确定维度:有 $n$ 个输入、$m$ 个输出,矩阵规模就是 $m$ 行 $n$ 列。

J∈Rm×n\displaystyle J \in \mathbb{R}^{m\times n}

2. 填充每个偏导:第 $i$ 行第 $j$ 列元素是偏导数 $\partial y_i / \partial x_j$,描述单点影响。

Jij=∂yi∂xj\displaystyle J_{ij} = \frac{\partial y_i}{\partial x_j}

3. 与模式对应:前向模式是 $J$ 右乘向量(求一列),反向模式是向量左乘 $J$(求一行)。

J⋅vvsu⊤J\displaystyle J \cdot \mathbf{v} \quad \text{vs} \quad \mathbf{u}^{\top} J

这张“影响大表”点明了自动微分的关键权衡:存整张表很贵,但大多数场景我们只需要一行——损失对每个参数的梯度。所以反向模式成为深度学习的默认选择,而雅可比矩阵则是理解两种模式如何分工的数学透镜。

梯度检查点

如果蛋糕配方长达几千步,烘焙助理每走一步都把半成品拍照存档,仓库很快就会塞满。聪明的做法是:只在大步骤(比如每 10 步)设置一个“驿站快照”,中间过程不存档;需要回溯时,从最近的驿站重新走一遍到目标步骤。这就是梯度检查点(Checkpointing)的核心思想——用一点重算时间,换取大量内存。

训练深层神经网络时,反向传播需要所有中间激活值来计算梯度。如果网络有 1000 层,正向时把每一层的激活都存下来,显存直接爆满。梯度检查点策略只保存一部分层(检查点)的激活,其余层在反向时重新正向计算一次,从而大幅降低显存占用。

Mcheckpoint=O(L)vsMfull=O(L)\displaystyle M_{\text{checkpoint}} = O(\sqrt{L}) \quad \text{vs} \quad M_{\text{full}} = O(L)

1. 全程存储的代价:反向需要每一层的中间激活,内存与层数 $L$ 成正比。

Mfull=O(L)\displaystyle M_{\text{full}} = O(L)

2. 分段存储省内存:每 $\sqrt{L}$ 层设一个检查点,内存约为检查点数量乘以每段存储量。

Mcheckpoint=O(L)\displaystyle M_{\text{checkpoint}} = O(\sqrt{L})

3. 用时间换空间:反向时检查点之间的层需要重新计算,总时间约增加一倍,但显存大幅下降。

Ttotal≈Tforward+Trecompute\displaystyle T_{\text{total}} \approx T_{\text{forward}} + T_{\text{recompute}}

训练超大模型时,显存往往比算力更稀缺。梯度检查点就像长途运输中的“周转仓库”——不全程囤货,只在关键节点中转,需要时再回头补货。从检查点到重算,就是典型的时空权衡。

框架实现

终于轮到你的“智能烘焙助理”登场了。在 PyTorch 中,一切求导魔法都围绕 Tensor 展开:当你设置 requires_grad=True 时,框架就开始自动记账——每次运算都在背后偷偷建立计算图节点;当你调用 loss.backward() 时,助理从成品(损失)出发,沿着记账本反向走一遍,把梯度填到每个 Tensor 的 .grad 属性里。

自动微分的框架实现高度模块化:Tensor 层负责记录数据和追踪运算;autograd 引擎负责维护计算图和调度反向;每个算子都注册了自己的“局部反向函数”,反向时逐层调用。框架还会做梯度累积——如果一个节点被多条路径使用,它的梯度会把所有路径传来的梯度加起来,因为链式法则对多元路径求和。

∂L∂v=∑u∈succ⁡(v)∂L∂u⋅∂u∂v\displaystyle \frac{\partial L}{\partial v} = \sum_{u \in \operatorname{succ}(v)} \frac{\partial L}{\partial u} \cdot \frac{\partial u}{\partial v}

1. 找下游节点:枚举所有直接使用 $v$ 的后继节点 $u$。

u∈succ⁡(v)\displaystyle u \in \operatorname{succ}(v)

2. 算每条路径贡献:对每个 $u$,梯度贡献等于 $L$ 对 $u$ 的梯度乘以 $u$ 对 $v$ 的局部导数。

∂L∂u⋅∂u∂v\displaystyle \frac{\partial L}{\partial u} \cdot \frac{\partial u}{\partial v}

3. 汇总求和:把所有路径的贡献相加,得到 $v$ 的总梯度。

∂L∂v=∑u∂L∂u⋅∂u∂v\displaystyle \frac{\partial L}{\partial v} = \sum_{u} \frac{\partial L}{\partial u} \cdot \frac{\partial u}{\partial v}

理解了框架的记账逻辑,你就明白了为什么每次训练要调用 optimizer.zero_grad()——因为框架只负责累加梯度,不会自动清零上一轮的旧账。也明白了为什么自定义算子时要实现“前向 + 反向”两个函数:前向供计算图记录,反向供引擎回传。自动化程度再高,原理始终是那条链式法则。

三种微分对比

市面上的“求导方案”有三种,可以类比成三种做蛋糕的方式。数值微分:每次试吃微调,用“多加一勺和少加一勺的分数差”来估算影响,简单但粗糙,还有截断误差。符号微分:把整个配方的数学公式完整写出来,再对公式做符号化简求导,思路清晰但公式会随步骤增多爆炸式膨胀。自动微分:像记账员一样按流程图逐步记录每个运算的导数,既不写大公式,也不算近似值——精确又高效。

f′(x)≈f(x+h)−f(x−h)2h\displaystyle f'(x) \approx \frac{f(x+h) - f(x-h)}{2h}

1. 选一个小步长:$h$ 是一个很小的增量,比如 $10^{-6}$。

h=10−6\displaystyle h = 10^{-6}

2. 算两侧函数值:分别计算 $f(x+h)$ 和 $f(x-h)$。

f(x+h),f(x−h)\displaystyle f(x+h), \quad f(x-h)

3. 做差分取平均:两值之差除以 $2h$,近似得到 $f'(x)$。

f′(x)≈f(x+h)−f(x−h)2h\displaystyle f'(x) \approx \frac{f(x+h) - f(x-h)}{2h}

三种方式各有千秋,但深度学习选择了自动微分:数值微分只能用来“验证梯度是否正确”,符号微分不适合复杂计算图,而自动微分既有解析的精确度,又有数值的高效性。它不去写整本菜谱,只是默默记好每一笔账——这就是现代深度学习框架统一的求导方案。

AI 应用场景

自定义算子梯度校验

阶段:模型调试与验证

解决的问题:自定义激活函数或损失函数时,手推梯度容易出错,且实现的反向传播若有 bug 会导致模型训练异常。

具体案例:你在研究一个新的注意力机制,写了一个自定义算子。用 torch.autograd.gradcheck 把你的反向实现与数值微分结果对比,几毫秒内就能报告“梯度校验通过/失败”。自动微分框架作为“裁判”,用数值微分辅助验证你的手写反向是否与正向前向一致,避免模型 train 到一半 loss 变成 NaN。

大模型显存优化

阶段:高效训练

解决的问题:训练深层网络时,反向传播需要保存全部中间激活,显存随层数线性增长而很快耗尽。

具体案例:训练一个 50 层的 Transformer 模型,单卡 24GB 显存几乎爆掉。你启用 PyTorch 的 torch.utils.checkpoint.checkpoint_sequential,只保存关键层的中间激活,反向时重新计算被跳过的层,显存占用下降约 40%,训练成功跑通。自动微分的“梯度检查点”技术让大模型在有限显存中得以训练。

梯度诊断与调试

阶段:训练监控

解决的问题:模型训练 loss 不下降时,工程师难以判断是数据问题、学习率问题还是梯度出了问题。

具体案例:新训练一个生成对抗网络,判别器 loss 一直震荡。你在每个 step 后打印各层参数梯度的均值与方差,发现深层梯度接近 0(梯度消失)、浅层梯度爆炸。借助自动微分提供的 .grad 信息,你定位到初始化方式不当,改用正交初始化并加入梯度裁剪,训练恢复稳定。

Python 演示

python60 行
class Value:    """自动微分标量节点:记录数据、梯度与局部反向函数"""     def __init__(self, data, children=(), op=''):        self.data = data        self.grad = 0.0        self._children = set(children)        self._op = op        self._backward = lambda: None     def __add__(self, other):        other = other if isinstance(other, Value) else Value(other)        out = Value(self.data + other.data, (self, other), '+')         def _backward():            self.grad += out.grad            other.grad += out.grad        out._backward = _backward        return out     def __mul__(self, other):        other = other if isinstance(other, Value) else Value(other)        out = Value(self.data * other.data, (self, other), '*')         def _backward():            self.grad += other.data * out.grad            other.grad += self.data * out.grad        out._backward = _backward        return out     def relu(self):        out = Value(max(0.0, self.data), (self,), 'relu')         def _backward():            self.grad += (out.data > 0) * out.grad        out._backward = _backward        return out     def backward(self):        topo = []        visited = set()         def build_topo(v):            if v not in visited:                visited.add(v)                for child in v._children:                    build_topo(child)                topo.append(v)        build_topo(self)        self.grad = 1.0        for node in reversed(topo):            node._backward()  if __name__ == '__main__':    x = Value(2.0)    y = x * x + 1          # y = x^2 + 1    y.backward()    print('y =', y.data)    print('dy/dx =', x.grad)   # 期望 4.0
目的:用约 60 行代码实现一个微型自动微分引擎,演示反向模式的核心流程——构建计算图、拓扑排序、从输出到输入回传梯度。
  1. self.data = data存张量的数值;self.grad = 0.0 — 存累积梯度,对应框架里 Tensor 的 .grad 属性。
  2. self._backward = lambda: None每个节点默认没有反向函数,运算时会替换为真正的局部反向逻辑。
  3. def __add__重载 + 运算符:前向算出 out.data,同时把两个子节点登记到 _children,相当于给计算图加了一条“加法边”。
  4. out._backward = _backward注册反向函数:加法把输出梯度原样传给两个输入,实现 $\bar{x}=\bar{y}$。
  5. def __mul__乘法反向按链式法则:self.grad += other.data * out.grad,对应 $\bar{x}=\bar{v}\cdot y$。
  6. build_topo(self)深度优先遍历子节点完成拓扑排序,确保反向时每个节点的梯度都已就绪。
  7. self.grad = 1.0反向起点:输出对自身的导数为 1,就像反向模式从损失函数出发。
  8. for node in reversed(topo): node._backward()按拓扑逆序逐节点回传梯度,这就是框架 loss.backward() 的本质。

常见误区

误区很多初学者以为每调用一次 `loss.backward()`,框架会自动把上次的梯度清空,所以不需要写 `optimizer.zero_grad()`。

正解自动微分引擎的机制是累加梯度——它按“梯度累积”公式把多条路径的贡献加到一起。如果不清零,每一个 batch 的梯度都会叠加在上一次之上,参数更新的方向就会错乱。回想可视化里的反向模式:红色光点把梯度从输出一路回传,路径上每经过一个节点就做加法。这正是为什么训练循环里必须显式调用 optimizer.zero_grad() 把 .grad 归零。

为何常见:框架把“求梯度”和“更新参数”封装得太自然,用户只看到 loss.backward() 和 optimizer.step(),容易忽略梯度是累加而非覆盖这一底层事实。

误区有人说“数值微分是直接算出来的,没有推导,应该比自动微分更可信”,尤其担心自动微分是黑盒。

正解数值微分本质是近似——中心差分公式存在截断误差($h$ 不够小)和舍入误差($h$ 过小导致浮点精度损失)。而自动微分沿计算图精确应用链式法则,每一步都是精确的解析求导,只是以数值形式输出。它既非黑盒,也没做任何近似。实践中数值微分只作为验证辅助工具(如 gradcheck),而自动微分才是被依赖的“真相源”。

为何常见:数值微分实现简单、结果直观,且数学课上求导公式都是从“极限差分”引入的,让人误以为它更接近定义、更可靠。

误区有人设置某个参数 `requires_grad=False` 后,认为这个参数就“退出计算图”,对梯度传播毫无影响。

正解requires_grad=False 只是表示不追踪、不保存该参数的梯度,但它仍然是计算图的一部分。如果一条数据流经过它再传到可训练参数,梯度依然会穿过它继续回传(因为中间计算图节点仍会记录运算)。真正被“截断”的是该参数自身的 .grad 不会被填充。理解这一点,才能正确冻结模型的某些层(如迁移学习时冻结骨干网络)。

为何常见:框架文档强调“设置为 False 时不需要梯度”,初学者容易把“不需要梯度”误解为“不参与梯度传播”。

误区一些有经验的开发者觉得框架的 autograd 太“重”,自己手动存中间值、手动写反向函数更灵活也更快。

正解自动微分引擎做了大量的优化:算子级融合、显存复用、反向调度优化、稀疏梯度支持等。手动反向在小模型上或许可行,但在深层网络、动态控制流、混合精度的场景下极易出错且难以维护。更重要的是,手写反向等于同时维护正向和反向两套逻辑,任何修改都可能引入不一致。框架的自动微分是经过千万次训练验证的工程结晶。

为何常见:初学反向传播时大家都会手推几层 BP 公式,产生“我自己能写”的错觉,忽视了框架在复杂场景下的正确性与性能优势。

面试题

Q1. 自动微分与传统手推梯度相比,最大的优势是:

单选题 · easy

  • A.自动得到精确梯度解析式
  • B.省去人工推导求导过程← 正确答案
  • C.能够完全替代数值微分
  • D.不需要借助计算图结构

答案:B。自动微分把求导自动化,开发者不必手推复杂网络的梯度公式。A 错在“解析式”——自动微分输出的是数值梯度而非表达式;C 错在它无法完全替代数值微分在验证中的作用;D 错在自动微分恰恰需要计算图。

Q2. 在自动微分的计算图中,一个节点通常代表:

单选题 · easy

  • A.神经网络的完整参数集
  • B.一个中间计算产生的张量← 正确答案
  • C.一条完整训练数据样本
  • D.一个定义好的损失函数

答案:B。计算图的节点是张量(中间计算结果),边是运算。参数集、数据样本、损失函数在图中都只是某个具体的张量节点,并不是“节点类型”的划分标准。选 B 最准确地描述了节点在宏观图中的角色。

Q3. 前向模式自动微分适合的应用场景是:

单选题 · easy

  • A.网络模型结构特别深
  • B.网络输入维度非常少← 正确答案
  • C.需要多次反向传播
  • D.网络输出维度很少

答案:B。前向模式的遍历次数等于输入维度——每求一个输入的导数就要完整走一遍图。输入维度少时它最高效;反过来,输出维度少时更适合反向模式。网络深度不是决定性因素,故选 B。

Q4. 反向模式自动微分最核心的数学基础是:

单选题 · mid

  • A.泰勒级数展开
  • B.克莱姆法则
  • C.链式法则← 正确答案
  • D.特征值分解

答案:C。反向模式从输出向输入回传梯度,本质是反复使用链式法则把局部导数逐层相乘、多条路径求和。泰勒展开是数值微分的原理之一,克莱姆法则与特征值分解与反向传播无关。

Q5. 梯度检查点技术主要解决了深度学习训练中的:

单选题 · mid

  • A.模型过拟合严重问题
  • B.反向传播梯度爆炸问题
  • C.显存容量不足的问题← 正确答案
  • D.参数初始化困难问题

答案:C。梯度检查点通过“少存中间结果、反向时重算”来降低显存占用,用时间换空间。它不改变梯度大小,也不影响过拟合或初始化,因此选 C。

Q6. 在 PyTorch 中完成 loss.backward() 调用后,参数的梯度存放在:

单选题 · mid

  • A.loss 对象的 data 属性
  • B.模型对象的梯度缓存中
  • C.计算图的每条边上
  • D.参与计算的叶子张量的 grad 中← 正确答案

答案:D。反向传播完成后,只有叶子张量(通常是模型参数)的 .grad 属性被填充。非叶子节点的梯度默认不保留以节省内存;计算图的边记录的是运算而非梯度。故选 D。

Q7. 关于自动微分的前向模式与反向模式,正确的说法有:

多选题 · mid

  • A.前向模式要遍历输入维度次← 正确答案
  • B.反向模式一次得到全部梯度← 正确答案
  • C.标量损失通常采用反向模式← 正确答案
  • D.前向模式不依赖链式法则

答案:ABC。前向模式每求一个输入的导数走一遍图,遍历次数等于输入维度;反向模式从标量输出出发,一次反向就能得到所有输入的梯度,所以深度学习普遍用反向模式。前向模式同样依赖链式法则(每步运算都使用了局部导数相乘),D 错误。

Q8. 一个标量损失 L 依赖 100 维输入 x,用反向模式求 ∂L/∂x,需要完整遍历计算图:

单选题 · hard

  • A.恰好 1 遍← 正确答案
  • B.恰好 2 遍
  • C.恰好 100 遍
  • D.恰好 200 遍

答案:A。反向模式的遍历次数取决于输出维度,而不是输入维度。标量损失输出维度为 1,因此只需要一次反向遍历即可得到全部 100 个偏导(另外还需一次前向遍历来缓存中间值,但反向遍历本身只需 1 遍)。100 维输入对应的是前向模式的遍历次数。

Q9. 关于雅可比矩阵与两种模式的关系,下列说法正确的是:

单选题 · hard

  • A.前向模式相当于矩阵乘向量← 正确答案
  • B.前向模式相当于向量乘矩阵
  • C.反向模式不涉及矩阵运算
  • D.雅可比矩阵是方阵必定可逆

答案:A。前向模式每次传播是雅可比矩阵 $J$ 右乘一个种子向量,相当于取一列;反向模式是梯度行向量左乘 $J$,相当于取一行。反向模式同样涉及矩阵运算,C 错;雅可比矩阵不一定方阵,方阵也不一定可逆,D 错。

Q10. 关于数值微分、符号微分与自动微分,正确的描述有:

多选题 · hard

  • A.数值微分会引入截断误差← 正确答案
  • B.符号微分可能产生表达式爆炸← 正确答案
  • C.自动微分兼具精确与高效← 正确答案
  • D.数值微分可直接用于大型模型

答案:ABC。数值微分用差分近似导数,$h$ 的选择必然带来截断或舍入误差;符号微分对复杂函数求导时表达式会指数级膨胀;自动微分沿计算图应用链式法则,既有解析精度又有数值效率。数值微分计算开销大且精度有限,不适用于大型模型训练,D 错误。

Q11. 关于梯度检查点与计算图的复杂度的分析,正确的有:

多选题 · extreme

  • A.梯度检查点用重算代价降显存← 正确答案
  • B.检查点个数过大时重算开销上升← 正确答案
  • C.反向模式内存通常高于前向模式← 正确答案
  • D.检查点可完全消除前向计算

答案:ABC。梯度检查点的本质是“少存中间值、反向时重新计算”,显存从 $O(L)$ 降到 $O(\sqrt{L})$,但重算增加了时间开销;检查点设置过多,重算段变多,总计算量上升。反向模式因为要缓存所有中间激活,内存通常高于前向模式。检查点只能减少存储,不能消除前向计算,D 错误。