资讯动态

物理信息神经网络(PINN)实战:Python求解微分方程与Burgers方程

发布时间:2026/9/15 3:17:54 来源:尧图企业网站定制
简介针对基于物理信息神经网络求解微分方程的Python实践需求这份压缩包提供了系统的方法示例与可运行代码。面向数值计算、深度学习交叉领域的初学者及研究人员覆盖常微分方程、偏微分方程、随机微分方程等典型问题并展示了DeepXDE、自动微分与损失函数构建等关键实现。压缩包共包含二十六个文件以十七个ipynb笔记为主辅以三个Python脚本、四个备份文件、图片与说明文档等整体大小为八百九十一KB便于快速查看与复用。已有二百一十七人参与学习。内容包含欧拉梁、扩散方程、泊松方程、拉普拉斯方程、洛伦兹系统等多个算例既有常微分/偏微分方程基础案例也有狄利克雷、诺伊曼、罗宾等边界条件的处理以及雅可比-海森方法对比能够帮助读者理解物理信息神经网络的建模、训练与验证流程并迁移到自己的科研或工程问题中。1. 为什么 PINN 能解微分方程从网格到函数的思路转变在传统数值解法里我们习惯把求解区域铺满网格然后在这个离散网格上用有限差分或有限元去逼近微分算子。但当几何变得复杂、维数升高或者方程带有强非线性和多尺度特征时网格生成的成本往往比求解本身还高。PINN物理信息神经网络换了个思路不网格化而是用神经网络去直接逼近微分方程的解函数然后把控制方程、初始条件、边界条件全都编码到损失函数里交给优化器去最小化。下面会用 Python 从零开始实现一个 PINN覆盖最小代码、损失函数设计、训练排错到具体的 Burgers 方程求解。适合两类人想把 AI 方法引入科学计算的研究者以及想验证 PINN 是否值得替换现有求解器的工程人员。2. PINN 求解微分方程的数学构成网络、残差与损失PINN 的核心是把“求解微分方程”这个最终目标转换成“优化一个由物理规律约束的损失函数”。所以先别急着写代码先弄清楚损失函数是怎么来的每一项代表什么。2.1 神经网络作为解空间的函数逼近器在 PINN 里神经网络的作用是“函数逼近”。输入是自变量例如时间 t 和空间 x输出是方程未知函数 u(t,x) 的预测值。理论上只要网络足够宽、足够深就能以任意精度逼近任意连续函数这保证了 PINN 的解空间不会偏离真实解太远。常见的做法是使用全连接前馈网络MLP层数和宽度取决于问题复杂度。线性问题用 2 到 3 层即可带激波、边界层这类剧烈变化的解通常要 4 到 6 层每层 32 到 64 个神经元。PINN 不需要预训练也不需要任何已有的解作为监督信号网络自己会在优化过程中学着符合物理规律。以下是一个可动态指定层宽的 MLP 定义import torch.nn as nn class PINN_Net(nn.Module): def __init__(self, layers, activationnn.Tanh()): super().__init__() self.activation activation self.linears nn.ModuleList() for i in range(len(layers) - 1): self.linears.append(nn.Linear(layers[i], layers[i1])) for layer in self.linears: nn.init.xavier_normal_(layer.weight) nn.init.zeros_(layer.bias) def forward(self, x, t): inputs torch.cat([x, t], dim1) for layer in self.linears[:-1]: inputs self.activation(layer(inputs)) return self.linears[-1](inputs)代码里layers是一个列表例如[2, 50, 50, 50, 1]表示输入 2 维、中间三层各 50 个神经元、输出 1 维。激活函数默认用 Tanh因为它的输出范围在-1,1之间且高阶导数连续适合在自动微分中构造二阶导数。权重用 Xavier 初始化配合 Tanh 能避免深层网络信号逐层衰减。2.2 自动微分PINN 的基石PINN 之所以能算残差靠的是自动微分。给定一个输入点网络前向计算出 u随后框架自动构建 u 对输入 x 和 t 的求导图。这样哪怕方程里出现对 x 的二阶导、对 t 的一阶导也只需要调用torch.autograd.grad不需要手推差分格式也不会引入截断误差。以 Burgers 方程 u_t u * u_x - nu * u_xx 0 为例用自动微分定义残差函数import torch def pde_residual(model, x, t, nu): x x.clone().requires_grad_(True) t t.clone().requires_grad_(True) u model(x, t) u_t torch.autograd.grad(u, t, grad_outputstorch.ones_like(u), create_graphTrue)[0] u_x torch.autograd.grad(u, x, grad_outputstorch.ones_like(u), create_graphTrue)[0] u_xx torch.autograd.grad(u_x, x, grad_outputstorch.ones_like(u_x), create_graphTrue)[0] f u_t u * u_x - nu * u_xx return f这里有几个细节需要留意。输入 x、t 在求梯度前必须显式设置requires_grad_(True)否则梯度函数会报错。create_graphTrue保证二阶导数也能反向传播这意味着 PDE 残差对网络参数的梯度是完整的不会因为截断而出现梯度缺失。最后返回的 f 就是该点上的物理残差理想情况下应该为零。2.3 完整的损失函数形态PINN 的损失函数一般由三部分构成控制方程残差、初值残差、边值残差有时也加入观测数据残差。对于 Burgers 方程L L_pde λ_ic · L_ic λ_bc · L_bc其中 L_pde 是内部采样点上物理残差的均方误差L_ic 和 L_bc 是初值、边值条件上的均方误差。λ 是两个权重系数用于平衡不同区域的任务难度。初值条件给的是 t0 时刻整个 x 区间上的函数值边界条件给的是左右端点在 t 上的取值规律两者物理意义完全不同所以需要分开加权。训练开始前可以参考表 2-1 设置初始值。参数建议值说明λ_ic / λ_bc1.0 / 1.0训练初期保持平衡内部采样点数5000~10000控制方程残差采样初边值采样点数500~1000各边界均匀采样优化器学习率1e-3Adam后续按轮衰减训练过程中如果内部残差下降很慢而初边值损失已经接近零说明网络在“过度拟合边界”此时把 λ_ic、λ_bc 调低到 0.1 左右让网络把更多容量留给物理区域。反过来如果边界损失一直压不下去就适当调高对应权重。2.4 采样策略怎么收集训练点PINN 没有固定训练集每轮迭代都可以重新生成样本点。内部点在整个求解区域内用均匀分布随机采样初边值点沿初始时刻和边界线上采样。每个训练步重新采样比一次性固定训练集更稳网络不会记忆某组特定点而是在整个区域上泛化。采样点数量的选择会影响收敛。数量越多物理约束越强但单步训练越慢。调参时先固定网络结构和权重只调采样点规模观察损失曲线是否平滑再下结论。对于一维时间相关方程8000 个内部点通常够用二维或高维问题需要按维度增长量级增加采样数。3. 用 Python 最小实现 PINN从数据生成到训练循环理论清楚之后现在搭一个可运行的 Python 实现。这里的代码基于 PyTorch版本 2.0 以上都行。机器有 CUDA 就把 device 切到 cuda没有就 CPU样例规模训练时间在几分钟到几十分钟不等。3.1 环境准备与依赖安装在开始之前确认当前环境已经装好 Python、torch、numpy。如果还没配置过 PyTorch直接创建一个独立环境最省心conda create -n pinn python3.10 conda activate pinn pip install torch numpy matplotlib上面的命令创建了一个干净的虚拟环境避免污染系统的 Python。PyTorch 安装时注意对照官网的 CUDA 版本CPU 版也能跑通本文的全部代码只是训练速度慢一些。安装完成后可以用python -c import torch; print(torch.__version__)验证是否成功。3.2 生成训练数据与损失计算PINN 的训练数据不是一次生成完的而是按需在训练循环里重新采样。这里写了一个显式的采样函数返回内部残差点、初值点、边界点三组坐标def sample_data(n_pde8000, n_ic1000, n_bc1000, domain[0, 2*3.14159], t_span[0, 1.0]): x_pde torch.rand(n_pde, 1) * (domain[1] - domain[0]) domain[0] t_pde torch.rand(n_pde, 1) * (t_span[1] - t_span[0]) t_span[0] # 初始条件点: t0, x 在区间内均匀 x_ic torch.rand(n_ic, 1) * (domain[1] - domain[0]) domain[0] t_ic torch.zeros_like(x_ic) # 边界点: 左右边界各一半t 在时间上均匀 x_bc torch.cat([torch.zeros(n_bc // 2, 1), torch.full((n_bc // 2, 1), domain[1])], dim0) t_bc torch.rand(n_bc, 1) * (t_span[1] - t_span[0]) t_span[0] return (x_pde, t_pde), (x_ic, t_ic), (x_bc, t_bc)这个函数返回三组点(x_pde, t_pde)用于计算物理残差(x_ic, t_ic)用于计算初始条件损失(x_bc, t_bc)用于计算边界条件损失。domain和t_span是需要根据具体问题修改的两个区间参数。注意torch.full生成边界点时要指定 dtype否则可能隐式转换成 float64和网络参数的 float32 不一致导致类型报错。配合上面的采样函数以 Burgers 方程为例写损失计算。初始条件取 u(0,x) -sin(x)边界条件取周期边界def compute_loss(model, data, nu0.01): (x_pde, t_pde), (x_ic, t_ic), (x_bc, t_bc) data # PDE 残差 f pde_residual(model, x_pde, t_pde, nu) loss_pde torch.mean(f**2) # 初始条件 u_ic_pred model(x_ic, t_ic) u_ic_exact -torch.sin(x_ic) loss_ic torch.mean((u_ic_pred - u_ic_exact)**2) # 边界条件x_bc 两端是 0 和 2π u_bc_pred model(x_bc, t_bc) left_mask x_bc[:, 0] 0 right_mask x_bc[:, 0] 2*3.14159 loss_bc torch.mean((u_bc_pred[left_mask] - u_bc_pred[right_mask])**2) return loss_pde, loss_ic, loss_bc周期边界的编码是 PINN 里一个常见的易错点。很多入门代码会把边界条件错误地写成“左右两端各自逼近某个固定值”那并不符合真实物理约束。这里用的是“左端预测值 右端预测值”的形式才对应周期边界。mask 筛选时用了布尔掩码分别取出左边界和右边界的预测值再算均方误差。表 3-1 是 PINN 初始参数的建议值范围之后所有实验都可以从这组参数起步。参数起始值调整方向网络宽度50不收敛时加宽网络深度3~4边界层陡峭时加深pde 采样点数8000高维或强非线性时翻倍训练轮数2000观察损失曲线确定3.3 训练循环与优化器配置数据生成和损失计算都到位之后训练循环本身不复杂但有一个容易被忽略的关键点每一轮迭代都重新采样而不是用固定数据集。这样做的好处是网络在无数个“随机视角”下不断优化最终在整个区域上连续地符合物理规律。import torch.optim as optim model PINN_Net([2, 50, 50, 50, 1]) optimizer optim.Adam(model.parameters(), lr1e-3) scheduler optim.lr_scheduler.StepLR(optimizer, step_size500, gamma0.9) for epoch in range(2000): data sample_data() loss_pde, loss_ic, loss_bc compute_loss(model, data) loss loss_pde loss_ic loss_bc optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step() if epoch % 100 0: print(fepoch {epoch}: pde{loss_pde.item():.3e}, fic{loss_ic.item():.3e}, bc{loss_bc.item():.3e})训练中需要观察三个损失的相对量级。PINN 的损失面比纯监督学习复杂得多学习率建议从 1e-3 以下开始。如果 loss 震荡优先降低学习率或者加大采样点数量而不是改变网络结构。StepLR 每 500 轮把学习率乘以 0.9是一种温和的衰减方式既能保证前期收敛速度又能在后期帮助损失稳定下降。4. PINN 训练不收敛时的检查顺序与参数调整PINN 的调试和传统机器学习不太一样。没有固定数据集没有验证标签你的正确性完全来自物理规律。训练卡住时按顺序排查比乱调参数有效得多。4.1 先看三项损失的相对走势训练日志里如果 pde 损失始终在 1e-2 以上下不来而 ic、bc 已经到 1e-4问题几乎都出在内部点不够或者权重失衡。这种情况我一般先调权重再看采样密度。举个例子运行上面的代码如果 200 轮后看到epoch 200: pde1.3e-2, ic1.2e-4, bc8.0e-5说明网络已经满足了初边值约束但物理残差没有降下去。这是 PINN 里最常见的“欠约束内部”状态。可以把采样点从 8000 提升到 20000或者在构成总损失时给 loss_pde 乘上一个大于 1 的系数例如loss 10 * loss_pde loss_ic loss_bc。这种改动不需要动网络结构只需要调整 loss 组合方式。4.2 激活函数与网络结构的影响激活函数的选择直接影响高阶导数的表达能力。Tanh 是最常用的 PINN 激活函数因为它的输出范围在 (-1,1) 之间二阶导数非零且连续适合自动微分构造的二阶损失。ReLU 虽然训练快但二阶导恒为零求解二阶微分方程时残差会恒等于零完全无法训练——这是新手最容易踩的坑。网络宽度和深度也需要控制。宽度从 50 增加到 100训练时间大约翻倍但损失下降速度不一定同比例加快。通常用“宽度 50、深度 4”起步模型能拟合再增宽而不是一开始就堆参数量。边界层特别陡峭的问题加宽比加深更有效需要捕捉全局振荡特征时加深比加宽更明显。表 4-1 整理了收敛异常时的快速排查方向。症状最可能原因先试的调整三项损失都不降学习率过高学习率降到 1e-4pde 损失高ic/bc 很低权重失衡λ_ic/bc 降到 0.1训练后期 loss 震荡采样点太少pde 点数加到 20000边界附近振铃边界权重过大调低 bc 项权重4.3 采样点、边界权重、学习率是三角组合调参不是单变量任务。实际训练中采样点数量、损失权重、学习率是协同作用的三角组合。某一项改动往往会引发连锁反应把 pde 点数从 5000 加到 20000单步计算变慢需要同步降低学习率并延长训练轮数把边界权重调低pde 损失会更快下降但边界误差可能反弹。PINN 对权重初始化也比较敏感。同一个方程换一个随机种子最终误差可能差 30% 以上。建议一开始固定随机种子把初始化变量排除掉import torch torch.manual_seed(1234)如果发现每次运行结果差异大先固定种子再用上面的排查表定位真正的结构问题而不是把随机性误判成算法缺陷。这样可以节省大量调试时间。5. 用 PINN 求解带粘性的 Burgers 方程并验证误差最后用一个具体的偏微分方程跑完整个流程验证效果到底如何。这里选择 Burgers 方程u_t u * u_x ν u_xx区域 x ∈ [0, 2π]t ∈ [0, 1]初值 u(0,x) -sin(x)周期边界。当粘性系数 ν 较小时解会出现陡峭的激波结构是检验 PINN 对高阶导计算能力的经典基准。5.1 完整的训练配置torch.manual_seed(1234) model PINN_Net([2, 64, 64, 64, 64, 1])这里把网络宽度从 50 提升到 64并加深一层是为了让网络有能力表达可能出现的高梯度区域。训练轮数增加到 3000学习率仍然从 1e-3 开始配合 StepLR 逐步衰减。5.2 结果验证误差怎么算训练完成后需要在一个稠密的网格上把预测结果和参考解做对比。参考解析解可以用 scipy 的数值方法或者已知的级数解生成。简单起见在所有测试格点上计算相对 L2 误差x_test torch.linspace(0, 2*3.14159, 256).repeat(100, 1) t_test torch.linspace(0, 1, 100).unsqueeze(1) u_pred model(x_test, t_test) u_ref reference_solution(x_test, t_test, nu0.01) relative_l2 torch.norm(u_pred - u_ref) / torch.norm(u_ref) print(frelative L2: {relative_l2:.2e})如果相对 L2 误差在 1e-2 到 1e-3 量级这个 PINN 模型基本合格。若误差偏大回到第 4 章的排查表优先增加 pde 采样点和训练轮数而不是加网络宽度。激波附近的单个点误差可以单独统计很多情况下整体误差不高但局部误差超出了工程容忍度。5.3 边界条件硬约束与自适应采样PINN 的优点是免网格、能融合观测数据、天然适合逆问题但训练时间明显高于传统数值方法并且很难给出先验误差界。如果追求更高精度可以把边界条件从软损失改造成硬约束比如构造一个自动满足周期边界的网络输出层把周期性的三角函数乘到网络输出上。这样边界损失就不再参与优化网络可以把全部容量留给内部物理残差。另一个有效的改进是自适应采样。在激波附近加密集采样点在平缓区域减少采样密度能显著加快收敛。做法是每训练若干轮用当前模型预测的内部损失反过来指导下一轮采样把损失高的区域作为新采样点的候选区。手头有具体方程需要求解时建议从本文的最小实现开始跑通再逐步替换成硬约束和自适应采样每一步都观察误差变化。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价