想象你是一家连锁奶茶店的会计。深夜总部审计师来查账,想知道“如果白糖进货价涨 1 元,咱们店里 8 款饮品的成本分别会浮多少?”你当然可以重新算一遍所有成本表,但更聪明的做法是提前准备一张“变化率账本”:每一行对应一款饮品,每一列对应一种原料,格子里只写“原料涨价 1 元,这款饮品成本变化多少”。有了这个账本,审计师可以沿着行读,也可以沿着列读。沿着一列读,是问“一种原料的变动会怎样影响所有饮品”,这是前向模式想做的事;沿着一行反过来汇总,是问“如果希望某款饮品利润提高 1 元,每种原料价格应该承担多少反馈”,这是反向模式想做的事。这两种读法,就是雅可比向量积 JVP 与向量雅可比积 VJP。AI 训练里的反向传播,本质上是把无数个“反馈分摊”动作串成一条流水线;这篇文章带你把这本账彻底看透。

岗位关联

理解 JVP 与 VJP,就是拿到自动微分引擎的“内部汇率表”。无论你调 PyTorch、写自定义算子,还是开发 AI 编译器,都能精确预测一次反向传播到底在算什么。

目录
  1. 可视化演示
  2. 雅可比矩阵:变化率账本
  3. JVP前向模式:顺着账本往前读
  4. VJP反向模式:拿着反馈单倒着分摊
  5. 计算图实现:流水线上的账房
  6. 框架API设计:收银台的点单方式
  7. 模式选择与复杂度:哪种记账更划算
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示JVP/VJP三维度:链式法则对比(JVP前向链式J*v沿输入到输出传播每步用局部雅可比乘切向量VJP反向链式J^T*v̄沿输出到输入传播)、计算图实现(JVP前向流式传播无需保存中间结果内存O(1)VJP需保存前向中间值反向重放内存O(n)以空间换时间)、选型决策(n小于m少输入多输出选JVP需O(n)次前向n大于m多输入少输出选VJP需O(m)次反向DL中n远大于m必选VJP),三模式切换理解自动微分底层运算机制。

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

'+C1+'

雅可比矩阵:变化率账本

你是奶茶店会计,总部审计师每个月都会来查“价格对利润的影响”。你不会把所有价格重新算一遍,而是提前维护一张表:行是 8 款饮品,列是 6 种原料,每个格子只记一个变化率——原料涨价 1 元,某款饮品的成本变动多少。这张表不记录绝对价格,只记录“彼此拉扯的敏感度”,学名叫雅可比矩阵。

把业务翻译成数学语言:函数 \(f:\mathbb{R}^n \to \mathbb{R}^m\) 有 \(n\) 个输入和 \(m\) 个输出。雅可比矩阵的第 \(i\) 行第 \(j\) 列,就是第 \(i\) 个输出对第 \(j\) 个输入的偏导数。于是整张表有 \(m\) 行、\(n\) 列,像一本竖着排的账本。

账本一旦建立,审计师就拥有了两种提问方式。他可以拿笔指着某一列问:“如果白糖涨 1 元,所有饮品分别怎样变?”也可以拿着总部的反馈单问:“如果珍珠奶茶利润要提高 1 元,每种原料各要承担多少?”前者引出 JVP,后者引出 VJP。

\displaystyleJ = \begin{bmatrix}\frac{\partial f_1}{\partial x_1} & \cdots & \frac{\partial f_1}{\partial x_n} \\\vdots & \ddots & \vdots \\\frac{\partial f_m}{\partial x_1} & \cdots & \frac{\partial f_m}{\partial x_n}\end{bmatrix} \in \mathbb{R}^{m \times n}
雅可比矩阵就是一张“变化率账本”:第 \(i\) 行回答“第 \(i\) 个输出会被哪些输入影响”,第 \(j\) 列回答“第 \(j\) 个输入会影响哪些输出”。
  1. 行对应输出矩阵有 \(m\) 行,每一行是某个输出分量 \(f_i\) 对全部输入的偏导。
    Ji∗=[∂fi∂x1 ⋯ ∂fi∂xn]\displaystyle J_{i*} = \left[\frac{\partial f_i}{\partial x_1} \ \cdots \ \frac{\partial f_i}{\partial x_n}\right]
  2. 列对应输入每一列是某个输入 \(x_j\) 对全部输出分量的影响。
    J∗j=[∂f1∂xj ⋯ ∂fm∂xj]T\displaystyle J_{*j} = \left[\frac{\partial f_1}{\partial x_j} \ \cdots \ \frac{\partial f_m}{\partial x_j}\right]^T
  3. 形状由函数决定输出数 \(m\) 决定行数,输入数 \(n\) 决定列数。
    J∈Rm×n\displaystyle J \in \mathbb{R}^{m \times n}

JVP前向模式:顺着账本往前读

某天审计师问:“白糖价格涨 1 元,店里 8 款饮品利润各受影响多少?”你不需要把 8 款饮品逐一重新核算,只需要把“白糖”这一列的敏感度取出来,乘上“涨 1 元”这个幅度。这就是雅可比向量积 JVP:给定一个输入方向向量 \(v\),把它从账本左边乘进去,得到输出方向向量。

\(v\) 不是一个具体的新价格,而是一个“扰动方向”。例如 \(v_j=1\) 表示第 \(j\) 个输入动 1 个单位,其他输入不动。前向模式会把这个方向传给每一行:每一行和 \(v\) 做内积,得到该输出沿这个方向的变化率。

在复合函数里,JVP 从输入层一路向前传播:先算第一个操作对方向的响应,再把结果当作下一个操作的输入方向。每一步都不需要构造完整雅可比矩阵,只做“局部雅可比乘向量”。所以 JVP 是前向模式的引擎,特别适合回答“单输入方向如何影响所有输出”这类问题。

JVP(v)=Jv=[∑k=1n∂f1∂xkvk⋮∑k=1n∂fm∂xkvk]\displaystyle\mathrm{JVP}(v) = J v = \begin{bmatrix}\displaystyle \sum_{k=1}^{n} \frac{\partial f_1}{\partial x_k} v_k \\\vdots \\\displaystyle \sum_{k=1}^{n} \frac{\partial f_m}{\partial x_k} v_k\end{bmatrix}
JVP 相当于问“输入沿着 \(v\) 方向动一点,所有输出会跟着怎么动?”它是方向导数的多维推广。
  1. 定义方向向量\(v\) 的每个分量 \(v_k\) 表示第 \(k\) 个输入施加的扰动幅度。
    v=[v1,v2,…,vn]T\displaystyle v = [v_1, v_2, \ldots, v_n]^T
  2. 每行做点积第 \(i\) 个输出方向变化等于雅可比第 \(i\) 行点乘 \(v\)。
    (Jv)i=∑k=1n∂fi∂xkvk\displaystyle (Jv)_i = \sum_{k=1}^{n} \frac{\partial f_i}{\partial x_k} v_k
  3. 结果是输出侧向量因为有 \(m\) 个输出,所以 \(Jv\) 是 \(m\) 维向量。
    Jv∈Rm\displaystyle Jv \in \mathbb{R}^m

VJP反向模式:拿着反馈单倒着分摊

总部这次发来一张反馈单:“珍珠奶茶利润要提高 1 元,请告诉我每种原料进货价应该分摊多少调整。”这不再是问“白糖涨价会怎样”,而是问“要让某个输出变化,每个输入该承担多少”。你手里的算法就是把账本转置,再把反馈向量从输出端往输入端乘回去,这就是向量雅可比积 VJP。

数学上,输出侧反馈用向量 \(u\) 表示,\(u_i\) 是第 \(i\) 个输出拿到的梯度信号。VJP 计算 \(J^T u\),得到一个 \(n\) 维向量,正好是每个输入的分摊值。它和 JVP 不是换汤不换药:JVP 固定输入方向,VJP 固定输出反馈,两者互为转置关系。

在神经网络中,损失 \(L\) 是一个标量,所以输出维度 \(m=1\)。反向传播只需要一次 VJP,就能把损失的反馈分摊给所有参数。这就像总部只关心“总利润”一个数,却可以一口气算出几十万个原料商各自该承担多少——这就是反向传播高效得可怕的原因。

VJP(u)=JTu=[∑i=1mui∂fi∂x1⋮∑i=1mui∂fi∂xn]∈Rn\displaystyle\mathrm{VJP}(u) = J^T u = \begin{bmatrix}\displaystyle \sum_{i=1}^{m} u_i \frac{\partial f_i}{\partial x_1} \\\vdots \\\displaystyle \sum_{i=1}^{m} u_i \frac{\partial f_i}{\partial x_n}\end{bmatrix} \in \mathbb{R}^{n}
VJP 是“把总反馈 \(u\) 按照账本的转置分摊给每个输入”。反向传播的每一步,本质上都是一次这样的分摊。
  1. 输出反馈向量\(u\) 的每个分量是某个输出拿到的上游梯度。
    u=[u1,u2,…,um]T\displaystyle u = [u_1, u_2, \ldots, u_m]^T
  2. 转置后与反馈点积第 \(j\) 个输入的分摊值,是所有输出对它的偏导与对应反馈的加权和。
    (JTu)j=∑i=1mui∂fi∂xj\displaystyle (J^T u)_j = \sum_{i=1}^{m} u_i \frac{\partial f_i}{\partial x_j}
  3. 结果回到输入空间因为输入有 \(n\) 个,所以 \(J^T u\) 是 \(n\) 维向量。
    JTu∈Rn\displaystyle J^T u \in \mathbb{R}^n

计算图实现:流水线上的账房

真实模型不是一张现成的雅可比表,而是一长串操作:煮茶、加糖、加奶、封装。每个操作都是一个“账房站”,它只知道自己局部的输入输出和局部变化率。把这些站串成一张有向图,就是计算图。

前向传播时,模型按原顺序执行每个操作,同时把中间结果记在小本子上;反向传播时,模型从最后一个站出发,把损失反馈逐站往回传。每个站收到上游反馈后,计算自己这个操作的局部 VJP,得到对本站输入的梯度,再传给上一个站。整个过程中,没有任何一站会张开一张完整的大雅可比矩阵。

这种“边算边记、倒着结算”的流水线让自动微分可以处理任意深度的网络。代价是反向传播必须保存前向的中间结果,相当于每个账房站都留了一沓底单,内存占用因此也会上升。

∂f∂x=JhkJhk−1⋯Jh1,xˉ=Jh1T⋯JhkTyˉ\displaystyle\frac{\partial f}{\partial x} = J_{h_k} J_{h_{k-1}} \cdots J_{h_1}, \qquad\bar{x} = J_{h_1}^T \cdots J_{h_k}^T \bar{y}
前向像流水线顺序生产,反向像把流水线倒着巡查:每个工作站只做局部 VJP,连起来就等价于全局链式法则。
  1. 链式法则连乘复合函数的雅可比矩阵是各个操作局部雅可比的连乘。
    ∂f∂x=JhkJhk−1⋯Jh1\displaystyle \frac{\partial f}{\partial x} = J_{h_k} J_{h_{k-1}} \cdots J_{h_1}
  2. 反向逐层传播反馈每个操作站把上游反馈变成对本站输入的反馈。
    hˉi−1=JhiT hˉi\displaystyle \bar{h}_{i-1} = J_{h_i}^T \, \bar{h}_i
  3. 一路传到输入所有 VJP 倒序执行完,就得到输入侧梯度。
    xˉ=Jh1T⋯JhkTyˉ\displaystyle \bar{x} = J_{h_1}^T \cdots J_{h_k}^T \bar{y}

框架API设计:收银台的点单方式

不同奶茶品牌给审计师提供的查询工具不一样:有的要他自己翻厚厚的报表,有的直接问“你想问的是原料涨价还是利润反馈”。深度学习框架的 API 也是这样:把 JVP 和 VJP 封装成不同的“点单入口”。

JAX 把两种运算都摆到台面上:jvp(f, primals, tangents) 让你显式传入输入点和方向向量;vjp(f, primals) 则返回一个函数,你把输出反馈 \(u\) 喂给它,它给你 VJP 结果。PyTorch 的 loss.backward() 把反向 VJP 藏在内部,用户只需要触发一次反向传播;自定义 torch.autograd.Function 时,开发者要在 backward 里返回局部 VJP。TensorFlow 的 GradientTape 则是先录完整段前向操作,退出上下文后再统一反推梯度。

PyTorch 和 TensorFlow 默认把 VJP 作为主力,是因为训练阶段损失是标量,反向模式一次就能拿到所有参数梯度。而 JAX 把 JVP 也开放出来,方便做方向导数、高阶微分和自定义自动微分变换。

jvp⁡(f,x,v)=(f(x), Jf(x)v),vjp⁡(f,x)(u)=(f(x), Jf(x)Tu)\displaystyle\operatorname{jvp}(f, x, v) = (f(x), \ J_f(x)v), \qquad\operatorname{vjp}(f, x)(u) = (f(x), \ J_f(x)^T u)
无论前端按钮长成什么样,后端真正执行的都是同一本账的两种读法:要么从输入方向往前读 JVP,要么从输出反馈往后读 VJP。
  1. JAX 的 jvp同时返回前向结果和输入方向 \(v\) 对应的输出方向。
    jvp⁡(f,x,v)=(f(x), Jf(x)v)\displaystyle \operatorname{jvp}(f, x, v) = (f(x), \ J_f(x)v)
  2. JAX 的 vjp先返回前向结果,再返回一个“反馈函数”。
    vjp⁡(f,x)=(f(x), u↦Jf(x)Tu)\displaystyle \operatorname{vjp}(f, x) = \left(f(x), \ u \mapsto J_f(x)^T u\right)
  3. 两种 API 的差别jvp 需要你给输入方向,vjp 需要你给输出反馈。
    v∈Rn,u∈Rm\displaystyle v \in \mathbb{R}^n, \quad u \in \mathbb{R}^m

模式选择与复杂度:哪种记账更划算

既然前向模式有 JVP,反向模式有 VJP,那到底用哪个?答案是:看账本的行数和列数。

想得到完整的雅可比矩阵 \(J\),前向模式需要分别用 \(n\) 个单位方向做 JVP:每个方向得到一列,凑齐 \(n\) 列。反向模式需要分别用 \(m\) 个单位反馈做 VJP:每个反馈得到一行,凑齐 \(m\) 行。因此,完整雅可比矩阵的成本对比非常简单。

但成本不只看次数,还要看内存。反向模式需要保存前向中间结果,像流水线上每个账房都要留底单;前向模式则不用保存太多中间状态,内存压力更小。所以实际选择要综合“输入输出维度、网络深度、内存预算”来做判断,而不是死记某一个结论。

完整雅可比={n 次 JVP前向模式m 次 VJP反向模式\displaystyle\text{完整雅可比} =\begin{cases}n \ \text{次 JVP} & \text{前向模式}\\m \ \text{次 VJP} & \text{反向模式}\end{cases}
如果输入数 \(n\) 远大于输出数 \(m\),反向模式更划算;如果输出数 \(m\) 远大于输入数 \(n\),前向模式更划算。训练神经网络时 \(m=1\),所以反向模式完胜。
  1. 前向覆盖列每个标准基方向对应一列,所以要 \(n\) 次 JVP。
    J=[Je1∣Je2∣⋯∣Jen]\displaystyle J = [J e_1 \mid J e_2 \mid \cdots \mid J e_n]
  2. 反向覆盖行每个标准反馈对应一行,所以要 \(m\) 次 VJP。
    J=[JTe1∣JTe2∣⋯∣JTem]T\displaystyle J = [J^T e_1 \mid J^T e_2 \mid \cdots \mid J^T e_m]^T
  3. 标量损失特例\(m=1\) 时一次 VJP 就能拿到全部梯度。
    JTu∈Rn,u=1∈R1\displaystyle J^T u \in \mathbb{R}^n, \quad u = 1 \in \mathbb{R}^1

AI 应用场景

神经网络训练:反向模式自动分账

阶段:模型训练 / 反向传播

解决的问题:传统手推梯度在深层网络里几乎不可能完成,数值差分既慢又易受舍入误差影响。反向模式用 VJP 把标量损失的反馈逐层分摊给每个参数,一次反向就能得到全部梯度。

具体案例:PyTorch 中调用 loss.backward() 后,自动微分引擎沿着计算图执行一系列局部 VJP:每一层的 backward 接收上游梯度,返回对权重和输入的梯度。框架设计者只需要保证每个算子提供正确的局部 VJP,就可以组合出任意的深度网络。

灵敏度分析:实时回答“涨价会怎样”

阶段:模型分析 / 输入扰动影响评估

解决的问题:风控或量化场景中,经常要评估某个输入特征的小幅扰动会如何影响多个预测结果。逐样本重算代价太高,而 JVP 可以直接沿指定输入方向传播变化率,相当于审计师沿着账本某一列快速扫过去。

具体案例:一家金融科技公司用神经网络同时预测多个风险指标,风控经理问“贷款利率上调 0.1 个百分点,违约率、回收率、客户流失率分别会变化多少?”用 JAX 的 jvp 传入扰动方向,一次前向即可拿到所有输出对该维度的敏感度,不用重新训练模型。

AI编译器:根据形状自动选择模式

阶段:编译优化 / 自动微分变换

解决的问题:AI 编译器在把模型编译到 GPU 或 NPU 时,需要决定用 JVP 还是 VJP 来实现梯度。传统手工实现一个算子一个样,维护成本高。基于形状分析:输入维度大、输出维度小时选反向;输入维度小、输出维度大时选前向。

具体案例:在 JAX 的 jvp 与 vjp 原语之上,编译器可以自动把 grad(f) 展开为 VJP 组合;对于某些特殊算子,也可以用 JVP 做更省内存的高阶微分。自定义算子只要声明局部 JVP/VJP,编译器就能统一调度。

Python 演示

python44 行
import math  def f(x, y):    """从 R^2 到 R^2 的示例映射:    out1 = x^2 + y    out2 = sin(x) * y    """    return [x * x + y, math.sin(x) * y]  def jacobian(x, y):    """返回示例映射在 (x, y) 处的 2x2 雅可比矩阵。"""    return [        [2.0 * x, 1.0],        [math.cos(x) * y, math.sin(x)]    ]  def jvp(x, y, vx, vy):    """前向模式:J * v。"""    J = jacobian(x, y)    return [        J[0][0] * vx + J[0][1] * vy,        J[1][0] * vx + J[1][1] * vy    ]  def vjp(x, y, ux, uy):    """反向模式:J^T * u。"""    J = jacobian(x, y)    return [        J[0][0] * ux + J[1][0] * uy,        J[0][1] * ux + J[1][1] * uy    ]  if __name__ == "__main__":    x, y = 0.5, 1.5    vx, vy = 1.0, 0.0    print("f =", f(x, y))    print("J =", jacobian(x, y))    print("JVP =", jvp(x, y, vx, vy))    print("VJP =", vjp(x, y, 1.0, 0.0))
目的:用一个小型 \(2 \times 2\) 雅可比矩阵演示 JVP 与 VJP 的完整计算过程,直观对照“前向传播方向”和“反向传播反馈”。
  1. def f(x, y):定义一个双输入双输出的示例函数,相当于奶茶店的“成本转利润”规则。
  2. def jacobian(x, y):手工构造 \(2 \times 2\) 账本,第一行是 \(f_1\) 对 \(x,y\) 的偏导,第二行是 \(f_2\) 对 \(x,y\) 的偏导。
  3. J[0][0] * vx + J[0][1] * vy这就是“雅可比第一行点乘方向向量”,对应 JVP 的第一个输出分量。
  4. J[0][0] * ux + J[1][0] * uy这是 \(J^T u\) 的第一个分量,把所有输出反馈汇总到输入 \(x\) 上,对应 VJP。
  5. print("JVP =", ...)固定输入方向 \((1,0)\),等价于只让 \(x\) 变化,观察两个输出分别怎么变。
  6. print("VJP =", ...)固定输出反馈 \((1,0)\),等价于只给第一个输出 1 的梯度,看每个输入各得到多少分摊。

常见误区

误区JVP 是 \(Jv\),VJP 是 \(J^Tu\),不都是“矩阵乘向量”吗?转置一下就好,理解成一个东西完全没问题。

正解两者虽然都是矩阵乘向量,但语义完全不同。JVP 从输入侧给定“扰动方向”,回答的是“一个输入方向如何影响所有输出”;VJP 从输出侧给定“反馈梯度”,回答的是“一个输出反馈如何分摊给所有输入”。就像账本里“问白糖涨价”和“问利润反馈”是两类截然不同的问题,方向相反、信息流向相反。

为何常见:矩阵公式长得太像,初学者容易只盯着乘法符号,忽略自动微分里“从哪一侧进入”才决定前向还是反向。

误区每次 `backward()` 都要先算出完整雅可比矩阵,再拿去乘梯度向量,所以显存爆炸是因为雅可比矩阵太大。

正解框架不会显式构造完整雅可比矩阵。计算图的每个操作只保存局部雅可比,或者更准确地说是局部 VJP 规则;反向传播沿途做矩阵向量乘,而不是把整张大表展开。真正让显存上升的往往是前向保存的中间激活值,而不是一张巨型 \(J\)。

为何常见:数学推导里 \(J\) 作为整体出现,很容易让人以为软件也把整块矩阵建出来。

误区反向传播是“倒着算”,所以 VJP 应该是在对雅可比矩阵求逆,把输出梯度变回输入梯度。

正解VJP 用的是转置,不是逆。转置是把反馈按列汇总回输入空间;求逆则要求方阵且可逆,绝大多数网络映射根本不存在可逆雅可比。倒着读账本不等于把账本翻个底朝天,而是把行和列交换后做加权汇总。

为何常见:“反向”一词太有迷惑性,让人联想到逆运算。

误区反向传播在深度学习中无敌,所以任何情况下只要输入维度大于输出维度,直接选反向模式就对了。

正解反向模式确实适合 \(m=1\) 的标量损失,但代价是要保存大量中间状态。如果输出维度也很大,反向模式的完整雅可比需要 \(m\) 次 VJP,可能并不划算。选择模式要综合维度比和内存预算,而不是只看“输入多不多”。

为何常见:大家最常接触的是损失函数 \(m=1\),容易把反向模式的优点过度推广到所有场景。

误区JVP 是雅可比乘向量,梯度向量也是向量,所以让 \(v=(1,1,\dots,1)\) 就能一次得到所有输入参数的梯度。

正解一次 JVP 只能得到一个组合方向上的输出变化率,而不是每个输入各自的梯度。想让每个输入分别拿到自己的梯度,必须用 \(n\) 个单位方向逐一做 JVP。反向模式一次 VJP 能同时给出所有输入梯度,这才是它在训练中胜出的关键。

为何常见:把“沿某个方向的导数”误解为“对每个变量的偏导”,两者在数学上是不同的对象。

面试题

Q1. 在自动微分中,JVP 是哪种模式的核心计算?

单选题 · easy

  • A.前向模式← 正确答案
  • B.反向模式
  • C.正则化模式
  • D.混合精度模式

答案:A。JVP 把输入方向 \(v\) 从矩阵左边乘进去,对应信息从输入传向输出的前向模式;反向模式的核心是 VJP。

Q2. 反向传播的每一步都在执行哪种基本操作?

单选题 · easy

  • A.雅可比矩阵求逆
  • B.向量雅可比积VJP← 正确答案
  • C.雅可比向量积JVP
  • D.标量除法

答案:B。反向传播把输出侧反馈沿计算图传回输入侧,每一步都是局部 VJP,也就是 \(J^Tu\) 或它的复合形式。

Q3. 若 \(f\) 把 \(\mathbb{R}^n\) 映射到 \(\mathbb{R}^m\),雅可比矩阵的形状是?

单选题 · easy

  • A.\(n \times n\)
  • B.\(n \times m\)
  • C.\(m \times n\)← 正确答案
  • D.\(m \times m\)

答案:C。输出数 \(m\) 决定行数,输入数 \(n\) 决定列数,所以形状是 \(m \times n\)。

Q4. 关于 JVP 与 VJP 的说法,正确的有?

多选题 · mid

  • A.JVP 沿输入方向传播变化率← 正确答案
  • B.VJP 沿输出反馈传播梯度← 正确答案
  • C.两者都需要显式求逆雅可比
  • D.JVP 适合输入维度很大的情形

答案:AB。JVP 是给定输入扰动方向后看所有输出如何变化,VJP 是给定输出反馈后看每个输入如何分摊。两者都用雅可比矩阵乘向量,但不用求逆;输入维度很大时一次 JVP 只能覆盖一个方向,往往不划算。

Q5. 要得到标量损失 \(l=f(x)\) 对所有 \(n\) 个输入的梯度,反向模式最少需要几次 VJP?

单选题 · mid

  • A.1 次← 正确答案
  • B.\(n\) 次
  • C.\(m\) 次
  • D.\(n \times m\) 次

答案:A。标量损失对应 \(m=1\),反向模式一次 VJP 就能把损失反馈分摊给全部 \(n\) 个输入,这正是反向传播高效的根本原因。

Q6. 对于输入维度 \(n\) 很大、输出维度 \(m=1\) 的函数,哪种方式通常更划算?

单选题 · mid

  • A.前向模式
  • B.反向模式← 正确答案
  • C.两种完全等价
  • D.必须用数值差分

答案:B。完整雅可比时前向需要 \(n\) 次 JVP,反向只需要 \(m=1\) 次 VJP,因此反向模式远更划算。

Q7. 关于前向模式 JVP,下列说法正确的有?

多选题 · hard

  • A.适合输入少输出多的情况← 正确答案
  • B.一次JVP开销接近一次前向计算← 正确答案
  • C.输出侧需要给梯度反馈
  • D.它是方向导数的推广← 正确答案

答案:ABD。前向模式一次 JVP 对应一个输入方向,需要 \(n\) 次才能覆盖完整雅可比,所以适合输入少、输出多的场景;它不需要输出侧反馈,只是沿输入方向传播变化率,本质是方向导数的多维推广。

Q8. PyTorch 中调用 loss.backward(),自动微分引擎默认执行的核心操作是?

单选题 · mid

  • A.前向 JVP
  • B.反向 VJP← 正确答案
  • C.雅可比显式求逆
  • D.符号积分

答案:B。backward() 从标量损失出发,沿计算图反向传播梯度,每个节点执行局部 VJP,最终得到所有参数的梯度。

Q9. 对比 JAX 的 jvp 和 vjp 接口,正确的说法是?

多选题 · hard

  • A.jvp 需要传入 tangent 方向← 正确答案
  • B.vjp 返回计算输出梯度的函数← 正确答案
  • C.两者返回值完全一样
  • D.jvp 沿反向传播路径计算

答案:AB。jvp 需要显式给出输入方向向量;vjp 先返回前向值,再返回一个接收输出反馈并给出梯度的函数。两者返回结构不同,jvp 走前向模式,而不是反向传播路径。

Q10. 已知 \(J = \begin{bmatrix} 2 & 0 \\ 1 & 1 \end{bmatrix}\),\(v = \begin{bmatrix} 1 \\ 2 \end{bmatrix}\),则 JVP 的结果是?

单选题 · mid

  • A.\([2, 2]\)
  • B.\([2, 3]\)← 正确答案
  • C.\([4, 3]\)
  • D.\([0, 2]\)

答案:B。JVP 是 \(Jv\)。第一行 \(2\times1+0\times2=2\),第二行 \(1\times1+1\times2=3\),所以结果是 \([2,3]\)。