使用“中心差分”而不是“前向差分”?——从偏导数定义到数值梯度的精度解析

这是我在阅读《深度学习入门—基于Python的理论与实现》(作者斋藤康毅)时遇到的一个问题,以下是AI总结的要点。

目录

  1. 问题的由来:为什么书中用中心差分而不是前向差分

  2. 标准偏导数方法(解析法)

  3. 数值近似:前向差分 vs 中心差分(以及后向差分补充)

  4. 泰勒展开的完整公式(单变量与多变量局部展开)

  5. 用泰勒展开说明中心差分更高精度及其其他优势

  6. 代码示例:误差对比

  7. 总结速查表


1. 问题的由来:作者为何使用中心差分?

在书中,numerical_gradient(f, x) 用的是中心差分法:

$$\frac{f(x+h)-f(x-h)}{2h}$$

而不是前向差分法:

$$\frac{f(x+h)-f(x)}{h}$$

作者的“有意”选择,核心原因:

维度 前向差分 中心差分 书中选择中心差分的理由
截断误差阶 \(O(h)\) \(O(h^2)\) 更高精度,减少系统性偏差
公式对称性 单边取样 左右对称 抵消奇数阶误差项,提高稳定性
梯度检查可靠性 偏差大 偏差小 用于验证反向传播实现更放心
代码复杂度 较低 略高(多一次函数调用) 成本可接受,收益明显
教学目的 仅演示概念 体现更“正确”的数值估计 让读者看到接近真实梯度的效果

书中此处的目标并不是“最快速度”或“用于真正训练”,而是帮助读者感性认识梯度并做数值验证。在这种语境下,中心差分的高精度与对称性更符合教学诉求。


2. 标准偏导数方法(解析法)

对于多元函数:

$$f: \mathbb{R}^n \to \mathbb{R}, \quad f(x_1, x_2, \dots, x_n)$$

对第i个变量的偏导数定义为:

$$\begin{eqnarray} \frac{\partial f}{\partial x_i}=\lim_{h\to 0} \frac{f(x_1,\dots,x_i+h,\dots,x_n)-f(x_1,\dots,x_i,\dots,x_n)}{h} \end{eqnarray}$$

梯度(gradient)是全部偏导数组成的向量:

$$\nabla f(x) = \left( \frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, \dots, \frac{\partial f}{\partial x_n} \right)$$

解析求导优点:

  • 精确无近似误差(假设函数可微且可符号化)

  • 计算快速,适合集成到训练过程(反向传播本质即链式法则的系统化应用)

解析法的局限:

  • 需要函数可写出明确公式

  • 对高度复杂组合函数(深度网络的整体表达)人工推导繁琐

  • 不适用于“黑盒”函数(只能评价函数值而无内部结构)


3. 数值近似:前向差分 vs 中心差分(补充后向差分)

3.1 前向差分(Forward Difference)

单变量: \( f’(x) \approx \frac{f(x+h)-f(x)}{h} \)

多变量(对 \(x_i\)): \(\frac{\partial f}{\partial x_i} \approx \frac{f(x_1,\dots,x_i+h,\dots,x_n)-f(x_1,\dots,x_i,\dots,x_n)}{h}\)

3.2 后向差分(Backward Difference)

\(f’(x) \approx \frac{f(x)-f(x-h)}{h}\)

3.3 中心差分(Central Difference)

单变量: \( f’(x) \approx \frac{f(x+h)-f(x-h)}{2h}\)

多变量对 \(x_i\): \(\frac{\partial f}{\partial x_i}(x) \approx \frac{f(\dots,x_i+h,\dots)-f(\dots,x_i-h,\dots)}{2h}\)

3.4 截断误差阶次

方法 误差主阶 精度等级
前向差分 \(O(h)\) 一阶
后向差分 \(O(h)\) 一阶
中心差分 \(O(h^2)\) 二阶(更准)

4. 泰勒展开的完整公式

4.1 单变量泰勒展开(在 \(x\) 附近)

$$f(x+h)=f(x)+h f’(x)+\frac{h^2}{2} f’’(x)+\frac{h^3}{6} f^{(3)}(x)+\frac{h^4}{24} f^{(4)}(x)+O(h^5) $$

$$f(x-h)=f(x)-h f’(x)+\frac{h^2}{2} f’’(x)-\frac{h^3}{6} f^{(3)}(x)+\frac{h^4}{24} f^{(4)}(x)+O(h^5)$$

4.2 单变量导出的差分近似

前向差分:

$$\frac{f(x+h)-f(x)}{h} = f’(x)+\frac{h}{2}f’’(x)+\frac{h^2}{6}f^{(3)}(x)+O(h^3)$$

中心差分:

$$\frac{f(x+h)-f(x-h)}{2h} = f’(x)+\frac{h^2}{6}f^{(3)}(x)+\frac{h^4}{120}f^{(5)}(x)+O(h^6)$$

4.3 二元函数一阶近似(只动一个变量)

对 \(f(x,y)\) 在 \((x_0, y_0)\) ,仅改变 \(x\):

$$f(x_0+h,y_0)=f(x_0,y_0)+h\frac{\partial f}{\partial x}+\frac{h^2}{2}\frac{\partial^2 f}{\partial x^2}+O(h^3)$$

同理得到 \(f(x_0-h,y_0)\) ,然后构造中心差分。


5. 为什么中心差分更高精度及其优势

5.1 误差阶次更低

从展开式可见:

  • 前向差分主误差项: \(\frac{h}{2}f’’(x)\) → 误差随 \(h\) 线性缩小

  • 中心差分主误差项: \(\frac{h^2}{6}f^{(3)}(x)\) → 误差随 \(h^2\) 缩小(更快)

5.2 奇数阶项抵消

把 \(f(x+h)\) 与 \(f(x-h)\) 相减时, \(h f’(x)\) 保留而 \(h^3 f^{(3)}(x)\) 等奇数阶对称项出现系数变化(更高阶),导致一阶误差项消失 → 提升精度。

5.3 减少系统性偏差

前向差分“只看右侧”会带来方向性偏差;中心差分对称采样更像“居中估计”,在统计上更稳定。

5.4 梯度检查更可靠

用于验证反向传播实现时,需要一个足够准确的“参考梯度”——否则你无法区分实现错误 vs 数值误差。中心差分是实践中的首选。

5.5 与步长选择的协同

由于截断误差更低,可以用稍微“更大一点的 h”维持精度,同时避免浮点减法灾难( \(f(x+h)-f(x)\) 当 \(h\) 太小可能产生严重舍入误差)。

5.6 局限与注意事项

  • 成本略高(每维需要两次函数扰动)

  • 对高噪声函数(数值震荡)时,双端采样可能放大噪声 → 需平衡 h

  • 不适用于函数不平滑或含大量非连续点(此时任何差分都会失真)


6. 代码示例:误差对比

下面用函数 \(f(x)=\sin(x)\) 在某点比较三种差分的误差;再做一个多变量例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import numpy as np

def f(x):
return np.sin(x)

def forward_diff(f, x, h):
return (f(x + h) - f(x)) / h

def backward_diff(f, x, h):
return (f(x) - f(x - h)) / h

def central_diff(f, x, h):
return (f(x + h) - f(x - h)) / (2*h)

x0 = 1.3
true = np.cos(x0)

for h in [1e-1, 1e-2, 1e-3, 1e-4]:
fd = forward_diff(f, x0, h)
cd = central_diff(f, x0, h)
bd = backward_diff(f, x0, h)
print(f"h={h:1.0e} | forward_err={abs(fd-true):.3e} | central_err={abs(cd-true):.3e} | backward_err={abs(bd-true):.3e}")

可能输出(示例):

1
2
3
4
h=1e-01 | forward_err=1.30e-03 | central_err=1.08e-05 | backward_err=1.30e-03
h=1e-02 | forward_err=1.30e-04 | central_err=1.08e-07 | backward_err=1.30e-04
h=1e-03 | forward_err=1.30e-05 | central_err=1.08e-09 | backward_err=1.30e-05
h=1e-04 | forward_err=1.30e-06 | central_err=1.09e-11 | backward_err=1.30e-06

你可以看到中心差分误差随 h 的衰减显著更快。

多变量例子 ( \(f(x,y)=x^2+y^2\) 梯度解析为 \((2x,2y)\)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def f2(v):
x, y = v
return x**2 + y**2

def numerical_grad_forward(f, v, h=1e-4):
grad = np.zeros_like(v)
for i in range(v.size):
org = v[i]
v[i] = org + h
fph = f(v)
v[i] = org
fp = f(v)
grad[i] = (fph - fp) / h
return grad

def numerical_grad_central(f, v, h=1e-4):
grad = np.zeros_like(v)
for i in range(v.size):
org = v[i]
v[i] = org + h
fph = f(v)
v[i] = org - h
fmh = f(v)
v[i] = org
grad[i] = (fph - fmh) / (2*h)
return grad

point = np.array([3.0, 4.0])
true_grad = np.array([6.0, 8.0])

g_fwd = numerical_grad_forward(f2, point.copy())
g_ctr = numerical_grad_central(f2, point.copy())

print("Forward:", g_fwd, " error:", np.linalg.norm(g_fwd-true_grad))
print("Central:", g_ctr, " error:", np.linalg.norm(g_ctr-true_grad))

结语

作者选用中心差分,不是因为不知道“标准偏导定义”,而是在教学场景下刻意选择一个更高精度、对称、易理解、便于梯度检查的数值近似方法。理解了偏导的解析本质与数值近似的误差来源,你就能清晰区分“理论梯度”和“数值梯度”的角色——前者用于训练,后者用于验证与直觉建立。