数值微分 · 有限差分,以及它为什么不稳

用函数值近似导数,靠有限差分。但它藏着一个尴尬的矛盾:步长 h 越小,截断误差越小,可 h 太小又会引发灾难性抵消——存在一个最优 h,再小反而更差。这一章用「U 形误差曲线」把这个「天生不稳」讲透。

作者 David更新于 第 14 篇(共 21 篇)

数值微分 · 有限差分,以及它为什么不稳

第五站进入微积分的数值版。先看导数:很多时候我们只有 ff 的函数值(甚至 ff 是一段仿真代码,没有解析导数),却需要它的导数 f′f'。怎么办?用有限差分。但这一章的主角,是数值微分一个尴尬的性质——它天生不稳。

有限差分:用差商近似导数

导数的定义是 f′(x)=lim⁡h→0f(x+h)−f(x)hf'(x)=\lim_{h\to0}\frac{f(x+h)-f(x)}{h}。计算机取不了极限,就取一个小而非零的 hh:

f′(x)≈f(x+h)−f(x)h(前向差分,误差 O(h))f'(x) \approx \frac{f(x+h)-f(x)}{h} \quad(\text{前向差分,误差 } O(h))

更准的是中心差分,用对称的两点,误差降一阶:

f′(x)≈f(x+h)−f(x−h)2h(中心差分,误差 O(h2))f'(x) \approx \frac{f(x+h)-f(x-h)}{2h} \quad(\text{中心差分,误差 } O(h^2))

由泰勒展开可证:前向差分的截断误差正比于 hh,中心差分正比于 h2h^2。所以 hh 越小,截断误差越小——听起来该把 hh 取得越小越好?

陷阱:h 太小,抵消登场

不能。 这里 hh 越小越好的直觉,撞上了灾难性抵消:

当 hh 很小时,f(x+h)f(x+h) 和 f(x)f(x) 几乎相等,它们的差 f(x+h)−f(x)f(x+h)-f(x) 正是「两个相近的数相减」——灾难性抵消! 有效数字大量湮灭,而这个已经不准的差还要除以一个极小的 hh(放大误差)。于是 hh 太小时,抵消误差(舍入)反而主导,结果越来越烂。

两股误差此消彼长:hh 大 → 截断误差大(近似太粗);hh 小 → 抵消误差大(舍入被放大)。中间存在一个最优 hh,让两者之和最小——再小,误差不降反升。这就是数值微分「天生不稳」的根源:它同时踩了截断和抵消两个坑。

眼见为实:U 形误差曲线

用中心差分算 f(x)=sin⁡xf(x)=\sin x 在 x=1x=1 的导数(真值 cos⁡1\cos 1),扫一遍 hh:

import numpy as np

f = lambda x: np.sin(x)
true = np.cos(1.0)

for h in [1e-1, 1e-2, 1e-4, 1e-6, 1e-8, 1e-10, 1e-13]:
    approx = (f(1 + h) - f(1 - h)) / (2 * h)     # 中心差分
    print(f"h={h:.0e}   误差={abs(approx - true):.2e}")
h=1e-01   误差=1.67e-03      ┐
h=1e-02   误差=1.67e-05      │ h 变小,截断误差主导 → 误差下降
h=1e-04   误差=1.67e-09      │
h=1e-06   误差=5.26e-11      ┘ 最优点附近(≈ 最小)
h=1e-08   误差=6.08e-09      ┐
h=1e-10   误差=2.94e-07      │ h 再小,抵消误差主导 → 误差回升!
h=1e-13   误差=7.36e-04      ┘

误差先降后升,画出来是一条 U 形曲线。 最优 hh 在 10−610^{-6} 附近(中心差分约 ϵ1/3\epsilon^{1/3}),此时误差最小;再往小取,舍入抵消占上风,越取越差。「hh 取得越小导数越准」是彻头彻尾的错觉。

解药:绕开减法

既然病根是「减法抵消」,自然想到抵消那章的思路——绕开减法。有两个漂亮的办法:

  • 复步长微分(complex-step):取虚步长,f′(x)≈Im⁡(f(x+ih))hf'(x)\approx \dfrac{\operatorname{Im}\big(f(x+ih)\big)}{h}。它根本不做减法(信息藏在虚部),所以没有抵消,hh 可以取到 10−10010^{-100} 都不失精度!前提是 ff 能接受复数输入。
  • 自动微分(automatic differentiation,AD):不做任何近似,靠链式法则精确算出导数,误差只有舍入级。这是现代深度学习框架(PyTorch、JAX)反向传播的底层——它让「求导」从「危险的数值近似」变成了「精确的机械计算」。
# 复步长微分:无抵消,精度惊人
def complex_step_diff(f, x, h=1e-20):
    return (f(x + 1j*h)).imag / h

print(complex_step_diff(np.sin, 1.0))    # 0.5403023058681398 ≈ cos(1),几乎机器精度

小结与下一章

数值微分用有限差分(前向 O(h)O(h)、中心 O(h2)O(h^2)),但它天生不稳:hh 大截断误差大、hh 小抵消误差大,存在最优 hh,误差随 hh 呈 U 形——「越小越准」是错觉。解药是绕开减法:复步长微分(无抵消)或自动微分(精确)。

有趣的是,数值积分和微分恰好相反——它又稳又好算(积分是「求平均、抹平」,天然压制误差,不像微分放大误差)。下一章看数值积分,你会松一口气。👉 数值积分:梯形、Simpson 与自适应。

参考文献

  • [1] Nick Higham.《Accuracy and Stability of Numerical Algorithms》—— 有限差分的舍入误差。
  • [2] Squire & Trapp.「Using complex variables to estimate derivatives.」1998. —— 复步长微分。
  • [3] Baydin et al.「Automatic Differentiation in Machine Learning: a Survey.」2018. —— 自动微分综述。

本页目录