try ai
科普
编辑
分享
反馈
  • 算法微分

算法微分

SciencePedia玻尔百科
核心要点
  • 算法微分(AD)通过系统地应用链式法则,计算计算机程序的数学精确导数,从而避免了数值微分中固有的截断误差和舍入误差之间的权衡。
  • AD 有两种主要运行模式:前向模式,对于输入少的函数非常高效;以及反向模式(也称为反向传播),对于输入多输出少的函数极为高效。
  • 前向模式和反向模式之间的选择是基于函数雅可比矩阵形状的经济性考量,这使得反向模式成为现代深度学习革命背后的引擎。
  • AD 已成为科学和工程领域不可或缺的工具,它能自动计算用于求解刚性微分方程的雅可比矩阵,计算物理模拟的敏感度,并推动进化生物学等领域的优化。
  • 反向模式 AD 提供了离散伴随方法的一种自动化、无差错的实现,揭示了机器学习与传统科学计算之间深刻的数学统一性。

引言

导数是微积分的基石,它量化了函数的输出如何响应其输入的微小变化。虽然这个概念在纸面上很优雅,但将其转化为计算机代码的世界却充满了挑战。最直观的方法,即数值近似,存在根本性的缺陷,它陷入了限制其精度的各种误差之间的拉锯战。这就提出了一个关键问题:我们如何能够既精确又高效地计算由复杂算法定义的函数的导数?

本文介绍了算法微分(AD),这是一种强大的第三种范式,它超越了数值方法和符号方法的局限性。AD 不是近似一个函数或操纵抽象表达式,而是通过将链式法则直接应用于程序内的一系列运算来计算精确的导数。我们将探讨这一优雅的原则如何为计算微分问题提供解决方案。

首先,我们将深入探讨 AD 的“原理与机制”,将其与传统方法进行对比,并揭示其两种风格——前向模式和反向模式——的机制。我们将看到它们如何实现机器精度的准确性,以及为何它们具有截然不同的性能特征。随后,“应用与跨学科联系”一章将展示这一思想如何成为一种变革性力量,充当现代人工智能的基石、科学模拟中的重要工具,以及跨越不同研究领域的统一概念。

原理与机制

微积分提出了一个简单的问题:如果我们稍微拨动一个函数的输入,其输出会如何变化?答案——导数,是科学中最强大的思想之一。但是,当我们从纯粹的纸笔数学世界转向实际的计算机代码领域时,我们究竟如何计算这个“拨动”呢?

近似的幻象

最直观的方法就是简单地模仿导数的定义。我们可以在点 xxx 处计算函数 f(x)f(x)f(x),然后在稍微不同的点 x+hx+hx+h 处再次计算它,并求出斜率:

D(x,h)=f(x+h)−f(x)hD(x, h) = \frac{f(x+h) - f(x)}{h}D(x,h)=hf(x+h)−f(x)​

这被称为通过​​有限差分​​进行的​​数值微分​​。它看起来很简单,但隐藏着一个棘手的陷阱。为了更接近真实的导数,我们必须使步长 hhh 小到可以忽略不计。但这样做,我们会一头撞上两个相互竞争的恶棍:​​截断误差​​和​​舍入误差​​。

​​截断误差​​是我们因使用有限步长 hhh 而非无穷小步长而引入的误差。看一眼函数的泰勒级数展开式 f(x+h)=f(x)+hf′(x)+h22f′′(x)+…f(x+h) = f(x) + hf'(x) + \frac{h^2}{2}f''(x) + \dotsf(x+h)=f(x)+hf′(x)+2h2​f′′(x)+…,就会发现我们的有限差分公式的误差项与 hhh 有关。对于上面简单的向前差分公式,这个误差的阶数为 hhh,记作 O(h)\mathcal{O}(h)O(h)。更复杂的公式可以将此误差减少到 O(h2)\mathcal{O}(h^2)O(h2) 或更好,但原理保持不变:要战胜截断误差,我们必须使 hhh 尽可能小。

但是,当我们缩小 hhh 时,我们唤醒了第二个恶棍。计算机以有限的精度存储数字。当 hhh 变得极小时,x+hx+hx+h 和 xxx 变得几乎无法区分。f(x+h)f(x+h)f(x+h) 和 f(x)f(x)f(x) 的值变得几乎相同。在浮点运算中减去两个非常相似的数是灾难的根源,这种效应被称为​​灾难性抵消​​。我们丢失了大量的有效数字,剩下的基本上都是垃圾。更糟糕的是,这个垃圾随后还要除以一个非常小的数 hhh,这会极大地放大误差。这种​​舍入误差​​的尺度约为 ϵmachh\frac{\epsilon_{\text{mach}}}{h}hϵmach​​,其中 ϵmach\epsilon_{\text{mach}}ϵmach​ 是机器精度。为了对抗这种误差,我们需要使 hhh 变大!

困境就在于此。我们陷入了一场拉锯战。减小 hhh 会减少截断误差,但会放大舍入误差。增大 hhh 则正好相反。存在一个最优的 hhh 值可以平衡这两者,但即使在这个最佳点,我们能达到的最佳精度也差得可怜——通常只有我们计算机能力所及的有效数字的一半左右。我们受到了根本性的限制,因为我们将函数视为一个黑箱。为了做得更好,我们必须审视其内部。

一个新视角:对算法进行微分

计算机上的函数是什么?它不是一个神秘的预言机;它是一个算法,是一系列精确的基本算术运算,如加法、乘法,以及求值如 sin⁡(x)\sin(x)sin(x) 或 exp⁡(x)\exp(x)exp(x) 等函数。我们知道所有这些基本构建块的导数。​​链式法则​​精确地告诉我们如何组合它们。

这就是​​算法微分(AD)​​的核心思想,它也被称为自动微分。AD 不是符号微分,后者操纵数学表达式,可能导致难以管理的大型公式。它也不是数值微分,后者本质上是一种近似。AD 是第三种独特的方法:它将链式法则逐步应用于计算机执行的实际操作。

其结果是由代码表示的函数的数学精确导数,计算精度达到机器精度的极限。没有截断误差,因为没有步长 hhh;这个过程是代数的,而不是近似的。没有因减去相近函数值而引起的灾难性抵消,因此毁灭性的 1h\frac{1}{h}h1​ 舍入误差放大效应也消失了。AD 的美妙之处在于,它将微分从一个近似问题转变为一个计算问题。事实证明,链式法则为我们提供了两种自然的方式来执行这种计算。

链式法则的两种风格:前向模式与反向模式

想象一个长计算过程,y=f(g(h(x)))y = f(g(h(x)))y=f(g(h(x)))。链式法则告诉我们导数是 dydx=dydududvdvdx\frac{dy}{dx} = \frac{dy}{du} \frac{du}{dv} \frac{dv}{dx}dxdy​=dudy​dvdu​dxdv​,其中 v=h(x)v=h(x)v=h(x) 且 u=g(v)u=g(v)u=g(v)。我们可以通过对这些导数项进行分组来计算这个乘积。我们可以从左到右分组:((dydududv)dvdx)((\frac{dy}{du} \frac{du}{dv}) \frac{dv}{dx})((dudy​dvdu​)dxdv​),或者从右到左分组:dydu(dudvdvdx)\frac{dy}{du} (\frac{du}{dv} \frac{dv}{dx})dudy​(dvdu​dxdv​)。这两种分组方式产生了 AD 的两种模式。

前向模式:驾驭计算的浪潮

前向模式 AD 感觉非常直观。它在从输入到输出的一次传递中,同时计算函数的值及其导数。关键在于扩展我们对数的概念。我们不再跟踪单个值 vvv,而是跟踪一对数:它的值和它的导数,即 (v,v′)(v, v')(v,v′)。这对数通常被形式化为​​对偶数​​,写作 v+v′ϵv + v'\epsilonv+v′ϵ,其中 ϵ\epsilonϵ 是一个奇特的新量,其性质为 ϵ2=0\epsilon^2 = 0ϵ2=0。

让我们看看用这些对偶数进行算术运算会发生什么。规则直接源于微积分的法则:

  • ​​加法:​​ (u+u′ϵ)+(v+v′ϵ)=(u+v)+(u′+v′)ϵ(u + u'\epsilon) + (v + v'\epsilon) = (u+v) + (u'+v')\epsilon(u+u′ϵ)+(v+v′ϵ)=(u+v)+(u′+v′)ϵ。这正是导数的加法法则!
  • ​​乘法:​​ (u+u′ϵ)×(v+v′ϵ)=uv+uv′ϵ+vu′ϵ+u′v′ϵ2=uv+(uv′+vu′)ϵ(u + u'\epsilon) \times (v + v'\epsilon) = uv + uv'\epsilon + vu'\epsilon + u'v'\epsilon^2 = uv + (uv' + vu')\epsilon(u+u′ϵ)×(v+v′ϵ)=uv+uv′ϵ+vu′ϵ+u′v′ϵ2=uv+(uv′+vu′)ϵ。这是乘法法则!

这个模式是通用的。对于任何基本函数,如 sin⁡\sinsin,我们定义 sin⁡(v+v′ϵ)=sin⁡(v)+cos⁡(v)v′ϵ\sin(v + v'\epsilon) = \sin(v) + \cos(v)v'\epsilonsin(v+v′ϵ)=sin(v)+cos(v)v′ϵ。

要计算函数 f(x)f(x)f(x) 的导数,我们只需将输入初始化为对偶数 x+1ϵx + 1\epsilonx+1ϵ 并执行程序。每个中间变量都变成一个对偶数,携带其值和其相对于 xxx 的导数。当计算完成时,最终结果是对偶数 f(x)+f′(x)ϵf(x) + f'(x)\epsilonf(x)+f′(x)ϵ。我们想要的导数就是 ϵ\epsilonϵ 的系数。我们以大约两倍于原始工作的代价得到了精确的导数。

这很优雅,但如果我们的函数有很多输入,比如 f(x1,x2,…,xn)f(x_1, x_2, \dots, x_n)f(x1​,x2​,…,xn​) 呢?如上所述,前向模式过程只给出相对于我们用非零 ϵ\epsilonϵ 部分“播种”的那个输入的导数。为了得到完整的梯度(所有偏导数),我们必须对每个输入变量运行整个过程 nnn 次。因此,总成本与 nnn 乘以评估原始函数的成本成正比。如果 nnn 很小,这没问题,但如果它是一百万呢?

反向模式:解开过去

这就是​​反向模式 AD​​ 的魔力所在。它是深度学习革命背后的主力,在深度学习中,函数(神经网络)可以有数百万甚至数十亿的输入参数。关键思想是反转信息流。

反向模式分两个阶段工作:

  1. ​​前向传递:​​ 首先,我们像往常一样从输入到输出执行程序。但在此过程中,我们不只是丢弃中间结果。我们将每个操作及其结果记录在“磁带”上,或构建一个代表函数整个结构的​​计算图​​。

  2. ​​后向传递:​​ 现在,我们反向播放磁带。我们从最终输出开始,称之为 LLL。LLL 对自身的导数,不言而喻,是 1。然后我们向后遍历图。对于每个中间变量 viv_ivi​,我们计算它对最终输出 LLL 的贡献。这个量 ∂L∂vi\frac{\partial L}{\partial v_i}∂vi​∂L​ 被称为 viv_ivi​ 的​​伴随​​。

链式法则为我们提供了传播这些伴随的秘诀。如果一个变量 vkv_kvk​ 是由 viv_ivi​ 和 vjv_jvj​ 计算得出的(例如,vk=vi+vjv_k = v_i + v_jvk​=vi​+vj​),那么 vkv_kvk​ 的伴随会贡献给其“父节点” viv_ivi​ 和 vjv_jvj​ 的伴随。具体来说,我们将 ∂L∂vk∂vk∂vi\frac{\partial L}{\partial v_k} \frac{\partial v_k}{\partial v_i}∂vk​∂L​∂vi​∂vk​​ 加到 viv_ivi​ 伴随的运行总和中。当我们一路回溯到函数的原始输入时,我们将累积得到 LLL 对每个输入的完整偏导数。

令人难以置信的结果是,仅通过一次前向传递和一次后向传递,我们就同时获得了单个输出对所有输入的导数。无论有多少输入,计算成本只是原始函数评估成本的一个小的常数倍。正是这种惊人的效率,使得反向模式 AD——以其更著名的名称​​反向传播​​——成为驱动现代神经网络训练的引擎 [@problem_id:3197443, @problem_id:3101263]。

生活在一个可微的世界

前向模式和反向模式之间的选择是一个简单的经济学问题,由函数 f:Rn→Rmf: \mathbb{R}^n \to \mathbb{R}^mf:Rn→Rm 的形状决定。

  • 要计算一个“高”的雅可比矩阵(n≪mn \ll mn≪m),使用​​前向模式​​ nnn 次。
  • 要计算一个“宽”的雅可比矩阵(n≫mn \gg mn≫m),使用​​反向模式​​ mmm 次。 对于训练神经网络,我们有一个从数百万参数到单个标量损失的函数(n≫1,m=1n \gg 1, m=1n≫1,m=1),这使得反向模式成为压倒性的优越选择。

AD 的强大之处伴随着一个关键的微妙之处:它微分的是实现,而不是抽象的数学思想。它实际上是你的代码的导数。

  • 这意味着,数值上巧妙的重构,例如用于稳定 softplus 函数的“log-sum-exp 技巧”,会产生一个不同且更稳定的梯度计算,尽管数学函数并未改变。
  • 这也意味着,依赖于输入值的 if/else 语句和其他形式的控制流会在函数中产生“扭结”。朴素的 AD 只会微分其中一个分支,导致导数可能发生不连续的跳跃。这催生了“可微编程”的整个领域,旨在为经典算法创建平滑、可微的近似。

也许最优雅的是,AD 有时可以“治愈”不可微的点。函数 ReLU(x)=max⁡(0,x)\mathrm{ReLU}(x) = \max(0, x)ReLU(x)=max(0,x) 在 x=0x=0x=0 处不可微。AD 系统必须在那里为其导数选择一个值(一个​​次梯度​​),通常是 0 或 1。但考虑函数 f(x)=ReLU(x)3f(x) = \mathrm{ReLU}(x)^3f(x)=ReLU(x)3。机械地应用链式法则得到 f′(x)=3⋅ReLU(x)2⋅ReLU′(x)f'(x) = 3 \cdot \mathrm{ReLU}(x)^2 \cdot \mathrm{ReLU}'(x)f′(x)=3⋅ReLU(x)2⋅ReLU′(x)。在 x=0x=0x=0 处,这变成 f′(0)=3⋅02⋅ReLU′(0)=0f'(0) = 3 \cdot 0^2 \cdot \mathrm{ReLU}'(0) = 0f′(0)=3⋅02⋅ReLU′(0)=0。无论我们为 ReLU 选择哪个次梯度,导数都是零!复合函数是平滑的,AD 毫不费力地找到了它的正确导数。

算法微分不仅仅是一个巧妙的技巧。它是一种根本性的视角转变,是微积分和计算机科学的美妙结合,为我们提供了一种以无与伦比的效率和准确性计算精确导数的方法。它是一种工具,让我们能够透过链式法则本身的清晰视野,而不是通过近似的模糊镜头,来观察复杂函数的景观。

应用与跨学科联系

我们花了一些时间来理解算法微分的机制,这个奇妙的计算工具能够追踪程序的逻辑,并通过优美而系统地应用链式法则,精确地告诉我们当拨动任何输入时输出会如何变化。这无疑是一个聪明的想法。但它有用吗?

事实证明,答案是响亮的“是”。它不仅有用,而且具有变革性。这个单一、优雅的原则就像一把万能钥匙,在众多学科中开启了进步之门。它是现代人工智能革命内部的引擎,是模拟湍流流体的物理学家的可靠伙伴,也是生物学家重建生命之树的秘密武器。现在,让我们踏上一段旅程,穿越其中一些领域,看看这个思想是如何将它们编织在一起的。

基石:完美的导数

在我们涉足复杂的模拟和人工大脑之前,让我们从一个简单而基本的问题开始:我们如何在计算机上计算导数?最显而易见的方法是拿来你第一堂微积分课上的定义,然后……直接用。你在两个点 xxx 和 x+hx+hx+h 处评估一个函数 f(x)f(x)f(x),求出差值,再除以微小的步长 hhh。这就是​​有限差分​​法。

但这里潜伏着一个可怕的、隐藏的问题。如果你把 hhh 设得太大,你的近似就会很差(这被称为截断误差)。所以,你把 hhh 调小。但当你把 hhh 变得越来越小时,f(x)f(x)f(x) 和 f(x+h)f(x+h)f(x+h) 这两个值变得几乎相同。当你减去两个非常大且几乎相同的数时,你的计算机会在一阵舍入误差中失去精度。这就像试图通过测量一辆卡车装上和卸下一根羽毛后的重量来称量这根羽毛的重量一样!你陷入了一个痛苦的权衡:无论你选择什么样的步长,你都会被这样或那样的误差所困扰。

算法微分(AD)完全绕开了这个困境。因为 AD 遵循微积分的规则来处理程序本身的基本运算,它不近似任何东西。它以机器的全部精度计算导数,如同魔法一般。对于一个给定的函数,比如常用于测试优化算法的具有挑战性的 Rosenbrock 函数,AD 提供的梯度几乎没有误差,而有限差分的准确性则是一场与步长 hhh 之间的敏感而令人沮丧的博弈。这种特性——提供一个精确、稳定的导数——是其所有更宏大应用得以建立的基础。

驱动科学的引擎

科学的很大一部分工作就是用数学的语言写下自然法则,而这种语言往往是微分方程的语言。这些方程描述了事物的变化,从反应器中化学物质的浓度到桥梁的振动。在这里,AD 已成为不可或缺的工具。

求解自然界的方程

自然界中的许多系统都是“刚性”的。这是一个绝妙的词,描述了一个简单的问题:事情在截然不同的时间尺度上发生。想象一下模拟一个化学反应,其中一个短暂的高能分子在微秒内出现又消失,而主要产物则在几分钟内缓慢积累。如果你试图用简单的方法来模拟这个过程,你的时间步长必须微小到足以捕捉快速过程,而你的模拟将花费永恒的时间才能看到慢速过程。

为了稳健地解决这类问题,我们使用“隐式”数值方法。这些方法非常稳定,但它们有一个问题:在每一个时间步,它们都要求你解一个非线性方程组。而解决这个问题的最佳方法是像牛顿法这样的方法,而这种方法——你猜对了——需要一个雅可比矩阵。对于一个有几十种相互作用的化学物质的系统,手动推导这个雅可比矩阵中的数百或数千个条目是一场微积分的噩梦,是一项出了名的容易出错且磨灭灵魂的任务。

这时,AD 挺身而出。通过简单地编写描述化学反应速率的代码,我们就可以将 AD 工具指向它,并自动获得精确的雅可比矩阵。这彻底改变了科学计算。它使科学家能够使用最强大、最稳定的数值方法,而无需手动微分的苦差事和风险。同样的故事在各处上演:在计算流体动力学(CFD)中,AD 为发动机内部的刚性反应流提供精确的雅可比矩阵;在计算固体力学中,它计算出模拟材料在应力下复杂行为所需的“一致切线矩阵”,即使是那些有历史记忆的材料,如塑料和金属。

重建过去

AD 的威力不仅限于物理模拟。思考一下一位进化生物学家试图构建生命之树的工作。他们拥有来自当今各种物种的 DNA 序列,以及一个描述一个核苷酸随时间变为另一个核苷酸的概率的数学模型(马尔可夫链)。我们今天看到这些数据的可能性是树的形状及其分支长度(代表进化时间)的一个极其复杂的函数。

为了找到最可信的进化树,必须找到使这种可能性最大化的参数。这意味着我们需要可能性函数相对于每个分支长度和替换模型中每个参数的梯度。但可能性本身是通过一种巧妙的递归算法计算的,该算法从树的叶子向上追溯到根(称为 Felsenstein 的剪枝算法)。你怎么可能对整个算法进行微分呢?有了 AD,这不仅可能,而且很优雅。反向模式 AD 通过完全相同的递归过程,将敏感性从根部的最终可能性一路反向传播到每个分支,为优化提供了所需的精确梯度。

现代人工智能的心脏

如果说 AD 是传统科学的强大工具,那么它就是现代人工智能的命脉。在机器学习的世界里,反向模式 AD 是如此核心,以至于它有自己著名的名字:​​反向传播​​。

训练深度神经网络,其核心是一个优化问题。你有一个拥有数百万甚至数十亿参数(权重)的模型,还有一个“损失函数”告诉你模型在数据上的表现有多差。目标是找到使损失尽可能小的权重。最常见的方法是计算标量损失函数相对于模型所有参数的梯度,然后在梯度的反方向上迈出一小步,在损失的地形上“下山”。对于一个标量输出(损失)和数百万输入(权重),反向模式 AD 效率惊人。毫不夸张地说,没有它,深度学习革命就不会发生。

但故事并不止于简单的梯度。对于更高级的优化,你可能不仅想知道地形的斜率,还想知道它的曲率(海森矩阵)。对于一个有 nnn 个参数的模型,海森矩阵是一个巨大的 n×nn \times nn×n 矩阵,对于大的 nnn 来说是不可能存储的。但是许多强大的优化方法并不需要整个矩阵;它们只需要知道它作用于一个向量上的结果,即所谓的黑塞-向量积(HVP)。通过巧妙地结合反向和前向模式,AD 可以在仅比计算梯度多一个小的常数因子的成本下计算出这个 HVP。

正是这种“无矩阵”的思想驱动着大规模工程中最先进的求解器。像 Newton-Krylov 这样的求解器,用于流体动力学或结构力学中的庞大系统,也依赖于计算雅可比-向量积(JVP)而不是构建完整的雅可比矩阵。而前向模式 AD 正是完成这项工作的完美工具。这是一个深刻统一的时刻:同样的核心思想,即用 AD 计算矩阵-向量积,既用于训练下一代语言模型,也用于设计新的飞机机翼。

这种科学建模和机器学习的融合正在开创一个新的前沿。在​​物理信息神经网络(PINNs)​​中,训练一个神经网络不仅要拟合观测数据,还要遵守已知的物理定律,如反应-扩散方程。损失函数包含一个惩罚网络违反偏微分方程的项。为了计算这个惩罚项,我们需要网络输出相对于其输入(空间和时间)的导数,而 AD 可以毫不费力地提供这些导数。在计算化学中,科学家训练网络来根据原子的位置预测分子的势能。然后,他们对训练好的网络使用 AD 来推导物理量:一阶导数给出作用在原子上的力,二阶导数(海森矩阵)给出振动频率。

一个统一的视角:自动化的伴随方法

几十年来,应用数学家和工程师一直使用一种称为​​伴随方法​​的强大技术来有效计算复杂系统中的敏感性。为给定系统推导伴随方程是许多领域的入门仪式,这个过程虽然强大,但也高度专业化、费力且容易出错。

这就是最终的、美妙的启示:应用于数值求解器代码的反向模式算法微分就是离散伴随方法。两者是同一回事。AD 是伴随方法原则的通用、自动化且无差错的实现,这些原则过去是以零散的、逐个领域的方式被发现的。它揭示了始终存在的深刻、统一的数学结构。

因此,算法微分不仅仅是一种计算导数的巧妙技巧。它是一种审视计算的新视角。它使我们能够构建任意复杂的模型——物理的、生物的、智能的——并将它们不视为黑箱,而是视为透明、可微的机器,我们可以用微积分的精度来探究其内部运作。它是一个简单而美丽的思想所具有的“不合理有效性”的明证。