
简介这份资源围绕物理信息神经网络PINN求解微分方程展开面向具备一定Python与深度学习基础、希望将神经网络用于科学计算的研究生、工程师及科研人员帮助解决传统数值方法在复杂边界条件与高维问题上建模繁琐、网格依赖强等痛点。压缩包共22个文件以17个ipynb交互式笔记本为主辅以3个py源码模块、1个md说明文档和1张png示意图整体约889KB涵盖PDE、model、pinns geometry等核心代码与实验记录。内容涉及泊松方程、拉普拉斯方程、扩散方程、欧拉梁、洛伦兹系统及多种ODE/PDE边值问题并包含Dirichlet、Neumann、Robin、周期边界等不同设定还测试了Jacobian-Hessian方法在ODE系统与拉普拉斯方程中的表现。已有2976人学习下载读者可据此快速复现PINN求解流程理解损失构造、边界处理与网络训练细节并在此基础上迁移到自身微分方程问题。1. 从一条弹簧振子曲线说起PINN 到底在算什么你手上有一组物理实验数据或者一条来自仿真软件的位移-时间曲线背后明明对应着一个微分方程但方程里的阻尼系数、刚度参数你并不完全清楚。传统做法是先用差分法估参数再拿数值积分去拟合中间误差一层层放大最后曲线对不上你还得回头怀疑是数据噪声还是模型结构错了。PINNPhysics-Informed Neural Network物理信息网络换了个思路把微分方程本身当成损失函数的一部分让神经网络在拟合观测数据的同时被迫满足物理规律。它解决的不是“解一个已知方程”而是“在数据稀疏、参数未知、边界条件复杂的情况下把方程约束和数据约束揉进同一个优化目标里”。适合谁手头有 Python 基础、懂一点自动微分、被正问题或反问题折磨过的工程人员和研究生。这一章不铺公式先把这件事的边界讲清楚。PINN 的核心竞争力在于它把“求解微分方程”转化成了“训练一个网络”。传统数值方法有限差分、有限元需要在空间和时间上划网格网格越密精度越高但维度一高就遭遇维数灾难。PINN 用神经网络做函数逼近器输入是坐标比如 x、t输出是待求物理量比如 u它天然无网格高维问题上不会因为网格爆炸而崩掉。更关键的是自动微分让方程里的导数项可以直接对网络输出求导不需要手动推导离散格式。这意味着你写残差的时候几乎就是把方程原样抄进代码。但别急着上头。PINN 不是万能替代品。它在高维、反问题、数据同化场景下有优势但在需要极高精度、强激波、多尺度湍流的问题上收敛慢、训练不稳定是常态。我见过太多人拿 PINN 去解一个一维热传导结果发现有限差分两秒出结果PINN 训了半小时还在震荡。所以第一件事判断你的问题值不值得上 PINN。判断标准很简单——如果你的方程维度低、边界规则、精度要求高传统方法更划算如果你的观测数据少、方程参数未知、区域不规则PINN 才真正开始发光。这一章先立住三个认知第一PINN 的损失函数由数据项和物理残差项组成两者权重需要调第二网络结构不是越深越好激活函数的选择直接影响导数计算的质量第三训练过程本质是一个约束优化不是普通回归。后面几章会围绕这三个认知把代码、参数、坑一个个拆开。2. 用 PyTorch 搭一个最小 PINN从方程到可运行代码2.1 选型理由为什么是 PyTorch 而不是别的框架做 PINN 的框架选择主流是 PyTorch、TensorFlow 和 JAX。我一般推荐 PyTorch原因很实际自动微分接口直观torch.autograd.grad对高阶导的支持稳定调试时能直接打印中间张量社区里 PINN 相关实现也最多。TensorFlow 的GradientTape也能做但高阶导嵌套写起来啰嗦JAX 的jit和vmap性能好但函数式编程风格对新手不友好调试报错信息也不够直观。如果你已经装了 Python直接上 PyTorch 就行。环境准备不复杂但版本要对齐。Python 3.8 以上都可以PyTorch 建议 1.10 以上因为早期版本对create_graphTrue的高阶导支持有坑。安装命令如下pip install torch torchvision numpy matplotlib如果你用 GPU去 PyTorch 官网按 CUDA 版本选对应命令别直接pip install torch装成 CPU 版后面训练慢到怀疑人生。装完验证一下import torch print(torch.__version__) print(torch.cuda.is_available())如果第二行输出False而你有显卡说明装错了回去重装 CUDA 版本。2.2 一个最小可运行例子一维阻尼振子我们拿一个经典问题开刀阻尼谐振子。方程是d²u/dt² 2ζω₀ du/dt ω₀² u 0其中 ω₀ 是自然频率ζ 是阻尼比。假设我们只知道初始条件 u(0)1、u(0)0以及若干稀疏观测点想反推出 ζ 和 ω₀同时让网络输出满足方程。这就是典型的 PINN 反问题。先定义网络。PINN 的网络不需要太深一般 3 到 5 层全连接就够每层 32 到 128 个神经元。激活函数是关键tanh是默认选择因为它的二阶导光滑且不为零ReLU的二阶导几乎处处为零会导致物理残差里的二阶导项失效千万别用。代码import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt class PINN(nn.Module): def __init__(self, layers[1, 64, 64, 64, 1]): super().__init__() modules [] for i in range(len(layers) - 1): modules.append(nn.Linear(layers[i], layers[i1])) if i len(layers) - 2: modules.append(nn.Tanh()) self.net nn.Sequential(*modules) # 可训练物理参数初始猜测 self.zeta nn.Parameter(torch.tensor(0.1)) self.omega0 nn.Parameter(torch.tensor(1.0)) def forward(self, t): return self.net(t)这里把 ζ 和 ω₀ 设成nn.Parameter它们会随着训练自动更新这就是反问题的实现方式。注意初始值别设得太离谱否则残差一开始就爆炸梯度直接 NaN。接下来构造损失函数。物理残差项是def physics_residual(model, t): t.requires_grad_(True) u model(t) du torch.autograd.grad(u, t, grad_outputstorch.ones_like(u), create_graphTrue)[0] d2u torch.autograd.grad(du, t, grad_outputstorch.ones_like(du), create_graphTrue)[0] residual d2u 2 * model.zeta * model.omega0 * du model.omega0**2 * u return residualcreate_graphTrue必须加否则二阶导无法回传。这是新手最容易翻车的地方——不加这个参数程序不报错但物理残差的梯度传不回去训练半天参数不动。数据项损失用观测点t_obs torch.tensor([[0.0], [1.0], [2.0], [3.0]], dtypetorch.float32) u_obs torch.tensor([[1.0], [0.5], [-0.2], [-0.6]], dtypetorch.float32) def data_loss(model): u_pred model(t_obs) return torch.mean((u_pred - u_obs)**2)物理残差在配点上采样配点可以随机生成也可以均匀撒点t_phys torch.linspace(0, 5, 200).reshape(-1, 1) def total_loss(model, lambda_phys1.0): res physics_residual(model, t_phys) loss_p torch.mean(res**2) loss_d data_loss(model) return loss_d lambda_phys * loss_plambda_phys是物理项权重这个参数非常敏感。太小网络只拟合数据方程约束形同虚设太大数据被忽略网络输出一个满足方程但偏离观测的解。我一般从 1.0 开始试观察两项损失的数量级如果物理项比数据项大两个数量级以上就调小。训练循环model PINN() optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(5000): optimizer.zero_grad() loss total_loss(model) loss.backward() optimizer.step() if epoch % 500 0: print(fEpoch {epoch}, Loss: {loss.item():.6f}, fzeta: {model.zeta.item():.4f}, omega0: {model.omega0.item():.4f})跑完 5000 轮如果一切正常ζ 和 ω₀ 会收敛到接近真实值。你可以把真实值设成 ζ0.15、ω₀1.2 生成观测数据看网络能不能反推出来。2.3 参数怎么设学习率、网络宽度、配点数量学习率用 Adam 默认的 1e-3 起步如果损失震荡就降到 1e-4。网络宽度 64 是甜点区32 可能欠拟合128 以上训练变慢且容易过拟合噪声。配点数量 200 到 1000 之间太少物理约束不够太多每轮计算量大。我一般先 200 个点跑通再逐步加到 500。还有一个隐藏参数配点的分布。均匀撒点适合解光滑的问题如果解在某个区域变化剧烈配点要往那里加密。常见做法是用残差大小做自适应采样但那是进阶技巧先把均匀撒点跑通再说。训练轮数不是越多越好。PINN 的损失下降曲线经常是阶梯状平一段时间突然掉一截。如果你看到损失卡住不动先别急着加轮数检查学习率、权重和网络结构。有时候换个激活函数或者调一下lambda_phys比多跑一万轮管用。3. 把 PINN 用到反问题参数识别与边界条件处理3.1 反问题的损失设计数据项和物理项怎么平衡反问题和正问题的区别在于正问题里方程参数已知网络只负责逼近解反问题里参数未知网络要同时学解和学参数。这意味着损失函数里数据项的作用更重因为参数是靠数据锚定的。如果数据项权重太低参数会漂到一个满足方程但不符合观测的值上。我的经验是反问题里lambda_phys从 0.1 到 1.0 之间调先让数据项主导再慢慢加物理约束。具体操作前 1000 轮只用数据损失训练让网络先拟合观测然后加入物理残差继续训练。这种分阶段策略比一上来就联合训练稳定得多。还有一个技巧对参数加边界约束。比如 ζ 必须在 0 到 1 之间ω₀ 必须为正。可以在损失里加惩罚项def param_penalty(model): penalty 0.0 if model.zeta 0 or model.zeta 1: penalty 100 * (model.zeta - 0.5)**2 if model.omega0 0: penalty 100 * model.omega0**2 return penalty这不是必须的但能防止参数跑飞。尤其是训练初期梯度噪声大参数可能瞬间跳到负值没有惩罚项的话后面很难拉回来。3.2 边界条件怎么进损失硬约束与软约束边界条件在 PINN 里有两种处理方式。软约束是把边界条件当成损失的一项比如loss_bc mean((u(0) - u0)**2)和物理残差、数据项一起优化。硬约束是改造网络输出让它在边界上自动满足条件比如令u(t) u0 t * net(t)这样 t0 时 u 恒等于 u0。软约束实现简单但权重难调硬约束不需要调边界权重但网络结构要针对问题定制。我一般先用软约束跑通如果边界误差降不下去再考虑硬约束。对于周期边界条件硬约束更优雅比如用傅里叶特征或者周期激活函数。一个常见的翻车场景边界条件权重设得太大网络为了满足边界牺牲了内部区域的拟合结果解在中间区域完全偏离。解决办法是观察边界损失和内部残差的数量级让它们保持在同一量级。如果边界损失比内部残差小两个数量级说明边界约束太弱反之则太强。3.3 用观测数据反推阻尼系数完整代码与结果解读把前面的代码串起来生成一组带噪声的观测数据看 PINN 能不能反推出 ζ 和 ω₀。真实值设 ζ0.15、ω₀1.2用数值积分生成 20 个观测点加 2% 高斯噪声。训练 10000 轮每 1000 轮打印参数。结果解读要注意参数收敛曲线不是单调的可能先上升后下降也可能在真实值附近震荡。判断收敛的标准不是参数完全不动而是损失不再显著下降且参数波动小于 1%。如果参数一直在漂说明数据量不够或者噪声太大PINN 不是魔法数据质量决定上限。还有一个细节观测点的分布影响参数可辨识性。如果观测点全集中在 t0 附近阻尼系数很难反推因为阻尼效应在后期才明显。所以实验设计阶段就要考虑观测点覆盖整个时间区间。4. 避坑与排查PINN 训练中常见的五个翻车现场4.1 损失不下降梯度为 NaN现象训练几轮后损失变成 NaN或者损失一直停在初始值附近不动。原因最常见的是学习率太大导致梯度爆炸其次是物理残差里出现了除零或者对数负数还有可能是create_graph没加二阶导梯度断链。解决先把学习率降到 1e-4 甚至 1e-5 试检查方程里有没有1/u或者log(u)这种项如果有给 u 加一个极小值保护确认torch.autograd.grad里create_graphTrue和grad_outputs都写了。如果还不行把物理残差的表达式打印出来看哪一项数值异常。4.2 物理项损失降了但数据拟合很差现象物理残差很小但网络输出和观测点对不上参数也偏离真实值。原因lambda_phys太大网络只顾满足方程忽略了数据。或者配点太多数据点太少优化被物理项主导。解决调小lambda_phys或者增加观测点数量。也可以分阶段训练先数据后物理。检查两项损失的数量级让它们在同一量级内。4.3 参数反推结果不稳定每次训练都不一样现象同样的数据换一个随机种子反推的参数差很多。原因PINN 的损失曲面非凸初始化对结果影响大。数据噪声大或者观测点少也会导致参数不可辨识。解决多跑几次取平均或者用集成方法。增加观测点降低噪声。对参数加先验约束缩小搜索范围。如果参数物理意义明确可以固定一个、反推另一个降低问题维度。4.4 训练速度慢GPU 利用率低现象每轮训练耗时很长GPU 显存占用低计算瓶颈在 CPU。原因配点数量太多或者网络太大。也可能是数据在 CPU 和 GPU 之间频繁拷贝。解决减少配点数量先用少量点跑通再增加。网络宽度降到 32 或 64。把所有张量提前移到 GPU 上避免在训练循环里做.cuda()转换。用torch.compile或者混合精度训练加速但注意混合精度可能影响二阶导精度。4.5 边界条件满足但内部解震荡现象边界处误差很小但解在内部区域出现非物理震荡。原因网络过拟合噪声或者物理残差在内部区域约束不够。激活函数选择不当也会导致高频震荡。解决增加配点密度尤其是在震荡区域。换用更光滑的激活函数比如tanh换成sin或者GELU。加正则化项比如对网络权重的 L2 惩罚。如果震荡来自数据噪声先做数据平滑再训练。5. 进阶技巧自适应权重与课程学习让 PINN 真正收敛5.1 自适应权重让损失自己找平衡固定lambda_phys的问题是训练过程中数据项和物理项的下降速度不一样前期数据项主导后期物理项主导固定权重很难兼顾。自适应权重的基本思路是根据两项损失的梯度范数动态调整权重让它们在反向传播时对网络参数的更新贡献相当。一个简单实现是梯度归一化def adaptive_loss(model, t_phys, t_obs, u_obs, alpha0.9): loss_d data_loss(model, t_obs, u_obs) res physics_residual(model, t_phys) loss_p torch.mean(res**2) grad_d torch.autograd.grad(loss_d, model.net[0].weight, retain_graphTrue, allow_unusedTrue)[0] grad_p torch.autograd.grad(loss_p, model.net[0].weight, retain_graphTrue, allow_unusedTrue)[0] norm_d grad_d.norm() if grad_d is not None else torch.tensor(1.0) norm_p grad_p.norm() if grad_p is not None else torch.tensor(1.0) lambda_hat norm_d / (norm_p 1e-8) return loss_d lambda_hat * loss_p这个权重每轮更新alpha是平滑系数防止权重剧烈波动。实际用的时候lambda_hat可能变化几个数量级建议加一个上限截断比如最大 100。5.2 课程学习先易后难分阶段训练课程学习的思路是先把问题简化让网络学一个粗略解再逐步增加难度。具体操作第一阶段只用数据损失配点少、训练轮数少第二阶段加入物理残差配点增加第三阶段加入边界条件和高阶导数约束。每个阶段的学习率递减。这种策略在反问题上尤其有效因为参数识别需要数据先锚定一个大致范围再靠物理约束精调。如果一上来就联合训练参数可能在早期就被物理项拉偏后面很难纠正。5.3 验证方法怎么判断 PINN 解是对的PINN 没有解析解可比时验证靠三条第一看物理残差在配点之外的测试点上是否也小如果只在训练配点上小说明过拟合第二看观测点之外的预测是否符合物理直觉比如能量是否单调衰减第三换一组初始条件重新训练看参数反推结果是否一致。我自己的习惯是每次训练完都把残差分布画出来看有没有局部区域残差异常大。如果有说明那个区域配点不够或者解变化剧烈需要加密配点。这个习惯帮我省了很多次重新训练的时间。最后说一句血泪经验PINN 的调参没有银弹每个问题都要重新试。但只要你把损失数量级、梯度范数、残差分布这三个东西盯住大部分问题都能定位到原因。希望帮到你。本文还有配套的精品资源点击获取