想象你推着一辆购物车去超市采购。每拿起一件商品,收银小票上就会自动多一行:这件商品多少钱,它对总账单有多“敏感”——如果它涨价1元,总账单会跟着涨多少。这个“敏感度”,在数学里就叫导数。前向模式(Forward Mode)自动微分,就是让这辆购物车从超市入口出发,每经过一个货架、每做一次计算,都同步更新小票上的“敏感度”信息,一路推到收银台结账。等结账时,你手里的小票已经写满了所有商品对总价的敏感度,不需要回头再算。 为什么这件事值得搞懂?因为 PyTorch、JAX 这些深度学习框架,底层全靠自动微分在算梯度。理解前向模式,你就看懂了它们在什么场景下选哪条“购物路线”,也就能在写 AI 编译器、做模型优化时,准确判断该用哪种求导方式。这篇文章就用这辆“带小票的购物车”,带你逛完前向模式的全过程。
对 AI 从业者而言,掌握前向模式意味着你能看懂框架底层的雅可比计算逻辑,在编译器优化、二阶优化器、仿真敏感度分析等场景中做出正确的技术选型,而不是只会调 loss.backward()。
可视化演示
动态演示自动微分中的前向模式原理与实的核心机制,通过输入/处理/存储/输出四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
自动微分中的前向模式原理与实:对偶数原 对偶数原理与运算规则 前向累积计算机制 核心机制可视化 自动微分中的前向模式原理与实整体架构与关键组件
自动微分基础
在超市里,想知道“西瓜涨价 1 元,购物车总账单会涨多少”,老一辈有几种土办法。第一种叫数值微分——真的去货架上把西瓜价格标签撕下来改 1 元,推车重新结一次账,看总价差多少。改一次只能测一个商品,货架上有一百种商品就得结一百次账,而且因为收银台只显示整数分,测出来的敏感度还带误差。第二种叫符号微分——把整个购物清单当成一个超长数学表达式,用纸笔法则一步步展开求导。如果清单里写着“满 100 减 20”、“第二件半价”这种嵌套规则,展开后的式子会膨胀得比超市还大,根本算不完。
自动微分(Automatic Differentiation,AD)的思路完全不同:它先把购物流程拆成一连串细小的“货架操作”——拿起商品、加入购物车、应用折扣、计算小计……这就像把整个购物过程画成一张计算图,每个节点是一次基本运算(加法、乘法、三角函数)。然后,AD 让每个数值都随身携带一张“小票”记录敏感度,每经过一个货架操作,小票就按照该操作的规则自动更新,全程不需要猜测、也不需要展开巨大公式。
这张计算图是整个自动微分的地基。无论是前向模式还是后面要讲的反向模式,都是在这张图上“走路”的规则:从哪个节点出发、往哪个方向走、沿路怎么更新小票。理解自动微分,本质上就是理解“把复杂函数拆成基本运算图,再让敏感度沿图流动”这件事。
回到超市的比喻:自动微分就是给每个商品挂上小票,每过一个货架按规则更新敏感度,不需要反复结账,也不需要把清单展开成天书。这条思路干净利落,为后面两种模式铺好了路。
x 传到 y,再传到 z,两段敏感度相乘就得到总敏感度。- 左侧整体敏感度表示输出
z对输入x最终有多敏感,也就是我们想求的导数。 - 第一段传送带
z对中间量y的敏感度,记录“y动一点,z动多少”。 - 第二段传送带
y对源头x的敏感度,记录“x动一点,y动多少”。 - 两段相乘像两段传送带首尾相接,把 "
x → y → z" 两段变化率乘起来,得到整条链的敏感度。
对偶数原理
现在正式介绍小票长什么样。在自动微分的前向模式里,每个数不再是孤零零的一个“价格”,而是一个携带影子信息的对偶数。比如 3 + 4ε,可以理解成“这件商品标价 3 元,而且它对某个基准量(比如预算)的敏感度是 4”。这里 ε 是一个特殊的符号,它不是数字,而是一个“影子标记”,数学上规定它满足一个神奇的规则:ε² = 0。
这个“ε² = 0“是整辆购物车能正常工作的核心机关。它意味着:影子信息只参与第一层传播,一旦它和另一个影子相乘,就会像”假一赔十的优惠券叠优惠券“一样自动失效归零,绝不会引发影子套影子的混乱。正是这个规则,保证了小票上的敏感度永远是简洁的”价格 + 一个影子”,不会无限膨胀。
对偶数参与运算时,规则极其简单。两个对偶数相加,价格和影子分别相加;两个对偶数相乘,价格部分按普通乘法算,影子部分则按“交叉相乘再相加”来处理。别看规则简单,它背后有泰勒展开撑腰——任何光滑函数在微小扰动下的变化,首阶项恰好就是导数乘以扰动,高阶项因为含 ε² 全部归零。
所以,每一件“商品”(数值)都自带价格和影子两栏。购物车每经过一次运算,小票就按“价格归价格、影子按法则更新”的方式刷新。这就是前向模式能在一次遍历中就得到精确导数的底层秘诀。
ε² = 0 负责把乘积中的影子与影子相乘项直接扔掉。- 常数部分直接相乘两个对偶数的“价格”部分
a和c按普通乘法得到ac,这是结果的主体。 - 交叉项出场影子部分来自“左价格 × 右影子”与“左影子 × 右价格”,即
a·d·ε和b·c·ε。 - 影子与影子相乘归零
bε × dε = bd·ε² = 0,所以不会出现 ε² 项,小票保持简洁。 - 合并影子系数把两个交叉项合到一起,得到最终影子系数
ad + bc。
前向模式推导
现在推着购物车上路。假设我们要算函数 f(x, y) = x·y + y 在点 x=3, y=4 处对 x 的偏导数。前向模式的做法是:在入口处给 x 的小票上写一个初始影子 ε=1(种子),意思是“我们现在只观察 x 动 1 单位时,所有量跟着动多少”;给 y 写影子 ε=0,表示“这场观察里 y 保持不变”。
然后购物车沿计算图前进。第一步来到乘法货架 t1 = x·y:价格 3×4=12,影子按乘法法则 x 的影子×y 的价格 + x 的价格×y 的影子,即 1×4 + 3×0 = 4。第二步来到加法货架 t2 = t1 + y:价格 12+4=16,影子 4+0=4。最后到达收银台,输出 f = 16,小票上的影子也是 4——这就是 ∂f/∂x 的精确值。
整个过程购物车只从入口推到出口一次,所有中间值的影子都随着计算同步更新,没有任何近似。为什么能用对偶数一路算到底?因为把 x = 3 + ε 代入任意光滑函数做泰勒展开后,系数恰好就是导数:
前向模式的特点由此凸显:它给每个中间值都维护一个影子导数,像购物车推过每个货架时,收银员顺手在小票上盖一个章。等结账时,小票上已经写满了这一路所有“如果 x 动 1 单位,每个中间量会动多少”的信息。
- 泰勒展开对
f在x附近做展开,把h = ε当作微小扰动代入。 - 高阶项清零因为
ε² = 0,展开式中第二项之后的所有项全部消失。 - 只剩首阶项等式右边只剩下
f(x)加上f'(x)ε,影子系数就是所求导数。
反向模式对比
有前向就有反向,它是另一条完全相反的购物路线。前向模式是“从入口出发,推着车往前走,沿路收集敏感度”;反向模式则是“从收银台的总账单出发,倒着走回每个货架,问每一件商品:你为总账单贡献了多少”。深度学习里天天听到的“反向传播”,就是反向模式在损失函数这个“总账单”上的特例。
这两个模式拿到的信息不太一样。在一张有 n 个输入、m 个输出的计算图里,前向模式每走一次,得到的是“一个输入对所有输出的敏感度”——对应雅可比矩阵的一行;反向模式每走一次,得到的是“所有输入对一个输出的敏感度”——对应雅可比矩阵的一列。打个比方:前向是“揪住西瓜问它对整辆购物车每件商品价格的影响”,反向是“揪住购物车总额,问每件商品各贡献了多少”。
当中间节点有多条路径汇聚时,反向模式需要把各路贡献累加,这就是求和形式的链式法则。比如损失 L 依赖中间量 v₁, v₂, …,而每个 vᵢ 又依赖 x,那么 L 对 x 的敏感度是每条路径敏感度的总和:
对比两种购物路线,前向模式适合“输入少、输出多”——就像你只问 1 个问题(西瓜涨价多少),却想知道它对 1000 件商品的影响;反向模式适合“输入多、输出少”——大多数神经网络训练正是这种情况:几百万个参数(输入),最终只汇聚成一个损失值(输出)。两条路线没有谁绝对更好,只有谁更适合当前的购物清单。
- 求和范围遍历所有直接依赖
x的中间节点v_k,因为x可能通过多条路影响最终输出。 - 每条路径的贡献对每个中间节点
v_k,把“输出对它的敏感度”和“它对x的敏感度”相乘。 - 汇总成总敏感度把每条路径的贡献加在一起,得到
L对x的总导数。
复杂度分析
既然有两条购物路线,怎么选?关键看两个数字:超市入口有几个商品(输入维度 n),收银台出口要打印几份小票(输出维度 m)。前向模式从入口出发推一次车,途中对每个中间量都更新影子,代价大约和输入数量 n 成正比——因为每多关心一个输入,就要多设一种种子;反向模式从出口倒推一次,代价大约和输出数量 m 成正比——因为每多关心一个输出,就要多跑一轮反向。
这个规律直接给了我们一个“选路判据”:当 n 远小于 m 时,前向模式更划算;当 m 远小于 n 时,反向模式更划算。举两个极端例子:神经网络训练是典型的上百万参数对一个损失值(n 巨大、m=1),所以必须用反向;而一个物理仿真器输入只有 10 个物理参数,输出却是 10 万个时刻的轨迹状态(n=10、m=100000),这时前向模式一次遍历就能把整张雅可比矩阵扫完,反向反而要跑 10 万轮。
除了维度,还有存储的权衡。反向模式通常需要把计算图的中间结果缓存下来,像超市保存每一笔交易记录,内存开销大;前向模式则像边走边盖章,中间值用完就扔,内存友好但计算次数受输入维度牵制。编译器在做自动微分变换时,正是根据这些权衡自动生成最优的“购物路线”。
- n 的意义
n是输入维度,对应超市里的商品种类数,前向模式每多一个输入就得额外推一轮车。 - m 的意义
m是输出维度,对应收银台要打印的小票份数,反向模式每多一个输出就得额外倒推一轮。 - 比较判据比较
n与m的大小关系,较小的那一边决定哪种模式总代价更低。
工程实现
这么聪明的小票机制,框架是怎么实现的?答案是“算子重载”——让每个数值都变成一个对偶数对象,把 +、× 这些运算符重新定义成会同步更新影子的版本。这就像超市给所有商品都贴上了电子标签,收银台的扫描枪一碰到标签,价格和敏感度自动录入,完全不需要人工干预。我们在 Python 里实现一个 Dual 类覆盖 __add__、__mul__,就等于给收银台换上了智能扫描枪。
在实际工业级框架里,JAX 提供了 jvp(Jacobian-vector product)接口来实现前向模式。给定函数 f、初始输入 primals 和种子方向 tangents,它返回“函数值 + 沿该方向的导数”。PyTorch 也在 torch.autograd.forward_ad 模块里提供了前向模式支持,方便处理反向模式吃力的场景。这些接口的数学本质,都是计算雅可比矩阵与种子向量的乘积:
更高级的玩法是嵌套微分:既然对偶数的影子本身也是数,那影子也可以再带一个影子,从而用“两层前向”求二阶导数。JAX 正是靠这种嵌套能力实现 jvp 与 grad 的灵活组合,让编译器在自动微分变换时像搭积木一样自由选择路线。
J 是“全景敏感度报表”(雅可比矩阵),v 是本次观察的种子方向,两者做乘法后,得到每个输出沿该方向的影子导数。- 雅可比矩阵
J_f(x)是一个 m×n 矩阵,第i行第j列是第i个输出对第j个输入的偏导。 - 种子向量
v是用户指定的方向,比如要观察第一个输入,就取v = (1, 0, …, 0)。 - 逐行点积结果向量的第
i个分量,是雅可比第i行与v的点积,即f_i沿v方向的方向导数。
应用场景
前向模式在真实 AI 工程里绝不是摆设,它专治“输入少输出多”的病人。第一个典型场景是超参数敏感性分析:比如一个语音合成模型只有 4 个全局温度参数(输入 n=4),但要在 1000 个频点输出上观察音色变化(输出 m=1000)。用前向模式,给每个温度参数设一次种子,4 轮就能拿到完整的 1000×4 雅可比行,画出一张“哪个参数对哪个频点影响最大”的热力图。换成数值微分,得跑 4000 次推理,差距立竿见影。
第二个场景藏在机器人仿真的 ODE 求解器里。强化学习训练时,仿真器输入是机械臂的关节质量、摩擦系数等少量物理参数,输出却是几十个时刻的完整轨迹状态。用前向模式顺着仿真时间步一路更新影子,就能一次性得到“参数扰动对整条轨迹的影响”,这对系统辨识和策略梯度估计都极其宝贵。
第三个场景是二阶优化中的海森对角估计。训练大规模神经网络时,完整海森矩阵太大存不下,但我们可以用前向模式计算“梯度的 JVP”来近似海森对角,给 AdaHessian 这类二阶优化器提供预条件。这就像购物车不仅记录了总账单的敏感度,还能告诉收银员“小票上哪一行最容易受手抖影响”,帮助优化器在参数空间里走得更稳。在这些场景里,前向模式不是反向模式的替补,而是那条更短、更聪明的购物路线。
实践误区
前向模式概念不复杂,但实操时大家常在几个坑里打转。第一个坑是维度判断反了:听到“反向模式适合训练”就以为前向没用,或者反过来在输入百万级、输出 1 的模型里强行用前向模式,结果慢到怀疑人生。判据其实就一句话——看输入和输出哪个少,少的决定选哪条路。
第二个坑是误以为前向模式有精度问题。这是把它和数值微分搞混了。数值微分需要取一个很小的 h 做近似,必然有截断误差;前向模式用的是对偶数代数,每一步都是精确的符号运算,误差只来自浮点数的机器精度,和反向模式完全同级别。你不需要“担心它不准”,只需要担心“选没选对”。
第三个坑是不知道框架原生支持。很多人以为前向模式要自己写算子重载,其实 PyTorch 的 torch.autograd.forward_ad、JAX 的 jvp 都是现成的轮子。自己手写对偶类练手很好,但上了生产环境要优先用框架接口,它们的算子覆盖更全、性能调优更好。顺便一提,手写对偶数时要小心把“梯度方向”搞反——小票上的影子是 输出如何随输入变化,不是 输入如何随输出变化,方向反了,整个优化就奔着反方向去了。
AI 应用场景
语音合成超参数敏感度分析
阶段:模型调试与可解释性
解决的问题:传统做法为了分析 4 个温度参数对 1000 个频点输出的影响,需要跑 4000 次前向推理再算差分,既慢又有数值误差。前向模式给每个参数设一个种子,4 次前向遍历就能拿到完整的雅可比行。
具体案例:某语音团队想调整“语速、音调、音量、呼吸感”四个全局风格参数,观察对 1000 维声码器输出的影响。用 JAX 的 jvp 实现前向模式,一次函数调用同时返回音频值和敏感度矩阵,据此画出的热力图直接指导了风格插值算法的设计。
机器人仿真轨迹对物理参数的敏感性
阶段:仿真训练与系统辨识
解决的问题:仿真器的物理参数(质量、摩擦、刚度)数量少,但输出是整个时间轴上几十步的状态向量,属于典型的“小输入大输出”。反向模式要为每个输出时刻单独倒推,代价太大。
具体案例:某四足机器人团队用 PyTorch 的 forward_ad 求“关节摩擦系数对 30 步足底力轨迹”的敏感度,用于校准仿真器和真机之间的动力学差异。前向模式顺着仿真时间步推进,影子导数一路同步更新,一次仿真就能完成全部参数分析。
二阶优化器的海森对角预条件估计
阶段:训练加速与优化器设计
解决的问题:大规模模型想用二阶信息加速收敛,但完整海森矩阵的平方级存储不可行。研究者用前向模式的 JVP 来估计海森对角,作为优化器的预条件。
具体案例:在 JAX 中实现 AdaHessian 风格的优化器,利用 jvp(grad(f), primals, tangents) 得到对角海森近似值,把 BERT 微调的收敛步数减少了约 30%。前向模式在这里不是“求梯度”,而是给梯度再算一次方向导数,完美发挥嵌套微分的优势。
Python 演示
class Dual: """对偶数:value 是主体值,deriv 是影子导数""" def __init__(self, value, deriv=0.0): self.value = value self.deriv = deriv def __add__(self, other): if isinstance(other, Dual): return Dual(self.value + other.value, self.deriv + other.deriv) return Dual(self.value + other, self.deriv) def __radd__(self, other): return Dual(other + self.value, self.deriv) def __mul__(self, other): if isinstance(other, Dual): return Dual(self.value * other.value, self.deriv * other.value + self.value * other.deriv) return Dual(self.value * other, self.deriv * other) def __rmul__(self, other): return Dual(other * self.value, other * self.deriv) def __sub__(self, other): if isinstance(other, Dual): return Dual(self.value - other.value, self.deriv - other.deriv) return Dual(self.value - other, self.deriv) def __truediv__(self, other): if isinstance(other, Dual): return Dual(self.value / other.value, (self.deriv * other.value - self.value * other.deriv) / (other.value * other.value)) return Dual(self.value / other, self.deriv / other) def __repr__(self): return "Dual(value=%s, deriv=%s)" % (self.value, self.deriv) def apply(f, x): """把普通函数 map 到 Dual 对象上""" return f(x) def f(x, y): return x * y + y def g(x): return [x * x, 2.0 * x + 1.0] def demo(): # 对 x 求偏导:给 x 的种子为 1,y 的种子为 0 x = Dual(3.0, 1.0) y = Dual(4.0, 0.0) out = f(x, y) print("f(3,4) =", out.value) print("df/dx =", out.deriv) # 对 y 求偏导:交换种子 x2 = Dual(3.0, 0.0) y2 = Dual(4.0, 1.0) out2 = f(x2, y2) print("df/dy =", out2.deriv) # 雅可比行:多个输出对同一输入的导数 xin = Dual(2.0, 1.0) res = g(xin) print("g(2) =", [r.value for r in res]) print("dg/dx =", [r.deriv for r in res]) if __name__ == "__main__": demo()
class Dual:定义对偶数对象,同时保存主体值和影子导数,相当于给普通数贴上一张小票。def __init__(self, value, deriv=0.0):构造器,deriv默认 0 表示“不敏感”,需要在入口显式设置种子。def __add__(self, other):重载加法运算符,让x + y自动按照“值相加、影子相加”的法则更新。def __mul__(self, other):重载乘法运算符,实现ad + bc交叉法则,这是前向模式的引擎。def __radd__(self, other):处理3 + dual这种数字在左边的加法,确保普通数与对偶数混合运算不出错。deriv * other.value + self.value * other.deriv乘法影子法则的代码形态:左影子×右值 + 左值×右影子。return Dual(self.value / other.value, ...)除法法则同样支持,满足基本算子覆盖需求。def demo():演示入口,分别计算df/dx、df/dy和函数g的雅可比行。Dual(3.0, 1.0)给x设置种子 1,表示“本次观察只关心 x 的变化”。print("df/dx =", out.deriv)前向模式的核心便利:算完函数值,导数已经在结果对象的deriv字段里。
常见误区
误区我听说“反向传播适合神经网络”,所以前向模式就是给简单函数用的玩具,实际项目用不上。
正解前向模式的适用条件不是“简单”,而是“输入少输出多”。比如只有 5 个物理参数、却要观察 10 万个轨迹点的仿真场景,前向模式比反向模式快几个数量级。选路标准永远是 n 和 m 的比较,而不是函数复杂程度。
为何常见:深度学习课程几乎只讲反向传播,大家把“训练神经网络”当成了自动微分唯一的应用场景,忽略了其他大量“小输入大输出”的工程问题。
误区前向模式本质上也是用差分近似吧?比如那个 ε 是不是相当于很小的 h,会带来截断误差?
正解对偶数里的 ε 不是一个小数值,而是一个抽象符号,满足 ε²=0 的代数规则。所有运算都是精确的符号推导,和数值微分的“取 h=0.0001 估算斜率”有本质区别。前向模式的结果只有浮点数的机器精度误差,与反向模式完全同级。
为何常见:人们容易把“用 ε 表示扰动”和“数值微分取小步长 h”混为一谈,因为表面形式很像,但一个来自代数系统、一个来自近似计算。
误区要用前向模式只能自己手写对偶类,太麻烦,所以还是算了。
正解PyTorch 从 1.7 起提供了 torch.autograd.forward_ad,JAX 更是把 jvp 作为一等公民接口。生产环境中应该优先使用这些经过算子覆盖和性能优化的内置实现。手写 Dual 类适用于教学和理解,但全量算子重载的工程量大且易出错。
为何常见:很多教材用“手动实现”来讲原理,读者误以为框架没这功能,或者以为框架的自动微分只有 backward() 这一条路。
误区前向模式算出来的影子导数应该是“参数如何被损失影响”,所以优化时直接用它更新参数就行。
正解导数永远是“输出如何随输入变化”,即 ∂输出/∂输入。优化参数时要沿着负梯度方向走,影子导数给出的是“输入增大时输出增大多少”,要更新参数减小损失,得用 参数 -= 学习率 * 影子导数。方向搞反,训练会直接发散。
为何常见:初学者在写优化循环时容易把“梯度值”和“更新方向”两件事合并,忽略了梯度下降需要负号这个基本事实。
误区反向模式只需要一次前向一次反向,时间上总是最优,所以内存问题无所谓。
正解反向模式要保存前向过程的全部中间激活值,内存开销随网络深度线性增长。前向模式恰恰相反,因为影子跟着值走,中间结果用完即弃,内存占用很小。在处理超长序列或深度很大的模型时,前向模式的内存优势可能是选它的决定性理由。
为何常见:大家只比较了时间复杂度 n vs m,忽略了反向模式“用存储换时间”的代价,对训练时的显存瓶颈认识不足。
面试题
Q1. 对偶数乘法 (3 + 2ε) × (4 + 0ε) 的结果中,ε 的系数是多少?
- A.
a·d = 3×0 = 0 - B.
a·d + b·c = 3×0 + 2×4 = 8← 正确答案 - C.
a·c = 3×4 = 12 - D.
b·d·ε² = 2×0 = 0
答案:B。对偶数的影子乘法法则是“交叉加”:ad + bc,即 3×0 + 2×4 = 8。A 只算了一半交叉项,C 是主体值的乘积,D 是 ε² 项按规则归零的部分。
Q2. 前向模式自动微分一次求值,能获得雅可比矩阵的哪一部分?
- A.完整雅可比矩阵
- B.一个 m×n 的稠密矩阵
- C.雅可比矩阵的某一列
- D.雅可比矩阵的某一行← 正确答案
答案:D。前向模式给某个输入设种子,等价于计算 J·v,即雅可比矩阵与标准基向量的乘积,结果是对应的一行。反向模式给某个输出设种子,得到的才是雅可比的一列。A、B 需要多轮求值才能拼出。
Q3. 一个深度学习模型的输入是 10000 维特征,输出是 1 维损失值,训练时应该优先选择哪种求导模式?
- A.前向模式
- B.反向模式← 正确答案
- C.符号微分
- D.数值微分
答案:B。n=10000、m=1,属于“输入多输出少”,反向模式每轮只需一次倒推即可得到损失对所有输入的梯度,而前向模式要为 10000 个输入各设种子跑 10000 轮。复杂度判据 n > m 时选反向。
Q4. 在对偶数体系中,ε² 的值等于多少?
- A.ε
- B.1
- C.0← 正确答案
- D.-1
答案:C。ε² = 0 是对偶数定义的核心规则,它保证影子信息在一阶传播后自动归零,使展开式不会无限膨胀。这个规则是整个前向模式自动微分不产生表达式爆炸的基础。
Q5. 下列关于自动微分相对数值微分和符号微分的优势,哪些是正确的?
- A.结果精确,没有手撕差分步长的截断误差← 正确答案
- B.避免了符号微分中表达式急剧膨胀的问题← 正确答案
- C.计算过程中完全不占用任何额外内存
- D.不需要存储计算图的中间结果
答案:AB。自动微分基于对偶数代数给出精确导数,且只沿着计算图做局部传播,不会产生符号微分的爆炸式表达式。C 和 D 都不绝对成立——反向模式必须缓存中间激活值,内存开销不可忽略。
Q6. 在 JAX 的 jvp(f, primals, tangents) 接口中,tangents 参数对应前向模式里的什么概念?
- A.函数输出值
- B.种子方向向量← 正确答案
- C.模型的权重张量
- D.优化器的学习率
答案:B。tangents 正是前向模式里的种子方向,即对偶数中的 ε 系数向量,指定了“本次观察沿哪个输入方向求导”。前向模式的核心就是给每个输入设置种子并同步更新影子导数,与 jvp 的语义完全对应。
Q7. 哪些场景下使用前向模式会比反向模式更合适?
- A.输入只有几个参数但输出维度很大的仿真分析← 正确答案
- B.输入是百万维特征、输出是一维损失的分类训练
- C.用 JVP 近似海森矩阵对角线的二阶优化器← 正确答案
- D.计算标量损失函数对所有网络参数的梯度
答案:AC。A 是典型的“输入少输出多”,前向模式一次遍历拿一行雅可比,效率极高。C 中 JVP 本身就是前向模式的核心原语。B 和 D 都是 n 巨大、m=1 的场景,反向模式才占优。
Q8. 对偶数求导的理论依据是以下哪种数学展开?
- A.洛朗级数展开
- B.泰勒级数展开← 正确答案
- C.傅里叶级数展开
- D.柯西主值展开
答案:B。f(x + ε) = f(x) + f'(x)ε 正是将 f 在 x 处做泰勒展开后,利用 ε²=0 消去高阶项得到的。洛朗级数涉及负幂项、傅里叶级数涉及谐波正交基,都与对偶数无关。
Q9. 用前向模式计算 h(x, y) = sin(x) × y 在 x = π/2, y = 2 处对 x 的偏导数,结果是多少?
- A.0← 正确答案
- B.2
- C.-2
- D.1
答案:A。对 x 求偏导时把 y 视作常数:∂h/∂x = cos(x) × y。代入 x = π/2 得 cos(π/2) = 0,再乘 y = 2 仍为 0。对偶数方法会给出同样的精确结果,不会有数值误差。
Q10. 关于前向模式与嵌套自动微分的关系,下列哪些说法是正确的?
- A.用两层前向模式可以求出函数的二阶导数← 正确答案
- B.对偶数的影子导数本身可以再做一次对偶数← 正确答案
- C.两层嵌套前向模式的复杂度必然呈指数爆炸
- D.JAX 支持前向与反向模式混合嵌套来求高阶导← 正确答案
答案:ABD。对偶数的影子部分也是普通数,自然可以再挂一层影子,因此两层前向模式能得到二阶导,JAX 正是用 jvp 与 grad 的组合实现高阶导。嵌套的复杂度随层数多项式增长而非指数爆炸,所以 C 错误。