物理信息神经网络解悬臂梁挠度:PINN从原理到Python实现

发布时间:2026/10/22 13:37:57
物理信息神经网络解悬臂梁挠度:PINN从原理到Python实现
悬臂梁挠度这个经典题目结构力学课程里被算过无数遍材料力学教材甚至直接给出了均布荷载下的解析公式。但我今天想聊的不是手推微分方程也不是传统有限元而是用物理信息神经网络PINN把这条梁“训练”出来——不需要网格剖分不需要离散刚度矩阵写几十行Python代码就能让神经网络自己学会四阶梁方程的解。这个项目适合两类人一类是学结构但想了解深度学习怎么用在工程计算上的工程师另一类是懂深度学习但没见过物理方程如何嵌入网络的算法同学。看完这篇文章你不仅能跑通一维悬臂梁问题还能把同样的套路迁移到其他微分方程求解场景。1. 从四阶梁方程说起悬臂梁问题到底在求解什么1.1 控制方程和边界条件的物理含义欧拉-伯努利梁理论下一维悬臂梁的挠度控制方程是EI * d⁴w/dx⁴ q(x)其中w是梁的竖向挠度EI是抗弯刚度q是分布荷载。这是一个四阶常微分方程相比二阶热传导方程、波动方程它最大的特点就是边界条件多——需要四个固定端两个自由端两个。固定端x0处位移和转角都为零w(0) 0w(0) 0自由端xL处弯矩和剪力为零EI * w(L) 0EI * w(L) 0我当初第一次看到这套边界条件时有个疑惑为什么自由端不约束位移后来想明白自由端可以自由上下移动和转动所以约束的不是几何量而是内力弯矩和内力变化率剪力。在PINN里这些条件必须一个不少地写进损失函数少一个条件解就飘了。为了做定量验证选一个简单场景梁长L1抗弯刚度EI1均布荷载q01。此时解析解是w(x) q0 * x² * (6L² - 4Lx x²) / (24 * EI)这个公式是现成的“标准答案”用来衡量神经网络算得准不准再合适不过。1.2 传统数值方法的几个痛点简化为一维问题后用有限差分或有限元其实都能解为什么还要折腾PINN我自己的体会是经典方法在求解正问题已知荷载求位移时非常高效但有几个场景会让人头疼。首先是网格依赖。有限差分对四阶方程需要至少四阶精度模板节点加密后带宽和计算量明显上涨有限元要划分网格、组装刚度矩阵边界条件处理靠“强制边界”或者“罚函数”实现起来比教科书公式复杂得多。其次是反问题难做。如果题目反过来给定实测挠度反推荷载分布q(x)传统做法需要反复迭代求解正问题计算量成倍增长。而PINN天然支持这种“反演”在损失函数里加入观测数据项即可这是它最吸引人的地方。还有一点是几何灵活性。实际工程梁可能带孔、变截面传统网格重画成本高PINN的配点是无网格的只要控制方程的表达式能写出来换几何只是换采样区域。当然PINN精度和训练速度目前远不如成熟的有限元软件但作为新兴工具它更适合“中等精度、弱约束、物理加数据混合”的场景。1.3 用哪些指标衡量模型效果训练结束后不能只看损失函数好看必须拿预测挠度和解析解做对比。我在这个项目里用两个指标最大绝对误差MaxError max(|w_pred - w_exact|)。相对L²误差RelL2 sqrt(Σ(w_pred - w_exact)²) / sqrt(Σ(w_exact)²)。从工程视角看最大绝对误差关心的是最坏情况比如裂缝宽度估算时往往看这个相对L²误差则是整体精度的度量类似有限元里的能量范数误差。后面可视化时还会画出预测曲线与解析解的贴合程度直观检查是否在固定端和自由端附近出现“尖刺”。2. 模型设计PINN如何把物理方程嵌入网络2.1 网络结构与激活函数的取舍PINN在这个问题里不需要复杂的网络结构。我的配置是一个三层全连接网络输入x输出w每层50个神经元激活函数全部用Tanh。为什么不用ReLU因为ReLU在二阶导数为零三阶四阶导数恒为零根本没法表示四阶梁方程的残差。这个问题上网络必须能提供足够光滑的导数Tanh无限次可微而且输出范围在-1到1之间梯度不容易饱和。实际调试中我发现隐藏层数量不宜超过四层。原因不在于拟合能力而在于导数计算的稳定性每加一层损失函数对参数的梯度就要多穿几层自动微分构建的计算图也会更复杂训练初期很容易出现NaN。这个项目里一两层通常收敛速度不行三层是个比较好的折中四层开始风险上升但不一定更好。2.2 四阶导数的求导负担与降阶处理直接对网络输出求四阶导数是这个项目最核心的“坑”。调用自动微分时一阶导数还好二阶三阶勉强能忍四阶导数的计算图非常大训练速度和内存占用都成问题。更重要的是高阶导数的数值噪声会被逐级放大尤其是自由端附近挠度变化平缓但弯矩、剪力变化仍然存在四阶导数在这里极不稳定。一种工程改造是把四阶方程降阶成两个二阶方程。引入曲率变量κw原方程变成EI * κ q0 w κ这时网络需要两个输出头一个是挠度w一个是曲率κ损失函数只涉及二阶导数计算负担大幅下降。我在代码里保留的是直接的“四阶导数版”更贴近PINN原教旨实现方便理解原理如果你要做工程级稳定版本强烈建议尝试降阶方案。损失函数甚至可以加一项物理意义上的弯矩约束让κ与w尽可能吻合。2.3 损失函数设计与边界条件权重分配PINN的损失函数是物理残差和边界条件的加权和L L_physics weight_bc * L_bc其中物理残差是内部配点处满足EI * w - q0 0的程度边界条件是四个端点条件的平方和。权重的选择对收敛影响极大。第一次跑的时候我把权重设为1结果发现边界条件总是滞后固定端位移不归零自由端弯矩曲线翘尾巴。原因很直观内部有上千个配点物理残差取平均之后是一个比较小的量级而边界只有两个点单独一算数值误差显得大在总损失里占比反而低。把权重weight_bc提到10到100之后边界条件很快被“压”下去内部物理项再慢慢跟上。还有一种更彻底的办法是硬约束。既然固定端要求w(0)0且w(0)0可以直接让网络输出乘以x²构造w_hat x² * N(x)这样一来无论网络参数怎么变w_hat在0处的位移和转角恒为零边界条件的两个几何约束自动满足只需要再罚自由端的弯矩和剪力。这个方案显著降低了训练难度代码改动不大非常适合入门者使用。3. 完整Python实现一维悬臂梁PINN从零搭建3.1 环境准备与参数设置我用的环境是Python 3.10加PyTorch 2.0CPU就足够跑完这个例子不需要显卡。依赖库只有三个torch、numpy、matplotlib。如果你用的是其他深度学习框架思路完全一样。物理参数按上面的约定设定梁长L1.0抗弯刚度EI1.0均布荷载q01.0。配点数量取2000个均匀分布在0到L之间训练步数5000学习率1e-3优化器Adam。把随机种子固定住保证每次训练结果可复现。3.2 网络定义与导数计算完整代码如下每一段都加了注释import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt torch.manual_seed(42) np.random.seed(42) # ---------- 物理参数 ---------- L 1.0 # 梁长 EI 1.0 # 抗弯刚度 q0 1.0 # 均布荷载 q(x) q0 # ---------- 解析解 ---------- def exact_w(x): return q0 * x**2 * (6*L**2 - 4*L*x x**2) / (24 * EI) # ---------- PINN网络 ---------- class PINN(nn.Module): def __init__(self, hidden50): super().__init__() self.net nn.Sequential( nn.Linear(1, hidden), nn.Tanh(), nn.Linear(hidden, hidden), nn.Tanh(), nn.Linear(hidden, hidden), nn.Tanh(), nn.Linear(hidden, 1) ) def forward(self, x): return self.net(x) # ---------- 自动微分求1~4阶导数 ---------- def compute_derivatives(model, x): x x.detach().clone().requires_grad_(True) w model(x) grads [w] for _ in range(4): g torch.autograd.grad( grads[-1], x, grad_outputstorch.ones_like(grads[-1]), create_graphTrue, retain_graphTrue )[0] grads.append(g) w, w_x, w_xx, w_xxx, w_xxxx grads return w, w_x, w_xx, w_xxx, w_xxxx # ---------- 损失函数 ---------- def pinn_loss(model, x_coll, weight_bc10.0): # 内部配点物理残差 w, w_x, w_xx, w_xxx, w_xxxx compute_derivatives(model, x_coll) f_res EI * w_xxxx - q0 loss_physics torch.mean(f_res**2) # 固定端边界 x0 x0 torch.zeros(1, 1) w0, w0_x, _, _, _ compute_derivatives(model, x0) loss_fixed torch.mean(w0**2) torch.mean(w0_x**2) # 自由端边界 xL xL torch.full((1, 1), L) _, _, wL_xx, wL_xxx, _ compute_derivatives(model, xL) loss_free torch.mean(wL_xx**2) torch.mean(wL_xxx**2) loss_bc loss_fixed loss_free return loss_physics weight_bc * loss_bc, loss_physics, loss_bc这段代码的核心在compute_derivatives函数里。循环四次调用torch.autograd.grad每次都用上一步的导数继续对x求导create_graphTrue保证整个过程可再次求导这样总损失才能回传到网络参数。需要注意边界点也走同一个函数所以边界条件那里必须重新单独调用每次调用都会开启一套新的计算图内存开销会略涨在一维小规模问题里可以忽略。3.3 训练循环与过程监控训练循环很常规重点是打印信息的粒度。model PINN() optimizer torch.optim.Adam(model.parameters(), lr1e-3) # 固定配点训练更稳定 x_coll torch.rand(2000, 1) * L print(开始训练...) for epoch in range(5000): optimizer.zero_grad() loss, loss_p, loss_b pinn_loss(model, x_coll) loss.backward() optimizer.step() if epoch % 500 0: print(fEpoch {epoch:5d} | Loss {loss.item():.2e} f| Physics {loss_p.item():.2e} | BC {loss_b.item():.2e})我在调试时养成了一个习惯不只打印总损失物理残差和边界残差分开打印。这样能直观看到训练是同步收敛还是边界拖后腿。正常情况是损失曲线稳步下降物理项在1000步后开始明显减小边界项在前几百步就被压到一个很小的量级。3.4 结果可视化与误差统计训练完之后把预测值和解析解画到一起再看定量指标# 生成测试点 x_test torch.linspace(0, L, 200).reshape(-1, 1) model.eval() with torch.no_grad(): w_pred model(x_test).numpy().flatten() w_exact exact_w(x_test.numpy().flatten()) # 误差指标 max_err np.max(np.abs(w_pred - w_exact)) rel_l2 np.sqrt(np.sum((w_pred - w_exact)**2)) / np.sqrt(np.sum(w_exact**2)) print(f最大绝对误差: {max_err:.3e}) print(f相对L2误差: {rel_l2:.3e}) # 绘图 plt.figure(figsize(8, 5)) plt.plot(x_test.numpy(), w_exact, k-, linewidth2, label解析解) plt.plot(x_test.numpy(), w_pred, r--, linewidth2, labelPINN预测) plt.xlabel(x) plt.ylabel(挠度 w(x)) plt.legend() plt.grid(True) plt.show()把训练收敛到位的模型拉出来对比预测曲线和解析解曲线基本重合。误差的具体数量级和你跑多少步、网络宽度都有关在我这个配置下通常最大绝对误差在10⁻³量级相对L²误差也差不多在这个水平。如果误差只到10⁻²量级说明训练还没收敛或者四阶导数计算不稳定后面几节会讲怎么排查。4. 踩坑实录PINN调参最容易翻车的几个点4.1 四阶导数导致NaN的排查思路训练时loss突然变成NaN是PINN四阶问题的高发症状我遇到得最多。根因通常是梯度爆炸四阶导数的计算图很深损失对最前端网络参数的梯度数值很大Adam在初期一步就越过了稳定区。我的排查顺序是固定的。第一步把学习率从1e-3降到1e-4治好了大半问题。第二步如果还爆就检查网络层数四层降三层神经元数量从80降到50。第三步给梯度裁剪只对参数梯度做clip比如torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)。早期不推荐加大配点数量去解决NaN那是稀释噪声治标不治本。一个容易被忽略的点是激活函数。如果你贪方便用了ReLU系列二阶导恒为零的问题会让导数计算直接出错表现为loss不降或出现异常大值。PINN的标准起步激活函数就是Tanh先把它跑通再考虑改进。4.2 边界条件被物理项淹没有一次训练到3000步总损失已经很小但画出自由端弯矩依然明显不为零。原因是边界条件在总损失里的占比太小。2000个内部配点取平均的物理残差和2个边界点的残差做一个简单求和边界点天然吃亏。解决办法有三个层次。最简单的把weight_bc调到10到100进阶一点用梯度归一化让两个损失项的梯度量级匹配更彻底的是硬约束前面提到的输出乘以x²固定端的边界条件自动满足。我个人推荐从硬约束开始因为它不只解决权重问题还减少了网络需要拟合的一个自由度。硬约束版本只需要把forward改成return x**2 * self.net(x)自由端边界项保留训练速度能快不少。缺点是这个技巧不是所有边界条件都适用要能写出一个满足齐次边界条件的基函数。4.3 “先边界后物理”的两阶段训练技巧如果一开始就让网络同时满足边界条件和内部物理方程很容易在头几百步里被物理残差的大梯度带着跑导致边界条件迟迟不收敛。我常用一个两阶段训练法第一阶段只优化边界条件损失跑500到1000步让端部条件先达到一个比较小的误差。第二阶段开放物理损失继续联合优化。实现上就是控制pinn_loss里weight_bc和物理项之间的开关或者直接用一个参数控制。这个思路很朴素但效果很明显。PINN本质上是多目标优化多目标问题最怕的就是各目标量级不一致先解决容易的目标再拿复杂目标微调是工程师直觉里最靠谱的做法。5. 扩展方向与应用价值5.1 从均布荷载到任意荷载分布代码里q0是常数换成任意x的函数也无所谓只要把损失函数里EI * w - q(x)改成EI * w - q(x)即可。这意味着你可以用同一个网络框架求解线性分布荷载、集中荷载需要做分段处理甚至随机荷载。集中荷载的奇异性是个好题目四阶导数在荷载作用点会跳变这种不连续问题对PINN挑战很大通常需要分区训练或引入额外变量。5.2 反问题已知挠度反推荷载这是PINN相对传统方法最有优势的方向。假设我们只有挠度传感器数据w_obs想反演荷载q(x)可以构造两个网络一个输出挠度w一个输出荷载q。损失函数包含三个部分控制方程残差、边界条件残差、挠度拟合误差。联合训练后第二个网络直接学出荷载分布。传统方法做这种反问题要么病态要么需要仔细设计迭代策略PINN的写法就跟正问题几乎一样。5.3 二维板壳与非线性扩展一维梁是很好的入门样板真正的工程价值在二维板壳、超弹性材料这些场景。二维问题的配点在平面上采样边界条件多了一组四阶偏导计算更贵此时降阶技巧几乎是必须的。非线性大变形梁则需要把控制方程换成考虑几何非线性的形式PINN里改动仍然是方程残差和损失函数网络架构无需大变。这也解释了为什么这类方法这几年在计算力学社区讨论度很高对物理规律的表达方式足够灵活同一个套路能跨场景复用。我在实际使用中最深的一点体会是PINN不是在替代有限元它更像是一种“物理先验数据驱动”的中间形态。对于结构工程师来说与其等它完全成熟不如先从一维问题练手把微分方程残差、边界条件、自动微分这套基本功打磨清楚。等真正碰到反问题、无网格、混合数据这类有限元不太舒服的场景时你会发现自己多了一个非常趁手的工具。