自动微分(AD)是一类计算技术,用于计算以计算机程序形式表达的数值函数的导数。它将链式法则应用于程序执行的基本运算,把各运算的局部导数组合成整个计算过程的导数。与有限差分近似不同,自动微分不需要引入求导步长。它是基于梯度的机器学习和数学优化的基础,也支持科学仿真和工程设计。(jmlr.org)
数学基础
数值程序通过加法、乘法和初等函数等一系列运算来计算函数值。每项运算都有已知的求导规则。自动微分根据中间值之间的依赖关系组合这些规则,实质上是在对函数复合求导。其结果通常是指定输入处的导数数值,而不是经过化简的符号公式。(jmlr.org)
这些依赖关系可以用计算图表示。节点代表输入、中间值和输出,边表示某项运算使用了哪些值。即使源程序包含循环,一次有限执行过程的记录也可以构成有向无环图。导数沿着这张图正向或反向传播。例如,PyTorch 在执行过程中构建其自动求导图,并在后续每次执行时重新构建,使记录的结构能够反映控制流的变化。(docs.pytorch.org)
自动微分不同于通过操作表达式求导的符号微分,也不同于利用邻近点的函数值估计导数的数值微分。自动微分避免了有限差分的截断误差,但其计算仍受浮点运算和舍入误差的影响。因此,“精确导数”指的是不采用有限差分近似,而直接应用解析求导规则,并不意味着数值精度无限。(jmlr.org)
前向模式
对于 ,用 表示其雅可比矩阵。前向模式自动微分在进行常规计算的同时传播输入扰动 ,得到雅可比矩阵与向量的乘积:
这就是输出沿 的方向导数。每个中间变量都同时携带其常规数值和切向值。对于乘法 ,切向值的计算规则为:
依次以各输入坐标方向作为初始种子,就能依次得到雅可比矩阵的各列。因此,当输入较少而输出较多时,前向模式通常更有优势。(docs.jax.dev)
一种解释方式是使用对偶数,其形式为 ,其中 。对这些数对进行算术运算时,一阶导数信息会与函数值一起传播。这种代数解释说明了为什么只需实现适当的算术规则,就能把普通的数值求值转变为导数求值。(jmlr.org)
反向模式
反向模式自动微分先计算函数值,再反向传播输出的敏感度。给定输出种子 ,它计算:
当敏感度写成行向量时,这等价于向量与雅可比矩阵的乘积。对于标量输出,将其敏感度的初始值设为 ,就能得到相对于所有输入的完整梯度。当输入较多而输出较少时,反向模式通常更合适。(docs.jax.dev)
考虑 ,通过 、 和 依次计算。应用反向求导规则可得:
由于 通过两条路径影响输出,对 的两项贡献必须相加。这个例子展示了如何累加来自具有依赖关系的运算的敏感度。(docs.pytorch.org)
反向传播通常需要前向求值时产生的中间值。保存这些值会增加内存消耗。梯度检查点技术又称重新物化,通过仅保留选定的中间值,并在反向计算时重新计算其余值,减少存储需求。它以额外计算换取更低的内存占用;重新计算必须保持原求值过程的相关行为不变。(docs.pytorch.org)
实现与高阶导数
自动微分系统通常采用运算符重载或程序变换。运算符重载为数值运算赋予跟踪导数的能力。程序变换则从源代码或中间表示生成导数计算过程。这些方法可以与计算图追踪和编译结合使用。它们在实践中的差异涉及所支持的语言特性、优化机会、运行时开销以及对数组运算的处理方式。(arxiv.org)
TensorFlow 在梯度带中记录相关运算。PyTorch 则记录张量运算之间的依赖关系,以进行反向传播。这类系统只能对其求导机制能够追踪到的计算求导:如果将某项计算移到未被追踪的外部库中,就可能切断求导路径。因此,未连接到输出的输入与数学导数为零的输入是两种不同情况,即使某个接口提供了将两者都表示为零的选项。(tensorflow.org)
导数计算本身也可以继续求导。嵌套使用前向和反向变换,可以得到包括海森矩阵在内的高阶导数。很多时候,只需要海森矩阵与向量的乘积。对于二阶连续可微的标量函数和常向量 ,有:
这样无需显式存储完整的海森矩阵,就能为数值优化提供曲率信息。(docs.jax.dev)
应用与局限
在人工神经网络中,反向传播是反向模式自动微分的一种专门应用。它计算损失函数相对于模型参数的导数,随后优化过程利用这些导数更新参数。自动微分提供的是敏感度信息,而不是规定更新规则本身。TensorFlow 的梯度带接口通过返回梯度供后续优化器使用,明确体现了这种分工。(tensorflow.org)
自动微分不能使不可微函数变得可微。在不可微的折点处,库可能采用文档中说明的约定,在适用时选择一个次梯度,或返回未定义的结果。对于依赖数据的分支,求导针对的是实际执行的计算,而不是选择该分支的离散决策。无效运算也可能污染梯度:在除以零后再屏蔽其结果,并不一定能从已记录的求导图中移除这项运算。因此,正确计算导数既取决于数学假设,也取决于实现的行为。(docs.pytorch.org)