☰
PINN求解微分方程实战:Python Notebooks从入门到避坑
2026/10/11 20:28:22 网站建设 项目流程

简介:这份资源围绕物理信息神经网络(PINN)求解微分方程展开,面向具备一定Python与深度学习基础、希望将神经网络用于科学计算的研究生、工程师及科研人员。内容覆盖常微分方程、泊松方程、拉普拉斯方程、扩散方程、欧拉梁及洛伦兹系统等典型问题,并涉及Dirichlet、Neumann、Robin、周期等多种边界条件,帮助读者理解如何把物理方程约束嵌入损失函数进行求解。资源包共22个文件,以17个ipynb交互式笔记本为主体,配合3个py源码模块、1个md说明文档和1张结果示意图,整体约889KB,结构紧凑便于按案例逐步运行与修改。其中源码模块封装了PDE、模型与几何等基础组件,笔记本则给出可直接复现的实验流程,另含Jacobian-Hessian方法等进阶测试。目前已有2976人学习下载,适合作为入门PINN与微分方程数值求解的实践参考。

1. 从一份 PINN-Notebooks 说起:Python 求解微分方程到底能落地到什么程度

很多人第一次听到 PINN(物理信息神经网络)是在论文里,公式一屏接一屏,真到自己动手时却卡在“这玩意到底怎么跑起来”。我手里这份基于PINN求解微分方程的方法(python).zip就是干这件事的:它把 PINN 求解微分方程拆成了一堆可以直接在 Jupyter 里点开就跑的 Notebook,覆盖 Euler Beam、扩散方程、Poisson 方程(Dirichlet / Neumann / Robin / Periodic 四类边界)、Laplace 圆盘、PDAE 系统、Lorenz 系统、简单 ODE 系统,还单独给了 Jacobian-Hessian 方法的测试用例。配套的pinns geometry.py、PDE.py、model.py是底层模块,data_domain.ipynb负责采样域,README.md交代环境。适合谁?适合已经会一点 Python、被有限差分/有限元网格划分折磨过、想看看无网格方法到底靠不靠谱的工程师和研究生。它不承诺精度吊打传统方法,但能让你在一个下午内把“神经网络解 PDE”从概念变成屏幕上一条收敛曲线。

2. 环境与目录:把 Notebook 跑起来前必须理清的三件事

2.1 依赖安装与版本选择

这份资源是纯 Python 栈,核心依赖是 PyTorch(或 TensorFlow,取决于model.py的实现,常见做法是 PyTorch)、NumPy、Matplotlib,以及 DeepXDE 用于部分 Poisson 算例。我一般会先建一个干净环境,避免和系统里已有的 CUDA 版本打架。下面这套命令在 Linux 和 Windows 的 conda 下都验证过:

# 创建独立环境,Python 3.8~3.10 兼容性最好 conda create -n pinn_env python=3.9 -y conda activate pinn_env # 安装 PyTorch,CPU 版先跑通逻辑,有卡再换 CUDA 版 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 科学计算与绘图 pip install numpy matplotlib scipy jupyter # DeepXDE 只在部分 Poisson 算例里用到,按需装 pip install deepxde

逻辑说明:先隔离环境是因为 PINN 对 PyTorch 版本敏感,torch.autograd.grad的create_graph参数在不同版本行为有差异,混装容易在二阶导上翻车。参数上,Python 3.9 是甜点版本,3.11 以上部分老 Notebook 的np.float别名会报错。DeepXDE 不是所有算例都依赖,DeepXDE Poisson_equation_Dirichlet.ipynb才需要,其余用原生 PyTorch 的可以先不装,省得它把 TensorFlow 后端也拖进来。

2.2 目录结构与模块职责

解压后你会看到 Notebook 和三个.py文件混在一起,这不是随意摆放,而是有调用关系的。pinns geometry.py定义求解域几何(区间、圆盘、边界点采样),PDE.py负责把微分方程残差写成可求导的形式,model.py是网络骨架和训练循环。Notebook 通过import把这些模块拉进来,所以必须从解压根目录启动 Jupyter,否则ModuleNotFoundError会直接糊脸。

# 进入解压后的根目录再启动,不要在上层目录启动 cd PINN-Notebooks-main jupyter notebook

逻辑说明:Python 的模块搜索路径默认包含当前工作目录,Jupyter 启动目录就是工作目录。如果你在上级目录启动,from pinns geometry import ...这种带空格的模块名(实际导入时通常写成pinns_geometry或按 README 约定)会找不到。参数上,建议把整个文件夹放在纯英文路径下,中文路径在 Windows 上偶尔会让 Matplotlib 保存图片失败。

2.3 先跑哪个 Notebook:推荐顺序

别一上来就点Lorenz system.ipynb,混沌系统对初值和网络容量都敏感,容易让你误以为代码坏了。我建议的顺序是:先y'=sin(πx)_cos(πx)这种一阶 ODE 建立信心,再Δu = 2的 Poisson 方程看二阶导怎么处理,然后Euler Beam.ipynb看四阶导(梁方程)的 Jacobian-Hessian 技巧,最后再碰Lorenz system和PDAE_system。data_domain.ipynb可以穿插看,它帮你理解采样点是怎么撒的。

提示:每个 Notebook 第一次运行会现场训练网络,CPU 上几分钟到十几分钟不等,别以为卡死了,看 loss 有没有在动。

3. 核心原理落地:PDE.py 与 model.py 里到底写了什么

3.1 把微分方程变成 loss:残差项怎么构造

PINN 的核心思想不复杂:让网络输出u(x)去逼近真解,然后把微分方程残差、边界条件残差、初值条件残差加权求和当 loss。PDE.py干的就是这件事。以-y'' = π²sin(πx)为例,真解是sin(πx),残差写法如下:

import torch def pde_residual(model, x): # 开启梯度追踪,x 需要 requires_grad x.requires_grad_(True) u = model(x) # 一阶导 u_x = torch.autograd.grad(u, x, grad_outputs=torch.ones_like(u), create_graph=True)[0] # 二阶导,create_graph=True 是关键,否则无法再求导 u_xx = torch.autograd.grad(u_x, x, grad_outputs=torch.ones_like(u_x), create_graph=True)[0] # 残差:-u'' - π²sin(πx) = 0 residual = -u_xx - (torch.pi ** 2) * torch.sin(torch.pi * x) return residual

逻辑说明:create_graph=True是 PINN 的命门,没有它二阶导直接报错或返回 None。参数上,grad_outputs必须和u同形状,通常用torch.ones_like。残差本身不是 loss,后面要取均方。常见做法是把域内残差、边界残差分别算 MSE 再加权,权重比(比如 1:10)直接影响收敛,边界权重太小会导致解“飘”出边界。

3.2 网络结构与采样:model.py 和 data_domain.ipynb 的配合

model.py一般是全连接网络(MLP),激活函数常用tanh或sin。为什么不用 ReLU?因为 ReLU 二阶导恒为零,解二阶 PDE 时残差直接失效,这是新手最容易踩的坑。data_domain.ipynb负责在域内和边界上采样,常见做法是均匀采样加随机扰动,或者拉丁超立方。

import torch.nn as nn class PINN(nn.Module): def __init__(self, layers=[1, 50, 50, 50, 1]): super().__init__() # 用 tanh,保证二阶导非零 self.act = nn.Tanh() self.linears = nn.ModuleList( [nn.Linear(layers[i], layers[i+1]) for i in range(len(layers)-1)] ) def forward(self, x): for i, lin in enumerate(self.linears[:-1]): x = self.act(lin(x)) return self.linears[-1](x)

逻辑说明:层数和宽度是精度与速度的权衡,[1,50,50,50,1]对一维问题是常见起点,二维问题要加宽。参数上,初始化用 Xavier 或默认即可,学习率我一般从1e-3起步,配合StepLR或ReduceLROnPlateau。采样点数不是越多越好,域内 1000~5000 点、边界每侧 100~200 点通常够用,点太多反而拖慢每轮迭代。

3.3 边界条件处理:Dirichlet、Neumann、Robin、Periodic 的差别

这份资源把 Poisson 方程的四种边界都单独做了 Notebook,这是它比很多“一锅炖”教程强的地方。Dirichlet 直接约束u值,Neumann 约束u的法向导数(又要用到 autograd),Robin 是两者线性组合,Periodic 要求左右边界值和导数都相等。以 Neumann 为例:

def neumann_loss(model, x_boundary, normal, g): x_boundary.requires_grad_(True) u = model(x_boundary) u_x = torch.autograd.grad(u, x_boundary, grad_outputs=torch.ones_like(u), create_graph=True)[0] # 法向导数 = g return torch.mean((u_x * normal - g) ** 2)

逻辑说明:Neumann 的坑在于法向方向,一维问题里就是正负号,二维圆盘上要用法向量点乘梯度。参数上,normal要和边界点一一对应。Periodic 边界常见做法是把左右边界的u和u_x差值都塞进 loss,权重给大一点,否则解在边界处会断开。

4. 避坑与排查:跑 PINN 时最容易被劝退的五个现象

4.1 loss 不降反升,或者降到某个值就卡住

现象:训练几百轮后 loss 在 1e-2 附近震荡,解的形状完全不对。原因:学习率太大,或者边界 loss 权重太小,网络只顾着满足域内方程而忽略边界。解决:先把学习率降到1e-4,再把边界权重调到域内的 10~100 倍,观察 loss 曲线是否重新下降。我一般会先单独训练边界几轮,让网络“记住”边界,再联合训练。

4.2 二阶导报错 “element 0 of tensors does not require grad”

现象:运行-y''=...类 Notebook 时直接抛 RuntimeError。原因:输入x没有requires_grad_(True),或者第一次求导时create_graph=False。解决:检查PDE.py里残差函数开头有没有x.requires_grad_(True),以及两次torch.autograd.grad是否都带了create_graph=True。这是血泪经验,少一个参数整条链路断掉。

4.3 解在边界处“飞出去”,域内却拟合得很好

现象:画图时域内曲线贴着真解,边界附近突然翘起。原因:边界采样点太少,或者边界 loss 用了 mean 而域内用了 sum,量级不对等。解决:把边界采样点加密到每侧 200 以上,统一用torch.mean算 loss,再检查权重。Periodic 边界尤其要注意,左右两边都要采样。

4.4 换台机器或换 PyTorch 版本后结果复现不了

现象:同一份 Notebook,别人跑出漂亮解,你跑出来一团糟。原因:随机种子没固定,或者 PyTorch 版本差异导致初始化不同。解决:在 Notebook 开头加torch.manual_seed(42)、np.random.seed(42),并记录 PyTorch 版本。常见做法是把种子写进model.py的初始化函数里,保证每次运行一致。

4.5 DeepXDE 算例报后端冲突

现象:DeepXDE Poisson_equation_Dirichlet.ipynb报 TensorFlow 相关错误。原因:DeepXDE 默认后端可能是 TensorFlow,而你环境里只装了 PyTorch。解决:装 DeepXDE 时指定后端,或在代码开头设置os.environ["DDE_BACKEND"] = "pytorch"。参数上,DeepXDE 对 PyTorch 版本也有要求,太新可能不兼容,建议按它文档的推荐版本装。

注意:避坑的核心不是背现象,而是学会看 loss 分量。把域内 loss、边界 loss 分开打印,哪个大就调哪个,比盲目调网络结构有效得多。

5. 进阶技巧:用 Jacobian-Hessian 方法啃高阶方程与混沌系统

5.1 为什么四阶梁方程需要 Jacobian-Hessian

Euler Beam.ipynb和Jacobian-Hessian methods for Laplace equation.ipynb是这份资源里最有含金量的部分。梁方程是四阶导,如果老老实实嵌套四次autograd.grad,计算图会爆炸,显存和速度都吃不消。Jacobian-Hessian 方法的思路是把高阶导拆成矩阵运算,利用 Hessian 的对称性减少重复计算。常见做法是显式构造 Jacobian 和 Hessian,再用它们组合出高阶残差。

def hessian_diag(model, x): # 计算 Hessian 的对角线,用于拉普拉斯类算子 x.requires_grad_(True) u = model(x) u_x = torch.autograd.grad(u, x, grad_outputs=torch.ones_like(u), create_graph=True)[0] # 逐分量再求导得到对角线 hess_diag = [] for i in range(x.shape[1]): grad_i = torch.autograd.grad(u_x[:, i].sum(), x, create_graph=True)[0] hess_diag.append(grad_i[:, i]) return torch.stack(hess_diag, dim=1)

逻辑说明:这段代码算的是 Hessian 对角线,对拉普拉斯算子Δu正好够用。参数上,x.shape[1]是维度,一维问题就退化成二阶导。相比嵌套求导,这种写法在二维以上问题里能省不少显存。Jacobian-Hessian methods for ODE system.ipynb则把它推广到方程组,适合PDAE_system.ipynb那种耦合系统。

5.2 Lorenz 系统与 PDAE:混沌和耦合系统的验证方法

Lorenz system.ipynb是检验 PINN 鲁棒性的试金石。Lorenz 对初值极度敏感,PINN 很难长时间精确拟合,所以验证时不要只看最终图,要看短时间窗口内的轨迹重合度和参数反演能力。我一般会把时间域切成若干段,逐段训练再拼接,比一次性训整个时间域稳。PDAE_system.ipynb是偏微分代数方程,约束更多,训练时要把代数约束单独作为一项 loss,权重给高。

Notebook方程类型关键技巧验证重点
Euler Beam四阶 ODEJacobian-Hessian边界弯矩是否为零
Lorenz system一阶混沌 ODE 组分段时间训练短窗轨迹重合
PDAE_system偏微分代数代数约束单独 loss约束残差量级
Laplace on a disk二维椭圆极坐标采样圆心处是否光滑

5.3 一个我常用的收敛判断习惯

跑完任何 Notebook,我不会只看最后那张对比图。我会把 loss 历史导出来,看三个量:域内残差是否降到 1e-4 以下、边界残差是否比域内低一个量级、解的最大值是否和真解同量级。三个都满足才认为这次训练有效。从那以后我每次开新算例都强制走一遍这个检查,省得被一张“看起来像”的图骗了。希望这份 Notebook 能帮你把 PINN 从论文里的符号变成手里能跑、能改、能验证的代码。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询