2. 开发环境与工具链搭建

PINN(物理信息神经网络)的开发本质上是“数值计算 + 深度学习”两条技术路线的交汇:一边要用 NumPy 做张量化的物理残差计算,一边要用 PyTorch 的自动微分把 PDE 残差对网络参数的梯度传回去。因此,环境搭建的目标不是把包装齐,而是让这两条链路能顺畅对接。本节按“科学计算栈 → PyTorch 张量与自动微分 → GPU 配置 → 最小可运行示例”的顺序展开,每一步都给出可验证的判断依据。

一、Python 科学计算栈:先保证数值层可靠

推荐使用 Python 3.10~3.11,配合独立虚拟环境隔离依赖。核心包包括 NumPy(数组运算与采样)、SciPy(稀疏矩阵、积分与优化参考解)、Matplotlib(残差曲线与场分布可视化)。安装后不要只看 pip list,而应做一次数值自检,确认浮点精度与线性代数后端正常:

python -c "import numpy as np, scipy, torch; \
print('numpy', np.__version__); \
print('scipy', scipy.__version__); \
print('torch', torch.__version__, 'cuda', torch.cuda.is_available()); \
A=np.random.rand(200,200); print('det-ok', np.isfinite(np.linalg.det(A)))"

若 det-ok 输出 True,说明 BLAS/LAPACK 链接正常。对 PINN 而言,NumPy 主要承担配点采样、边界条件构造和结果后处理;真正参与梯度回传的张量必须来自 PyTorch,二者之间用 torch.from_numpy 或 .numpy() 转换时要注意 dtype 一致(统一用 float32 或 float64),否则会在残差计算中引入隐式类型提升,拖慢速度甚至报错。

二、PyTorch 基础与自动微分机制

PINN 的网络通常很浅(3~5 层全连接,每层 32~64 个神经元),但输入需要求二阶导,因此必须理解 autograd 的工作方式。关键点有三:其一,输入坐标 x 必须设置 requires_grad=True,否则对输入的导数无法计算;其二,求导用 torch.autograd.grad 并设置 create_graph=True,才能对一阶导再求二阶导;其三,网络参数由优化器管理,不需要手动清零梯度图。

import torch, torch.nn as nn

class MLP(nn.Module):
    def __init__(self, width=32):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(1, width), nn.Tanh(),
            nn.Linear(width, width), nn.Tanh(),
            nn.Linear(width, 1))
    def forward(self, x):
        return self.net(x)

model = MLP()
x = torch.linspace(0, 1, 50, requires_grad=True).view(-1, 1)
u = model(x)
du = torch.autograd.grad(u, x, torch.ones_like(u), create_graph=True)[0]
d2u = torch.autograd.grad(du, x, torch.ones_like(du), create_graph=True)[0]
print(u.shape, du.shape, d2u.shape)   # 均为 torch.Size([50, 1])

这段代码是后续所有 PDE 残差的基础。需要特别提醒:激活函数选 Tanh 而非 ReLU,因为 ReLU 的二阶导几乎处处为零,会让二阶 PDE 的残差失去意义。另外,torch.ones_like(u) 作为 grad_outputs 表示对每个输出分量求导后按 1 加权求和,这是标量化处理的标准写法。

三、GPU 环境配置与设备一致性

若使用 NVIDIA 显卡,安装带 CUDA 的 PyTorch 版本后,用 torch.cuda.is_available() 判断可用性,并打印设备名与显存。GPU 加速对 PINN 的收益主要体现在大批量配点(如每步 1 万~10 万个点)上;小规模问题用 CPU 反而更省心。无论用哪种设备,模型、输入张量和常数张量必须位于同一设备,否则会抛出 device mismatch 错误。推荐在脚本开头统一定义 device = torch.device("cuda" if torch.cuda.is_available() else "cpu"),并在构造输入后立即 .to(device)。

显存不足时优先减小配点批量,而不是盲目缩小网络;PINN 的精度对配点密度往往比对网络宽度更敏感。训练中可用 torch.cuda.empty_cache() 缓解碎片,但不要在每个训练步调用,否则会显著拖慢速度。

四、第一个可运行示例:一维稳态热传导

用一个有解析解的问题验证整条链路最稳妥。取方程 u''(x) = 0,边界 u(0)=0, u(1)=1,解析解为 u(x)=x。损失由两部分组成:内部配点上的 PDE 残差均方值,以及两个边界点的 Dirichlet 条件偏差。训练若干步后,若边界损失与残差损失都降到 1e-4 量级,且预测曲线与直线 y=x 基本重合,即可认为环境与自动微分链路全部打通。

opt = torch.optim.Adam(model.parameters(), lr=1e-3)
x_col = torch.linspace(0, 1, 200, requires_grad=True).view(-1,1).to(device)
xb = torch.tensor([[0.],[1.]], device=device)
ub = torch.tensor([[0.],[1.]], device=device)
model.to(device)
for step in range(2000):
    opt.zero_grad()
    u = model(x_col)
    du = torch.autograd.grad(u, x_col, torch.ones_like(u), create_graph=True)[0]
    d2u = torch.autograd.grad(du, x_col, torch.ones_like(du), create_graph=True)[0]
    loss_pde = (d2u**2).mean()
    loss_bc = ((model(xb) - ub)**2).mean()
    loss = loss_pde + 100.0 * loss_bc
    loss.backward(); opt.step()
    if step % 500 == 0:
        print(step, loss_pde.item(), loss_bc.item())

边界损失乘以 100 是常见的加权手段,用来抵消边界点数量远少于配点带来的量级差异。实际运行时,前几百步残差下降较快,之后趋于平缓;若损失长期停在 1e-2 以上,应检查 create_graph 是否遗漏、输入是否忘记 requires_grad、以及边界权重是否过大导致优化震荡。

PINN 最小示例的计算链路与设备流向 配点采样 x_col (200点) 边界点 x=0, x=1 requires_grad 输入张量开启求导 MLP 前向 1→32→32→1, Tanh autograd 求导 du, d2u (create_graph) 损失合成 L_pde + 100·L_bc Adam 更新 lr=1e-3 反向传播迭代(每步清零梯度后重复) 设备一致性:model / x_col / xb / ub 全部 .to(device);CPU 与 CUDA 混用会触发 device mismatch 收敛判据:L_pde 与 L_bc 均降至 1e-4 量级,预测曲线与解析解 u(x)=x 重合

把上述脚本跑通后,建议做一次小实验加深理解:将 create_graph=True 去掉,观察程序在求 d2u 时是否报错;再把激活函数换成 ReLU,观察 PDE 残差是否退化为接近零的“假收敛”。这两个对照能让你真正记住自动微分在 PINN 中的两个关键约束。环境搭建的完成标准不是装完包,而是这条“采样—前向—二阶导—损失—反传”的链路在目标设备上稳定跑通并给出可解释的数值结果。