文章

13 梯度检查(Gradient Checking)

13 梯度检查(Gradient Checking)

一、目的与意义

  梯度检查是一种验证反向传播(Backpropagation)实现是否正确的重要调试技术。

  • 在手动推导或编码计算梯度时,极易引入细微错误。
  • 梯度检查通过数值微分近似真实梯度,并与反向传播计算出的解析梯度进行比对,从而发现 bug。
  • 吴恩达强调: “它帮我节省了大量时间,多次发现反向传播中的错误。”

二、基本思想

  神经网络的参数包括所有权重 $W^{[1]}, W^{[2]}, \dots, W^{[L]}$ 和偏置 $b^{[1]}, b^{[2]}, \dots, b^{[L]}$。
为统一处理,将这些参数拼接成一个大向量 $\theta$

\[\theta = \begin{bmatrix} \text{vec}(W^{[1]}) \\ \text{vec}(b^{[1]}) \\ \text{vec}(W^{[2]}) \\ \text{vec}(b^{[2]}) \\ \vdots \\ \text{vec}(W^{[L]}) \\ \text{vec}(b^{[L]}) \end{bmatrix}\]

  其中 $\text{vec}(\cdot)$ 表示将矩阵按列(或行)展开为向量。

  此时,损失函数 $J$ 可视为 $\theta$ 的函数:

\[J = J(\theta)\]

  同理,反向传播得到的梯度(即 $\frac{\partial J}{\partial W^{[l]}}$, $\frac{\partial J}{\partial b^{[l]}}$)也可拼接为与 $\theta$ 同维的向量:

\[d\theta = \begin{bmatrix} \text{vec}(dW^{[1]}) \\ \text{vec}(db^{[1]}) \\ \vdots \\ \text{vec}(dW^{[L]}) \\ \text{vec}(db^{[L]}) \end{bmatrix}\]

三、数值梯度近似(Numerical Gradient Approximation)

  对每个参数 $\theta_i$,使用中心差分法(two-sided difference) 计算其数值梯度:

\[d\theta_{\text{approx}, i} = \frac{J(\theta_1, \dots, \theta_i + \varepsilon, \dots) - J(\theta_1, \dots, \theta_i - \varepsilon, \dots)}{2\varepsilon}\]

  其中 $\varepsilon$ 是一个很小的正数(通常取 $\varepsilon = 10^{-7}$)。

为什么用中心差分?
相比前向差分 $\frac{J(\theta + \varepsilon) - J(\theta)}{\varepsilon}$,中心差分的误差是 $O(\varepsilon^2)$,精度更高。

  对所有 $i$ 执行上述操作,得到整个数值梯度向量 $d\theta_{\text{approx}}$。


四、梯度一致性检验

  比较数值梯度 $d\theta_{\text{approx}}$ 与反向传播得到的解析梯度 $d\theta$。

  使用相对误差(relative error) 来衡量两者接近程度:

\[\text{error} = \frac{\| d\theta_{\text{approx}} - d\theta \|_2}{\| d\theta_{\text{approx}} \|_2 + \| d\theta \|_2}\]

  其中 $| \cdot |_2$ 表示欧几里得范数(L2 范数)。

判断标准:

error 值范围含义
$< 10^{-7}$✅ 非常好,梯度很可能正确
$\sim 10^{-5}$⚠️ 可接受,但建议检查是否有个别分量偏差大
$> 10^{-3}$❌ 极可能有 bug,必须排查
🔍 若 error 较大,可逐个检查 $d\theta_{\text{approx}, i} - d\theta_i$,定位具体哪一层或哪个参数出错。

五、实施步骤(算法流程)

  1. 前向传播:计算损失 $J$。
  2. 反向传播:计算解析梯度 $d\theta$。
  3. 数值梯度:对每个 $\theta_i$,用中心差分计算 $d\theta_{\text{approx}, i}$。
  4. 计算 error:用上述公式评估一致性。
  5. 判断并调试:若 error 过大,检查反向传播实现。

六、注意事项(Tips)

  • 仅在调试阶段使用:梯度检查计算开销大(需多次前向传播),训练时应关闭。
  • 确保随机种子固定:避免因 dropout、数据 shuffle 等引入随机性,影响梯度比较。
  • 先用小网络测试:例如 1 层、少量神经元,便于快速验证。
  • 不要在有正则化/BN/dropout 时直接检查:需确保数值梯度和解析梯度在完全相同条件下计算。

七、关键公式

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import numpy as np

def gradient_check(theta, dtheta, compute_cost, epsilon=1e-7):
    """
    执行梯度检查,验证反向传播计算的梯度 dtheta 是否正确。
    
    参数:
        theta (np.ndarray): 所有参数拼接成的一维向量,形状 (n, )
        dtheta (np.ndarray): 反向传播计算出的梯度,形状 (n, ),应与 theta 同维
        compute_cost (function): 接受参数 theta 并返回标量损失 J 的函数
        epsilon (float): 微小扰动值,默认 1e-7
    
    返回:
        error (float): 相对误差
        passed (bool): 是否通过梯度检查(error < 1e-7)
    """
    n = theta.size
    dtheta_approx = np.zeros_like(theta)

    # 计算数值梯度(中心差分)
    for i in range(n):
        # 保存原始值
        theta_plus = np.copy(theta)
        theta_minus = np.copy(theta)
        
        theta_plus[i] += epsilon
        theta_minus[i] -= epsilon
        
        J_plus = compute_cost(theta_plus)
        J_minus = compute_cost(theta_minus)
        
        dtheta_approx[i] = (J_plus - J_minus) / (2 * epsilon)

    # 计算相对误差
    numerator = np.linalg.norm(dtheta_approx - dtheta)
    denominator = np.linalg.norm(dtheta_approx) + np.linalg.norm(dtheta)
    error = numerator / denominator

    # 判断是否通过
    passed = error < 1e-7

    print(f"Gradient check error: {error:.2e}")
    if passed:
        print("✅ 梯度检查通过!")
    elif error < 1e-5:
        print("⚠️  梯度可能正确,但建议仔细检查。")
    else:
        print("❌ 梯度检查失败!可能存在反向传播 bug。")

    return error, passed

🔧 使用示例

  假设你已将网络所有参数拼接为 theta,并通过反向传播得到 dtheta,并定义了一个能接受 theta 并返回损失的函数 compute_cost

1
2
3
4
5
6
7
8
# 示例:简单二次损失
def compute_cost(theta):
    return np.sum(theta ** 2)  # J = ||theta||^2

theta = np.random.randn(10)
dtheta = 2 * theta  # 解析梯度:dJ/dθ = 2θ

error, ok = gradient_check(theta, dtheta, compute_cost)

  输出可能为:

1
2
Gradient check error: 1.23e-10
✅ 梯度检查通过!

📌 注意事项

  • compute_cost 必须是一个纯函数:给定 theta 就返回确定的损失值(关闭 dropout、固定 batch 等)。
  • 实际神经网络中,你需要先将 Wb 展平拼接为 theta,反向传播后也将 dW, db 拼接为 dtheta
  • 在调试完成后,务必关闭梯度检查,因其时间复杂度为 $O(n)$ 次前向传播,非常慢。

  这份代码可直接用于你的深度学习项目中进行梯度验证,是吴恩达课程思想的工程化实现。

✅ 总结

  梯度检查是深度学习实现中不可或缺的调试工具。虽然现代框架(如 PyTorch、TensorFlow)自动求导降低了手动实现需求,但在自定义层、损失函数或研究新算法时,掌握梯度检查仍至关重要。

“当你实现了一个新的神经网络,先写反向传播,再用梯度检查验证——这是专业做法。”

本文由作者按照 CC BY 4.0 进行授权