你打开PyCharm,装好了torch,准备跑一个PINN,但搜来搜去全是MNIST和房价预测,没有一篇把物理信息神经网络从头讲到能跑通Helmholtz方程的。这其实是PINN入门最尴尬的地方:原理人人都能说两句“把PDE放进损失函数”,但真到自己写代码,网络怎么搭、二阶导怎么求、边界条件怎么加权重、为什么loss降不下去,全是坑。
这篇文章我就用二维Helmholtz方程当靶子,把整个PINN的PyTorch实现从头到尾拆一遍。你不需要额外装任何求解器,只需要numpy、torch和matplotlib,就能看着神经网络一步步逼近解析解。这篇文章适合有一定PyTorch基础、想认真把PINN跑通并理解背后原理的人。
1. 物理信息神经网络的原理:不是“数据拟合”,而是“物理约束”
1.1 从传统数值方法到PINN
传统求解偏微分方程(PDE)的主流路线是把求解域网格化。有限差分法用差分公式近似导数,有限单元法把域切成小片,在每个单元上假设插值函数,然后组装一个大型代数方程组。这类方法非常成熟,但有一个天然瓶颈:网格生成本身就占掉大量前处理时间,一旦求解域形状不规则,或者需要对某个区域局部加密,网格处理的工作量会迅速失控。
PINN走的是完全不同的路径。它不画网格,而是用一个神经网络直接表示解函数 u(x,y)。网络的可训练参数是权重和偏置,输入是坐标 (x,y),输出是 u(x,y) 的近似值。训练时不需要任何真实的解数据——至少不需要内部点上的标签——只需要强制网络输出满足两件事:
- 在计算域内部,代入方程后残差等于零;
- 在边界上,满足给定的边界条件。
这种思路的本质是把微分方程求解变成一个优化问题。你不需要为整个域建立离散代数方程,只需要求神经网络在采样点上让物理方程成立即可。这个理念最早由Raissi等人系统化,已经在地球物理、流体力学、结构分析里有了大量应用。
1.2 一个简单直觉:把方程本身变成损失函数
普通的三层神经网络只能拟合数据映射,但是在PINN里,网络的输出是连续可导的,因为激活函数和线性变换的复合函数整体光滑。神经网络是一个无限可微函数,如果激活函数选tanh这种光滑函数,网络输出对输入坐标的偏导数就存在,而这个导数可以通过自动微分(autograd)精确计算,不需要差分近似,也没有网格。
我们定义损失函数为两部分之和:
- PDE残差损失:将网络输出代入原方程(比如 Helmholtz 方程),计算左右差值,目标是让这个差值在全域趋近于0。
- 边界损失:在求解域的边界上采样,让网络输出逼迫给定的边界条件。
训练目标就是最小化两者加权和。网络没有见过解析解,却能在训练结束后逼近真实解。这不是魔法,而是因为PDE已经提供了足够强的约束信息,神经网络拟合的是满足物理规律的函数,而不是单纯拟合标签。
1.3 为什么选择Helmholtz方程作为案例
Helmholtz方程实际上是声学、电磁学里波动方程做时谐假设后得到的频域方程。它的形式是:
∇²u + k²u = f
k 是波数,代表空间振荡频率。这个方程好在两点:
- 它有我们可以手写的精确解(比如三角函数组合),方便验证网络训练得对不对;
- 它的解是高频振荡的,这恰恰是神经网络最难拟合的情形之一,踩坑经验充分,最能体现PINN调参的细节。
如果能把这个方程跑通,再迁移到其他PDE,思路是完全一致的。所以这就是一个“会一个就会一串”的典型案例。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Helmholtz方程问题设定与真解构造
2.1 方程形式与定义域
我选的计算域是单位正方形 [0,1]×[0,1],边界条件是Dirichlet型,即边界上 u=0。
我们要求解的具体方程是:
∂²u/∂x² + ∂²u/∂y² + k²u = f(x,y), (x,y) ∈ (0,1)×(0,1)
u(x,y) = 0, (x,y) ∈ ∂Ω
注意这个 f 不是随便给定的,它由真解代入方程倒推出来。这样做的好处是:我们知道 u_true,可以算出精确的 f,然后用网络去预测 u,最后把网络输出和 u_true 对比,就能量化误差。
2.2 通过“人工真解”验证PINN的正确性
我选真解为:
u_true(x,y) = sin(mπx) sin(nπy)
这组函数天然满足边界上的零值,同时在定义域内部光滑。对它求二阶导:
∂²u/∂x² = -m²π² sin(mπx) sin(nπy)
∂²u/∂y² = -n²π² sin(mπx) sin(nπy)
所以左边等于:
(-(m²+n²)π² + k²) sin(mπx) sin(nπy)
为了让这个式子等于 f,于是:
f(x,y) = (k² - (m²+n²)π²) sin(mπx) sin(nπy)
就这么简单。取 k=2, m=1, n=1 的时候,k² - 2π² ≈ 4 - 19.74 = -15.74,所以 f 是负数乘一个正弦波。如果你想增加解的振荡频率,把 m、n 调大就行,这也为后面的高频测试留了伏笔。
2.3 边界条件的处理方式
对于 Dirichlet 边界,不需要在损失里搞复杂的外推。直接在四条边上均匀取点,比如每条边取 N_boundary 个点,总共 4×N_boundary 个点,用网络在这些点的输出与0的均方误差作为边界损失。边界点在代码里可以通过固定一个坐标为0或1,另一个坐标随机或均匀取值生成:
底部边 y=0,x∈[0,1];
顶部边 y=1,x∈[0,1];
左边 x=0,y∈[0,1];
右边 x=1,y∈[0,1]。
核心变量汇总如下表
