news 2026/10/2 8:32:24

PyTorch实战:PINN求解微分方程从入门到避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyTorch实战:PINN求解微分方程从入门到避坑

简介:这份资源面向希望用Python实现物理信息神经网络(PINN)求解微分方程的科研人员、研究生与算法工程师,覆盖从常微分方程到偏微分方程的多类典型问题。包内共22个文件,以17个ipynb交互式Notebook为主,配合3个py模块、1个md说明与1张png示意图,压缩包约889KB,结构轻量便于按需查阅。内容涉及Euler梁、扩散方程、泊松方程(Dirichlet、Neumann、Robin、周期边界)、拉普拉斯方程、洛伦兹系统及ODE方程组等案例,并包含Jacobian-Hessian方法测试与几何、PDE、模型等基础模块,可帮助读者理解PINN的损失构造、边界条件处理与训练流程。目前已有2974人学习下载,适合作为入门到进阶的实践参考,快速搭建可复现的求解实验。

1. 从一条热传曲线说起:PINN 到底在算什么

如果你最近刷到过“pinn最近杀疯了”这类说法,大概率看到的是同一类演示:一个没有网格、没有标签数据的神经网络,硬生生把 Burgers 方程或者热传导方程的解析解拟合了出来。第一次看确实有点玄学,但拆开看,PINN(Physics-Informed Neural Network,物理信息网络)做的事情并不神秘——它把微分方程本身当成损失函数的一部分,让网络在训练中同时满足初始条件、边界条件和方程残差。换句话说,传统数值方法靠网格离散求解,PINN 靠自动微分和优化器“软约束”求解。

这篇面向的是已经会一点 Python、想用 PINN 求解微分方程但不知道从哪下手的人。我会按“最小可跑通 → 参数怎么调 → 坑在哪 → 怎么验证”的顺序讲,代码全部基于 PyTorch,环境用 vscode python 环境配置或 pycharm 配置 python 环境都行。读完你应该能自己改方程、改边界条件,而不是只会跑一个 demo。

2. 用 PyTorch 搭一个最小 PINN:从方程到可训练损失

2.1 为什么选 PyTorch 而不是 MATLAB 或纯 NumPy

热搜里有人问“matlab怎么搭建pinn”“matlab中定义微分方程”,MATLAB 当然能做,Deep Learning Toolbox 也支持自动微分,但生态和调试体验差一截。纯 NumPy 更不行——你需要对输入求导,NumPy 没有计算图。PyTorch 的torch.autograd.grad可以直接对网络输出关于输入求一阶、二阶导数,这是 PINN 的命根子。

选型上我一般这样判断:方程维度低于 3 维、边界规则、想要快速验证想法,用 PyTorch 写 PINN;如果是高维或强间断问题,先别急着上 PINN,传统方法可能更稳。PINN 的优势在于无网格、可反演参数、能融合稀疏观测数据,不是在所有场景都碾压有限差分。

2.2 最小可跑通代码:一维稳态热传导方程

先拿一个最简单的方程练手,避免一上来就被 Burgers 方程劝退。考虑:

d²u/dx² = 0, x ∈ (0, 1) u(0) = 0, u(1) = 1

解析解是 u(x) = x。这个例子足够简单,能让你把注意力放在 PINN 的流程上,而不是数值稳定性。

import torch import torch.nn as nn # 定义网络:输入 x,输出 u(x) class PINN(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential( nn.Linear(1, 32), nn.Tanh(), nn.Linear(32, 32), nn.Tanh(), nn.Linear(32, 1) ) def forward(self, x): return self.net(x) model = PINN() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) # 配置设备 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) # 采样配置点(方程残差约束) x_col = torch.rand(1000, 1, device=device) # 内部点 x_bc0 = torch.zeros(1, 1, device=device) # 左边界 x_bc1 = torch.ones(1, 1, device=device) # 右边界 for step in range(5000): optimizer.zero_grad() # 方程残差:u''(x) 应接近 0 x_col.requires_grad_(True) u = model(x_col) u_x = torch.autograd.grad(u, x_col, torch.ones_like(u), create_graph=True)[0] u_xx = torch.autograd.grad(u_x, x_col, torch.ones_like(u_x), create_graph=True)[0] loss_pde = torch.mean(u_xx ** 2) # 边界条件损失 loss_bc = (model(x_bc0) - 0.0) ** 2 + (model(x_bc1) - 1.0) ** 2 loss = loss_pde + loss_bc loss.backward() optimizer.step() if step % 1000 == 0: print(f"step {step}, loss={loss.item():.6f}")

逻辑说明:x_col.requires_grad_(True)是必须的,否则autograd.grad会报错。create_graph=True让你能对一阶导再求导,得到二阶导。损失由两部分组成——方程残差和边界条件,权重默认都是 1。

参数说明:隐藏层 32 维、2 层,对这个简单问题够用;Tanh激活函数比 ReLU 更适合 PINN,因为 ReLU 的二阶导几乎处处为零,会让方程残差失去梯度信号。学习率 1e-3 是 Adam 的常用起点,5000 步对这个方程绰绰有余。

2.3 训练完怎么验证:别只看 loss 曲线

跑完之后,把解析解和预测解画在一起:

import matplotlib.pyplot as plt x_test = torch.linspace(0, 1, 100, device=device).reshape(-1, 1) with torch.no_grad(): u_pred = model(x_test).cpu().numpy() plt.plot(x_test.cpu().numpy(), u_pred, label="PINN") plt.plot(x_test.cpu().numpy(), x_test.cpu().numpy(), "--", label="Analytic") plt.legend(); plt.xlabel("x"); plt.ylabel("u") plt.show()

如果两条线基本重合,说明流程通了。如果 loss 降不下去,先检查边界条件有没有写错、requires_grad_有没有漏。这个最小例子跑通之后,再换方程就只是改残差表达式的事。

3. 换方程、调权重、加观测:PINN 真正难的地方

3.1 从稳态到瞬态:一维热传导方程的改法

把方程换成:

∂u/∂t = α ∂²u/∂x², x ∈ (0,1), t ∈ (0,1) u(x,0) = sin(πx) u(0,t) = u(1,t) = 0

网络输入从 1 维变成 2 维(x, t),输出仍是u。残差项变成u_t - α * u_xx。代码改动集中在采样和残差计算:

# 网络输入改为 2 维 class PINN(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential( nn.Linear(2, 64), nn.Tanh(), nn.Linear(64, 64), nn.Tanh(), nn.Linear(64, 64), nn.Tanh(), nn.Linear(64, 1) ) def forward(self, x, t): return self.net(torch.cat([x, t], dim=1)) # 采样:内部点、初始条件、边界条件 x_col = torch.rand(2000, 1, device=device) t_col = torch.rand(2000, 1, device=device) x_ic = torch.rand(500, 1, device=device) t_ic = torch.zeros(500, 1, device=device) u_ic = torch.sin(torch.pi * x_ic) x_bc = torch.cat([torch.zeros(200,1), torch.ones(200,1)], dim=0).to(device) t_bc = torch.rand(400, 1, device=device) alpha = 0.01 for step in range(20000): optimizer.zero_grad() x_col.requires_grad_(True); t_col.requires_grad_(True) u = model(x_col, t_col) u_t = torch.autograd.grad(u, t_col, torch.ones_like(u), create_graph=True)[0] u_x = torch.autograd.grad(u, x_col, torch.ones_like(u), create_graph=True)[0] u_xx = torch.autograd.grad(u_x, x_col, torch.ones_like(u_x), create_graph=True)[0] loss_pde = torch.mean((u_t - alpha * u_xx) ** 2) loss_ic = torch.mean((model(x_ic, t_ic) - u_ic) ** 2) loss_bc = torch.mean(model(x_bc, t_bc) ** 2) loss = loss_pde + 10 * loss_ic + 10 * loss_bc loss.backward(); optimizer.step()

这里把初始条件和边界条件的权重提到 10,是因为瞬态问题里如果边界约束太弱,网络会优先满足方程残差,导致边界附近解漂移。权重怎么定没有公式,常见做法是先让各项量级接近,再根据验证误差微调。

3.2 损失权重与采样策略:PINN 调参的核心

PINN 翻车十有八九出在损失权重和采样上。方程残差、初始条件、边界条件三项如果量级差两三个数量级,优化器会被大项主导,小项形同虚设。我一般会做两件事:

第一,训练前先算一遍各项损失的初始值,手动缩放权重让它们在同一量级。第二,用自适应权重,比如每 1000 步根据各项损失的梯度范数调整权重,这是 NTK 类方法的简化版。

采样上,均匀随机采样对平滑解够用,但如果解在某个区域变化剧烈,比如激波附近,均匀采样会欠拟合。常见做法是在残差大的区域加密采样,或者用拉丁超立方采样替代纯随机。

# 简单的自适应权重示例 lambda_bc = 1.0 for step in range(20000): # ... 计算 loss_pde, loss_ic, loss_bc ... if step % 1000 == 0: # 根据损失比例调整,避免某项被淹没 ratio = loss_pde.item() / (loss_bc.item() + 1e-8) lambda_bc = min(max(ratio, 0.1), 100.0) loss = loss_pde + lambda_bc * loss_bc

参数说明:lambda_bc上下限设 0.1 到 100,防止权重爆炸。这个策略不保证最优,但比固定权重稳。

3.3 加稀疏观测数据:PINN 反演参数

PINN 另一个实用场景是已知部分观测点,反演方程里的未知参数。比如热传导方程里 α 未知,你有 10 个位置的温度测量值,可以把 α 设成可训练变量:

alpha = torch.nn.Parameter(torch.tensor(0.05, device=device)) optimizer = torch.optim.Adam(list(model.parameters()) + [alpha], lr=1e-3) # 观测损失 loss_obs = torch.mean((model(x_obs, t_obs) - u_obs) ** 2) loss = loss_pde + 10 * loss_ic + 10 * loss_bc + 100 * loss_obs

观测数据权重通常给大,因为它是硬约束。训练完alpha会收敛到接近真值,这就是 PINN 做参数反演的思路。注意观测点不能太少,否则反演问题本身不适定,α 可能收敛到错误值。

4. PINN 求解微分方程的避坑与排查清单

4.1 损失不下降,先查这五个地方

现象:训练几千步 loss 几乎不动,或者降到某个值就卡住。

原因一:激活函数用了 ReLU。ReLU 二阶导为零,方程残差拿不到有效梯度。解决:换成 Tanh、Sin 或 GELU。

原因二:create_graph没开。求二阶导时如果第一次autograd.grad没设create_graph=True,第二次求导会报错或返回 None。解决:一阶导计算时加上create_graph=True。

原因三:学习率太大。PINN 损失曲面比普通回归复杂,1e-2 经常直接发散。解决:从 1e-3 或 1e-4 起步,配合ReduceLROnPlateau。

原因四:边界条件写反。比如u(1)=1写成了u(1)=0,网络会努力满足错误约束,和方程残差打架。解决:拿解析解或已知特解核对边界。

原因五:采样点太少或分布太偏。内部点少于 500 时,方程残差约束不足。解决:一维问题至少 1000 个内部点,二维以上按维度增加。

4.2 边界附近误差大:权重和采样都要改

现象:内部解拟合很好,但靠近边界处误差明显。

原因:边界条件损失权重相对方程残差太小,网络优先满足内部残差。解决:把边界权重提高 10 到 100 倍,或者在边界附近加密采样点。另一个办法是把边界条件做成硬约束,比如输出用x*(1-x)*net(x)构造,自动满足齐次边界,但这招只对特定边界形式好用。

4.3 训练后期 loss 震荡:优化器和学习率的问题

现象:loss 降到一定程度后来回跳,不收敛。

原因:Adam 后期步长偏大,在极小值附近震荡。解决:在 Adam 之后接一段 L-BFGS 微调,或者用学习率调度器把 lr 降到 1e-5。我一般先 Adam 跑 10000 步,再 L-BFGS 跑 500 步,精度能提一个量级。

4.4 预测解在训练区间外完全失效

现象:在[0,1]上拟合很好,外推到[1,2]直接崩。

原因:神经网络本质是插值,PINN 没有外推能力。解决:如果问题需要外推,把训练区间设得比目标区间大,或者改用能嵌入物理先验的网络结构。别指望 PINN 做 extrapolation,这是它的边界。

4.5 GPU 显存爆了:二阶导的计算图很吃显存

现象:一维问题没事,二维或三维一跑就 OOM。

原因:二阶导需要保留一阶导的计算图,显存占用是普通训练的好几倍。解决:减小 batch 的采样点数,用torch.cuda.amp混合精度,或者把内部点分批计算残差再累加。如果还不行,考虑用 JAX 的jit和vmap,显存效率更好。

5. 用解析解和残差图验证 PINN 解的可信度

训练完一个 PINN,光看 loss 曲线不够,我一般会做三层验证。第一层,如果有解析解,直接算相对 L2 误差:

with torch.no_grad(): u_pred = model(x_test, t_test) u_exact = torch.sin(torch.pi * x_test) * torch.exp(-alpha * torch.pi**2 * t_test) rel_l2 = torch.norm(u_pred - u_exact) / torch.norm(u_exact) print(f"Relative L2 error: {rel_l2.item():.4e}")

相对误差在 1e-3 量级算不错,1e-4 以下说明网络容量和训练都到位了。如果只有 1e-2,检查权重和采样。

第二层,没有解析解时,把方程残差在测试网格上重新算一遍,画成热力图。残差应该在整个区域都接近零,如果某个区域残差明显偏大,说明那里采样不足或解变化剧烈。

第三层,检查物理守恒量。比如热传导方程的总热量应该随时间衰减,如果 PINN 解的总热量不降反升,说明解不可信。这一步能抓住很多 loss 看不出来的问题。

最后说个我自己的习惯:每次换方程,先拿一个已知解析解的特例跑通,确认流程没问题,再换到真正要解的方程。这样出问题时能快速定位是流程 bug 还是方程本身难。PINN 不是万能锤,它在高维、反演、数据融合场景有优势,但在强间断、高雷诺数流动上还不如传统方法。想清楚你的问题适不适合,比调参更重要。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 8:29:57

通达信日线.day文件二进制解析与SQLite入库实战

先把结论放前面:这篇文章要解决的问题,是很多做量化、做复盘、或者单纯想给自己留一份干净行情数据的朋友都会遇到的。通达信系的软件,包括申万宏源金融终端,会把日线行情以二进制文件存在本地,你可以在打开软件的情况…

作者头像 李华
网站建设 2026/10/2 8:29:47

面试官:说一说多线程常见锁的策略

一、为什么面试官总爱问“锁策略”多线程并发编程一直是 Java 后端面试的高频考点,而在并发编程中,“锁”又是绕不开的核心主题。很多同学能背出 synchronized、ReentrantLock、CAS、乐观锁、悲观锁这些名词,但一旦面试官追问“你为什么选择公…

作者头像 李华
网站建设 2026/10/2 8:27:53

Agent 开发实战:知识图谱、向量库与 Wiki 库的加载与结合

1. 引言 在 Agent 开发中,知识库的构建与加载是决定智能体回答质量的关键环节。单一知识源往往难以覆盖复杂场景:知识图谱擅长表达结构化关系,向量库擅长语义相似度检索,Wiki 库则擅长提供规范化的文档知识。这三类知识库本质上构…

作者头像 李华
网站建设 2026/10/2 8:27:48

自动管控档期|场地预约小程序怎么做2026搭建指南

中国信通院2026中小企业数字化调研显示,共享会议室、自习室、运动场馆这类空间商家,用上线上预约系统后,档期冲突问题可大幅减少。人工登记档期容易出现重复预定、信息遗漏,场地预约小程序核心价值就是系统自动管控档期。下面简单…

作者头像 李华
网站建设 2026/10/2 8:27:37

学业预警系统实战:特征工程、不平衡分类与随机森林预警模型

简介:一套完整的学业预警系统项目实践资料包,面向希望将人工智能与Python用于教育管理场景的学习者,解决如何从数据采集、数据处理、模型训练到预警推送构建可用系统的问题。压缩包共530个文件,约3.95MB,主要包含238个…

作者头像 李华