forward 设计

本页解释标量驱动函数 diff 和 value_and_diff 计算什么、代价如何,以及为什么它们是作用于 Dual[T] -> Dual[T] 的普通函数。

设计目标

为最常见的情形——单变量函数的导数——提供一行代码即可调用的 API,隐藏变量的种子设定,同时把全部语义保留在 Dual[T] 中,使驱动函数不可能与该类型不一致。

数学背景

前向模式

程序通过中间量 v1,…,vmv_1, \dots, v_m 计算 y=f(x)y = f(x),每个中间量都是对先前中间量的一次运算。前向模式在每个 vkv_k 旁边携带其导数 v˙k=dvk/dx\dot v_k = dv_k/dx,并按与程序相同的顺序用链式法则更新它:

vk=φk(vi,vj)⟹v˙k=∂φk∂vi v˙i+∂φk∂vj v˙j,x˙=1.v_k = \varphi_k(v_{i}, v_{j}) \quad\Longrightarrow\quad \dot v_k = \frac{\partial \varphi_k}{\partial v_i}\,\dot v_i + \frac{\partial \varphi_k}{\partial v_j}\,\dot v_j , \qquad \dot x = 1 .

对偶数算术执行的恰好就是这种更新:vk+v˙kεv_k + \dot v_k\varepsilon 就是 Dual::new(v_k, dv_k),dual 设计 中的规则就是各个 φk\varphi_k 的偏导数。种子 x˙=1\dot x = 1 就是 Dual::variable(x)。

通过嵌套得到高阶导数

如果 f 对其标量类型是泛型的,它就可以作用于 Dual[Dual[T]]。用 Dual[T] 上的对偶数对 x↦f′(x)x \mapsto f'(x) 求导,得到

ddx diff(f,x)=f′′(x),\frac{d}{dx}\,\mathrm{diff}(f, x) = f''(x),

因为 diff(f, ·) 本身就是一个由(内层的)对偶运算构建的程序。外层和内层的切向分量属于不同的类型,因此无类型嵌套前向模式中经典的扰动混淆(内层导数吸收了外层的扰动)会被类型检查器拒绝。11 J. M. Siskind 与 B. A. Pearlmutter 的 “Perturbation confusion and referential transparency”(IFL 2005)描述了无类型实现中的这一问题。 每嵌套一层,分量数就翻倍,因此 kk 阶导数的代价是普通求值的 2k2^k 倍。

设计决策

函数,而非 trait 或包装类型

问题。 用户应如何把函数交给驱动函数?

选项。 由用户类型实现的 trait;一个作用于 Double 的函数外加单独的导数;一个作用于 Dual[T] 的闭包。

选择。 闭包 (Dual[T]) -> Dual[T]。MoonBit 的闭包开销很小,该类型明确表明函数必须针对对偶数编写,而泛型函数无需包装即可在 Dual[T] 处实例化。

最小约束

value_and_diff 只需要 T : One,用于种子切向分量。其他一切约束都由 f 内部的运算提出,由编译器在调用处检查。

值来自同一次求值

值与导数读自同一次对偶求值。根据投影同态,它与 f 在 T 上计算出的值逐位相同,因此无需第二次求值。

放在独立的包中

驱动函数位于 forward 中,使 dual 包保持为纯粹的数值类型,再由根包重新导出它们。forward 只依赖 dual 和 luna-generic。

正确性与不变量

  • value_and_diff(f, x) == (f(variable(x)).value(), f(variable(x)).tangent()),而 diff(f, x) 是它的第二个分量。
  • 对于由环运算、除数非零的除法以及在其定义域内的初等函数构成的程序,结果就是 (f(x),f′(x))(f(x), f'(x)),其舍入误差界见 dual 设计。
  • 代价是在 Dual[T] 上对 f 求值一次,比在 T 上求值一次多出一个较小的常数因子。

被否决的方案

  • 在驱动函数内部做数值微分。 出于 dual 设计 中推导的精度原因而被否决。
  • 用于 nn 阶导数的 diff_n。 对泛型函数而言嵌套已经能给出它,而截断泰勒类型才是高效的方案;两者都不构成目前再增加一个 API 的理由。

边界

  • 仅支持标量输入。多个输入可由 linalg 驱动函数处理,或手动用 Dual::new 设定种子。
  • 没有带检查的变体:由 f 决定是否使用带检查的运算,带检查的 f 会返回它自己的 Result。
  • 没有高阶驱动函数,也没有反向模式。

Footnotes

  1. J. M. Siskind 与 B. A. Pearlmutter 的 “Perturbation confusion and referential transparency”(IFL 2005)描述了无类型实现中的这一问题。 ↩