2. 物理残差项梯度消失的排查

问题特征:残差损失在降,解却不收敛

在 PINN 训练中,物理残差损失 L_r 持续下降,但预测解与参考解的相对 L2 误差停滞甚至上升,这通常不是优化器“没在学”,而是残差项对网络参数的梯度在传播路径上被削弱。残差损失下降可能来自少数配点被拟合、或残差被网络输出层的偏置吸收,而真正决定解精度的内部层几乎没有收到有效梯度。

排查的核心是两条证据链:一是逐层梯度统计,看残差损失对每一层权重的梯度范数是否随层数迅速衰减;二是残差对输入的敏感度,即 ∂r/∂x 与 ∂r/∂t 的分布,若敏感度接近零,说明残差曲面在空间上过于平坦,网络没有学到能改变残差的特征。

残差项梯度消失:从损失到层的诊断路径 逐层梯度范数 ‖∂L_r/∂W‖ 输入层 (x,t) 隐藏层 1 隐藏层 2 隐藏层 3 输出层 u(x,t) 梯度强度(示意) 1.0e-2 6.0e-3 2.5e-3 7.0e-4 3.0e-4 梯度逐层衰减约 30× 残差敏感度 ∂r/∂x, ∂r/∂t 空间分布(示意) |∂r/∂x| ≈ 1e-4(接近平坦) 残差曲面缺乏空间变化 判据:敏感度中位数 < 1e-3 → 残差项对输入不敏感 验证实验设计 实验 A:梯度增强 残差损失权重 λ_r ↑ 10× 或引入梯度归一化 观察:深层梯度是否回升 实验 B:网络结构调整 改用 tanh / sin 激活 或加入残差连接 观察:敏感度是否提升 实验 C:输入归一化 x,t 归一化到 [-1,1] 检查输入尺度是否压缩梯度 观察:∂r/∂x 量级变化 通过判据 深层梯度 ≥ 1e-3 且 敏感度中位数 ≥ 1e-3 典型现象: L_r 持续下降 但 相对 L2 误差停滞 → 优先检查深层梯度与残差敏感度,而非继续调学习率 注:图中数值为教学示意,实际排查需以本模型逐层统计结果为准

逐层梯度统计:定位“断点”层

在 PyTorch 中,残差损失对每层权重的梯度可以通过 torch.autograd.grad 单独获取,避免被总损失中的其他项掩盖。下面给出一个可直接嵌入训练循环的诊断函数,它在每个记录周期计算 L_r 对各层参数的梯度范数,并输出归一化衰减比。

import torch

def residual_grad_stats(model, residual_loss_fn, collocation_pts, record=False):
    """
    model: PINN 网络(nn.Module)
    residual_loss_fn: 输入配点,返回物理残差损失 L_r
    collocation_pts: 配点张量,形状 [N, 2],列为 (x, t)
    """
    params = [p for p in model.parameters() if p.requires_grad]
    L_r = residual_loss_fn(collocation_pts)
    grads = torch.autograd.grad(L_r, params, retain_graph=True, allow_unused=True)

    stats = []
    for name, p, g in zip([n for n, _ in model.named_parameters() if _.requires_grad], params, grads):
        if g is None:
            stats.append((name, 0.0))
        else:
            stats.append((name, g.norm().item()))

    if record:
        for name, norm in stats:
            print(f"{name:30s}  ‖∂L_r/∂W‖ = {norm:.3e}")
    return stats

# 使用示例:每 200 步记录一次
# stats = residual_grad_stats(model, residual_loss_fn, collocation_pts, record=True)
# 关注相邻层梯度范数比值,若连续两层比值 < 0.1,则该处为疑似断点

判读要点:若输入层梯度范数在 1e-2 量级,而输出层附近降到 1e-4 以下,且相邻层比值持续小于 0.1,说明反向传播在该段被压缩。常见原因包括激活函数饱和(如 tanh 在大输入下导数趋零)、输入未归一化导致高阶导数项量级失衡、以及残差项本身对网络输出的一阶依赖过弱。

残差敏感度分析:判断残差曲面是否“平坦”

梯度消失的另一种表现是残差对输入几乎不敏感。对配点集计算 ∂r/∂x 与 ∂r/∂t,统计其中位数与分位数。若中位数低于 1e-3,说明残差曲面在空间上接近常数,网络输出的小扰动无法有效改变残差,优化器自然难以通过残差项改善解。

def residual_sensitivity(model, residual_fn, collocation_pts):
    pts = collocation_pts.clone().requires_grad_(True)
    r = residual_fn(model, pts)          # 形状 [N]
    dr_dx = torch.autograd.grad(r.sum(), pts, create_graph=False)[0]
    sens_x = dr_dx[:, 0].abs()
    sens_t = dr_dx[:, 1].abs()
    return {
        "median_dx": sens_x.median().item(),
        "median_dt": sens_t.median().item(),
        "q10_dx": sens_x.quantile(0.1).item(),
        "q10_dt": sens_t.quantile(0.1).item(),
    }

若 median_dx 与 median_dt 均低于阈值,同时逐层梯度统计显示深层梯度衰减,则可判定为残差项梯度消失,而非数据损失主导或学习率问题。

验证实验:三类干预与通过判据

定位到断点后,用对照实验确认因果,而不是一次性改多个因素。建议按以下顺序做单变量实验,每个实验训练相同步数并记录同一组指标。

实验改动观察指标通过判据
A 梯度增强 残差损失权重 λ_r 提高 10 倍,或对残差梯度做归一化 深层 ‖∂L_r/∂W‖、相对 L2 误差 深层梯度回升至 ≥1e-3,误差下降
B 网络结构调整 激活函数改为 tanh 或 sin,或加入残差连接 敏感度中位数、梯度衰减比 敏感度中位数 ≥1e-3,衰减比 >0.1
C 输入归一化 将 x,t 归一化到 [-1,1] ∂r/∂x 量级、训练稳定性 敏感度量级提升且不出现数值溢出
若实验 A 单独即可让误差下降,说明主要是损失权重失衡;若只有 B 或 C 有效,则根因在激活饱和或输入尺度。每次只改一个变量,才能把“梯度消失”的归因锁定到具体层或算子。
不要用“残差损失下降”作为解精度提升的证据。残差损失可以被输出层偏置或少数配点吸收,必须同时看逐层梯度范数与敏感度分布,二者一致才可下结论。