2. PINN基本原理回顾

在进入边界条件与损失函数的具体构造之前,有必要把 PINN 的整体运行框架重新梳理一遍。很多初学者在写代码时容易把注意力放在“损失函数怎么写”上,却忽略了损失函数之所以成立,是因为背后有一条完整的计算链:神经网络负责表示解,自动微分负责把解变成导数,物理方程负责把导数组合成残差,优化器负责把残差压到接近零。理解这条链上每一环的输入输出与相互约束,才能判断边界条件应该以什么形式、放在什么位置、以多大权重进入损失。

一、神经网络是解函数的参数化表示

PINN 的第一个基本假设是:待求的物理场可以用一个神经网络来近似。以二维稳态热传导为例,温度场记为 T(x, y),我们构造一个多层感知机 T_θ(x, y),其中 θ 表示全部权重和偏置。网络的输入是坐标 (x, y),输出是标量温度值。对于位移场、速度场这类向量场,只需把输出维度改为 2 或 3 即可。

这里有一个容易被忽视但非常关键的认识:网络本身并不知道任何物理规律,它只是一个光滑的、可微的参数化函数族。它的“物理正确性”完全来自后续损失项的约束。因此网络结构的选择主要服务于表示能力与可微性:激活函数通常选 tanh 或 sin,因为它们的二阶导数连续且不为零;而 ReLU 的二阶导数为零,会让二阶 PDE 的残差恒为零,一般不适合直接用于强形式 PINN。输入坐标通常先做归一化,把物理区域映射到 [-1, 1] 附近,否则不同量纲的坐标会让网络训练极不稳定。

二、自动微分把网络输出变成任意阶导数

传统数值方法用差分近似导数,而 PINN 用自动微分(Automatic Differentiation, AD)精确计算网络输出对输入的导数。以 PyTorch 为例,核心写法是:

import torch

def grad(y, x):
    return torch.autograd.grad(
        y, x,
        grad_outputs=torch.ones_like(y),
        create_graph=True
    )[0]

# 前向:T = T_θ(x, y)
T = model(xy)
T_x = grad(T, xy)[:, 0:1]      # ∂T/∂x
T_y = grad(T, xy)[:, 1:2]      # ∂T/∂y
T_xx = grad(T_x, xy)[:, 0:1]   # ∂²T/∂x²
T_yy = grad(T_y, xy)[:, 1:2]   # ∂²T/∂y²

其中 create_graph=True 是必须的,它让一阶导数本身保留在计算图中,从而可以继续对一阶导数求导得到二阶导数。如果漏掉这个参数,二阶导数会报错或返回 None,这是实际调试中最常见的错误之一。自动微分得到的是解析意义上的精确导数(在浮点精度内),不存在差分步长带来的截断误差,这也是 PINN 相比传统网格方法的一个理论优势。

三、物理方程残差构成主损失项

有了导数,就可以把 PDE 写成残差形式。仍以二维稳态热传导 ∂²T/∂x² + ∂²T/∂y² = 0 为例,残差定义为:

r(x, y) = T_xx(x, y) + T_yy(x, y)

如果网络输出恰好是真实解,那么区域内任意一点的残差都应为零。于是我们在求解域内部采样一批配点(collocation points),把这些点上的残差平方求平均,就得到 PDE 损失:

L_PDE = (1/N_r) Σ r(x_i, y_i)²

配点不需要网格,可以随机采样,也可以在边界附近加密。这一点是 PINN 区别于有限元的重要特征:它把“离散化”从几何网格转移到了损失函数的采样上。配点数量、分布和是否随训练动态调整,都会显著影响收敛质量。

四、三类损失项共同构成总目标

仅有 PDE 残差是不够的,因为满足 PDE 的函数有无穷多个,必须靠边界条件和初始条件把解“钉”在唯一的那一个上。因此总损失通常由三部分组成:

  • PDE 残差损失 L_PDE:在内部配点上约束物理方程;
  • 边界条件损失 L_BC:在边界点上约束 Dirichlet 值或 Neumann 通量;
  • 初始条件损失 L_IC:对瞬态问题,在 t=0 时刻约束场值或时间导数。

总损失写成加权和:L = w_PDE·L_PDE + w_BC·L_BC + w_IC·L_IC。权重不是可有可无的装饰,它直接决定了优化器优先满足哪一类约束。如果 w_BC 过小,网络可能收敛到一个 PDE 残差很小但边界完全错误的“平凡解”;如果 w_BC 过大,边界被强行满足,但内部物理被牺牲,残差在区域内大面积超标。下一节将专门讨论权重的选取与自适应策略,这里先建立“权重即优先级”的直觉。

判断训练是否走在正确方向上的一个实用方法是:分别记录 L_PDE、L_BC、L_IC 三条曲线,而不是只看总损失。若某一条长期居高不下,说明对应约束没有被有效满足,需要检查采样、权重或网络容量,而不是盲目增加迭代次数。

五、整体计算链与训练闭环

把上述环节串起来,一次前向与反向传播的完整路径是:坐标输入 → 网络输出场值 → 自动微分求各阶导数 → 代入 PDE 得残差 → 与边界、初始条件残差加权求和 → 反向传播更新 θ。下图展示了这条计算链以及三类损失项在其中的位置关系。

坐标输入 (x, y, t) 神经网络 T_θ 多层感知机 tanh / sin 激活 输出场值 T 自动微分 一阶 / 二阶导数 T_x, T_y, T_xx, T_yy create_graph=True 物理方程残差 r = T_xx + T_yy 内部配点采样 L_PDE = mean(r²) 边界条件损失 Dirichlet / Neumann L_BC 初始条件损失 t = 0 场值 / 时间导数 L_IC 加权总损失 L = w₁L_PDE + w₂L_BC + w₃L_IC 反向传播更新参数 θ 实线:前向计算链 虚线:约束项接入与反向传播 三类损失共同决定优化方向 权重 w 决定约束优先级

从图中可以看出,边界条件与初始条件并不是独立于网络的“外部模块”,它们同样作用在网络输出上,只是采样点位于边界或初始时刻。这意味着边界损失与 PDE 损失共享同一组参数 θ,优化器在同一个梯度下降过程中同时压低两类残差。理解这一点,就能明白为什么边界条件的写法必须与网络输出的形式匹配:如果边界是 Dirichlet 型,直接约束 T_θ(x_b, y_b) = g;如果是 Neumann 型,则要约束 ∂T_θ/∂n = h,此时边界损失里必须出现自动微分得到的法向导数,而不能只写场值。

六、一个可运行的最小示例结构

把框架落到代码上,训练循环的骨架大致如下(示例参数为教学假设,非实测结果):

# 示例:二维稳态热传导,区域 [0,1]×[0,1]
# 边界:左 T=100,右 T=0,上下绝热(Neumann=0)
for epoch in range(20000):
    # 1) 内部配点,随机采样 2000 点
    xy_r = torch.rand(2000, 2)
    T = model(xy_r)
    T_x = grad(T, xy_r)[:, 0:1]
    T_y = grad(T, xy_r)[:, 1:2]
    T_xx = grad(T_x, xy_r)[:, 0:1]
    T_yy = grad(T_y, xy_r)[:, 1:2]
    L_pde = ((T_xx + T_yy) ** 2).mean()

    # 2) Dirichlet 边界:左右两边各 200 点
    xy_b = sample_dirichlet(200)
    T_b = model(xy_b)
    L_bc = ((T_b - T_target) ** 2).mean()

    # 3) Neumann 边界:上下两边法向导数为 0
    xy_n = sample_neumann(200)
    T_n = model(xy_n)
    T_ny = grad(T_n, xy_n)[:, 1:2]
    L_bc += (T_ny ** 2).mean()

    loss = 1.0 * L_pde + 10.0 * L_bc
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

这段骨架体现了本节的核心结构:PDE 损失用内部随机配点,边界损失用边界采样点,Neumann 边界通过自动微分取法向导数。示例中把边界权重设为 10,是因为边界点数量远少于内部配点,需要放大其影响;这个数值只是教学假设,实际应根据损失量级调整。训练过程中应分别打印 L_pde 与 L_bc,观察两者是否同步下降。若 L_pde 降到很小而 L_bc 停滞,通常说明边界权重不足或边界采样过稀;反之则可能是网络容量不够或配点分布不合理。

回顾整节,PINN 的基本框架可以概括为一句话:用神经网络表示解,用自动微分获得导数,用物理方程残差和边界、初始条件残差共同构造损失,再用梯度下降把损失压到接近零。边界条件与损失函数的构建,本质上就是在这条链上决定“约束写在哪里、以什么形式写、以多大权重写”。把这条链理解透彻,后续讨论 Dirichlet 与 Neumann 的具体实现、硬约束与软约束的取舍、权重自适应策略时,才不会迷失在代码细节里。