ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PhysicsNeMo工程实践:物理机器学习代理模型的选型、训练与部署

PhysicsNeMo工程实践:物理机器学习代理模型的选型、训练与部署

做仿真的人大概都遇到过这种拉扯:一个工况跑几个小时甚至几天,参数扫描一圈下来,机器排期比项目周期还长。于是很多人第一反应是拿历史仿真结果训一个纯数据驱动的代理模型,输入几何和边界条件,输出流场或者应力场。刚上手时效果确实唬人,误差看着也就几个百分点,可一旦工况超出训练集覆盖的范围,预测结果就开始离谱,甚至出现质量不守恒这种物理上根本不可能的情况。这时候才会回头去找带物理约束的建模路线,也才会第一次认真看 PhysicsNeMo 这个名字。

PhysicsNeMo 是面向物理机器学习的一套开源框架,跑在 PyTorch 之上,把物理信息神经网络、神经算子、图神经网络这几类建模方式,连同几何处理、采样、分布式训练、导出部署这些工程环节打包在一起。它的价值不在于"一键出结果",而在于把物理机器学习从论文里的公式,翻译成能长期维护的工程代码。这篇内容适合两类人看:一类是懂仿真但深度学习只停留在调包层面的工程师,另一类是算法基础不错、但对偏微分方程和网格数据不熟的同学。我下面会围绕它的定位、模型选型、跑通链路、踩坑记录、工具链缝合几个角度讲,代码是示意性的骨架,参数名以你本地装的版本为准,但逻辑和坑是通用的。

1. PhysicsNeMo 的真实定位:它是零件库,不是一键出结果的黑盒

1.1 从"算不动"到"算得起"的需求本质

先把需求说透。工程上要用代理模型,动机基本只有三类:一是要快,实时控制、在线优化、数字孪生看板,这些场景等不了求解器;二是要多,参数扫描、不确定性量化、优化设计,动辄上万次评估;三是要嵌,把模型塞进更大的系统里当子模块,比如整机模型里的一段管路。这三类需求有个共同点——允许误差,但不允许违反物理。

纯数据驱动的模型在这一点上是天然残疾的。它可以拟合得很准,但它不知道质量守恒,不知道能量不能凭空产生,也不知道壁面上速度必须为零。训练集里没出现过的组合,它只能靠插值硬猜。物理机器学习要做的就是把这个先验塞进去,让模型在数据稀疏甚至无数据的地方,也能靠方程本身约束出合理结果。PhysicsNeMo 干的就是这件事的工程化封装。

我见过一些团队把它当成"输入几何输出流场"的黑盒工具来用,配置一改就想出结果,最后发现根本跑不出东西,回头骂框架不好用。这个预期本身就是错的。它更像一个高度可配置的建模工具箱:方程要你自己写,采样要你自己定,权重失衡要你自己调,框架负责的是把张量流转、分布式、日志、检查点这些脏活兜住。

1.2 物理约束被放在训练图的哪个位置

理解框架设计,关键是搞清楚物理约束到底作用在哪。常见做法有两类。

第一类是软约束,也就是把 PDE 残差写进损失函数。模型输出对坐标求偏导,代入方程算出残差,残差越小越好。这种方式不要求标签,只要求采样点,所以理论上无标签也能训。代价是训练难度高,不同损失项量纲不一样,权重没调好就会出现某一项压倒一切的情况。

第二类是硬约束,把边界条件、守恒律直接编码进网络结构。比如输出层乘一个到壁面距离的函数,保证壁面速度天然为零;再比如用散度自由的基函数构造速度场。硬约束训练更稳,但泛化到复杂几何上很麻烦,工程上多用软约束加局部硬约束的组合。

PhysicsNeMo 的 Sym 模块走的是第一条路的完整实现:你用一种接近符号计算的方式定义方程,框架自动生成需要的偏导节点,再和几何采样、边界条件拼成约束对象,最后汇总到求解器里训练。这套抽象的好处是方程和数据解耦,同一个几何换一套方程就能复用;坏处是抽象层数多,出问题时要顺着配置往下扒。

1.3 判断你的问题该不该用它:四个前置条件

不是所有仿真问题都值得上物理机器学习。我一般用四个条件筛一遍。

判断维度适合的信号不适合的信号
维度与规模二维或中等规模三维,关注整体趋势超大规模三维、强湍流细节
数据可得性有一定量仿真数据,或边界条件清晰完全没有可信数据且方程复杂
复用频率同一类几何/工况要反复算成百上千次一次性算例,跑完就结束
精度容忍度允许 1% 到 5% 的相对误差要求与求解器逐点一致

最典型的适用场景是"几何族"问题:一类换热器、一类叶型、一类管路,几何在参数空间里连续变化,需要快速评估。最不适合的是纯探索性、只算一次的问题,你花在建模调参上的时间远超直接跑求解器。

还有一个容易被忽略的隐性条件——你必须有一个可靠的验证基准。代理模型最容易骗人的地方是损失曲线好看但场是错的,没有独立的高保真结果对照,你根本发现不了。

提示:在立项阶段就把验证方案定下来,包括对照算例、评估指标、可接受的误差范围。这一步省了,后面返工的成本是建模成本的数倍。

2. 四类模型的能力边界:PINN、FNO、DeepONet、MeshGraphNet 分别吃什么数据

2.1 PINN:没有数据也能训,但代价在采样

物理信息神经网络是最直觉的一类。输入是空间坐标(和时间),输出是场变量,损失由 PDE 残差、边界条件、初始条件、观测数据几部分组成。它最大的优势是不需要成对的输入输出样本,只要有方程和几何,理论上就能训。

但它的短板同样明显。第一是训练慢,因为每个 batch 都要对输出做自动微分求高阶导数,计算图比普通网络长得多。第二是对高频、强对流问题不友好,容易出现光谱偏差,也就是低频分量先收敛、高频细节迟迟学不出来。第三是采样敏感,配点分布不合理的话,误差会集中在稀疏区域。

我在实际使用中的体会是,PINN 适合方程相对干净、几何不太复杂、维度偏低的问题,比如传热、扩散、层流、结构小变形。它的另一个高价值用法是数据融合:手上只有少量实验测点,想补出整个场,这时候 PINN 把测点当数据项、方程当正则项,比纯插值靠谱得多。

2.2 神经算子:学的是"算子"不是"解"

神经算子和 PINN 的思路完全不同。PINN 学的是一个具体问题的解,换个边界条件就得重训。神经算子学的是从输入函数到输出函数的映射,训练一次之后,新的边界条件、新的初始场可以直接推理,不用重新优化。

代表结构是傅里叶神经算子和深度算子网络。前者在频域做卷积,对周期性问题特别顺手,谱方法能表达的东西它基本都能表达;后者用分支网和主干网分别编码输入函数和查询坐标,输出在任意点上的值,对不规则查询点更友好。

这里有个误区得说清楚:神经算子不是万能的加速器。它的精度上限受训练数据质量限制,本质上还是一个数据驱动的映射学习器,只不过输入输出是函数空间里的元素。它比 PINN 快得多(推理一次就够),但比 PINN 更依赖数据覆盖。

2.3 图神经网络:网格拓扑友好的那条路

真实工程里的仿真数据大多长在网格上,有限元网格、有限体积网格,节点数从几万到几千万不等。这类数据结构不规则,用卷积处理不了,用点云方法又丢拓扑。图神经网络把网格直接当图处理:节点是网格点,边是相邻关系,消息传递就是在做局部聚合。

这类模型对几何变化天然鲁棒,因为图的连接关系随几何自适应。做结构力学、流体表面、粒子系统的人往往优先选这条路。代价是推理时要带上图结构,前处理比规整网格麻烦,而且模型规模上去之后显存吃紧。

PhysicsNeMo 里这几类模型都有对应实现,接口风格统一,切换成本不算高。但要注意,模型换了,数据处理管线、损失构造、评估方式都得跟着调整,不是改一行配置的事。

2.4 选型对照表与实际取舍

模型类型需要的输入训练成本换工况是否重训典型场景
PINN方程 + 几何 + 少量数据高(高阶导数)需要重训传热、扩散、反问题、数据融合
FNO成对函数样本(网格规整)中不需要周期性问题、参数化场映射
DeepONet成对样本,查询点可任意中不需要传感器布置、任意点查询
MeshGraphNet图结构样本中高不需要结构力学、非结构网格流体

选型的实操口诀:有方程没数据,选 PINN;有数据要泛化,看网格规整度;网格规整选算子,网格散乱上图网络。如果数据量很少但有方程,可以考虑 PINN 预训练再迁移;如果数据多但精度要求高,神经算子加物理正则往往比纯数据驱动稳。

还有一条经验:不要一上来就挑战最难的组合。我见过团队直接上三维湍流加复杂几何加 PINN,训了两周没收敛。先用二维简化算例把整条链路跑通,确认采样、权重、验证这套东西都对了,再往上加复杂度,效率高得多。

3. 跑通第一个算例:从几何到验证的完整链路

3.1 环境与版本对齐:最容易被跳过的一步

这套框架踩坑最多的环节其实是环境。它依赖 PyTorch、CUDA、以及一堆科学计算库,版本错配的表现往往不是报错,而是跑起来结果诡异或者分布式直接挂住。

我的做法是先固定三个版本:驱动对应的 CUDA 版本、PyTorch 版本、框架版本。三者要对齐,不能凭感觉装最新。装完之后立刻跑一个最小验证:

import torch import physicsnemo print("torch:", torch.__version__) print("cuda available:", torch.cuda.is_available()) print("device count:", torch.cuda.device_count()) print("physicsnemo:", getattr(physicsnemo, "__version__", "unknown"))

确认能识别到所有卡,再跑一次单卡的前向传播。这一步别嫌麻烦,多卡环境里如果 NCCL 配置有问题,往往要等到训练跑了半小时才暴露。

另一个常被忽略的是文件系统。训练过程会频繁写检查点、日志、可视化输出,如果工作目录挂在网络盘上,I/O 会成为瓶颈,表现出来是 GPU 利用率上不去但也不报错。把输出目录放到本地 SSD 上,这个问题基本就没了。

3.2 PDE 与约束项的正确写法

框架提供的符号层让方程定义接近数学写法。以二维不可压 Navier-Stokes 为例,骨架大致是这样:

from physicsnemo.sym.eq.pdes.navier_stokes import NavierStokes ns = NavierStokes(nu=0.01, rho=1.0, dim=2, time=True)

这一步生成的是方程节点,包括连续性方程和各个方向的动量方程。真正训练时,框架会对网络输出自动求偏导,拼出残差。这里有两个容易写错的地方。

一是无量纲化。方程里的粘性系数、密度、特征长度必须统一到无量纲体系,否则不同损失项的量级差出几个数量级,训练必然失衡。我通常把长度归一到 1,速度归一到 1,时间用特征时间归一并,粘性系数就变成 1/雷诺数。这样所有项都在 O(1) 附近,权重好调得多。

二是符号方向。动量方程里对流项、压力梯度项的符号一旦写反,模型不会报错,只会训出一个物理上讲不通的场。验证方法是拿一个已知解析解的简单算例(比如泊肃叶流)跑一遍,看残差是否能降到接近零。这一步是必须做的自检。

几何定义同样有讲究。规则几何可以用内置的图元拼,复杂几何从外部网格或者 STL 导入。导入之后要检查法向方向,法向反了会导致壁面边界条件加在错误的一侧,结果就是壁面附近速度不收敛。

3.3 损失权重的调法:为什么要做归一化和自适应

这是整个训练里最耗时间的部分。总损失通常是若干项加权和:

$$ L = \lambda_{pde} L_{pde} + \lambda_{bc} L_{bc} + \lambda_{ic} L_{ic} + \lambda_{data} L_{data} $$

调权重的本质是平衡梯度。如果某一项的梯度量级远大于其他项,优化器就会只顾着降这一项,其他约束形同虚设。表现出来就是损失曲线在降,但边界条件明显不满足。

我一般分三步走。第一步做量纲归一化,把每项损失除以它的特征尺度,让初始状态下各项量级接近。第二步观察训练早期的各项损失下降速度,如果某项在几百步内就降到很低,说明它的权重可能过大,可以适当降一点,让它和其他项同步收敛。第三步才考虑自适应方法,比如根据各项梯度的统计量动态调整权重。

有个反直觉的经验:边界条件项的权重通常要比直觉上给的更高。因为边界点数量远少于内部配点,按平均值算它天然吃亏,不加权就容易被淹没。我一般让边界项权重在内部项的十倍量级起步,再根据收敛情况微调。

还有一点,训练后期如果发现 PINN 的 PDE 残差已经很小但场仍然有偏差,多半是配点不够或者分布不合理,这时候加权重没用,得回去改采样。

3.4 验证集怎么设才算数

代理模型的验证不能只看训练损失。我一般设三层。

第一层是残差验证,在独立的配点集上算 PDE 残差、边界误差,确认物理约束真的被满足,而不是被训练点记住了。

第二层是场对比,拿高保真求解器的结果做对照,看云图、看剖面线、看关键位置的数值。云图能看出整体的形态对不对,剖面线能看出梯度的捕捉准不准,两者缺一不可。我见过云图看着挺像但剖面一画就发现峰值位置偏了的案例。

第三层是外推测试,故意拿训练集范围之外的工况去测。这一层的误差往往比内插大一个量级,但它才真正反映模型能不能用。如果外推误差不可接受,要么扩充训练集覆盖范围,要么老实承认模型只能在内插区间用。

验证指标建议至少报三个:相对 L2 误差、最大绝对误差、以及关键工程量(比如总压降、总换热系数、最大应力)的误差。前两个反映场的精度,最后一个反映工程上真正关心的量。有时候场误差 3% 但总压降误差只有 0.5%,这种模型在工程上是完全可以用的。

4. 那些文档里不会写的坑:五个真实排错链路

4.1 损失降了但场是错的:量纲不一致导致的假收敛

这是我自己踩得最深的一个坑。现象是训练日志很漂亮,总损失从 1e0 降到 1e-4,但导出场一看,压力场整体偏移,速度场在边界附近有明显残留。

排查思路是这样的。先看各项损失的分量,如果某一项从始至终比其他项低三四个数量级,基本可以确定是量级失衡。接着检查输入坐标的范围——如果几何尺度是毫米级但坐标直接喂进去,网络看到的输入都在 1e-3 附近,激活函数的动态范围被浪费,梯度传播也跟着变差。

解决办法是坐标归一化加方程无量纲化,两件事一起做。坐标归一到 [-1, 1],方程按特征量无量纲,损失项再各自除以特征尺度。改完之后损失曲线的形状会明显不一样,各项同步下降,而不是某一项独走。

顺带说一个诊断技巧:把各项损失的梯度范数打印出来。如果某一项的梯度范数比其他项大两个数量级以上,那就是它在主导训练。这个信息比损失值本身有用得多。

4.2 边界点采不到:几何与采样器的隐藏失配

现象是壁面边界条件怎么训都收敛不了,残差卡在某个值下不去。查了半天发现是采样点在几何的边界上根本没落到该落的位置。

根因通常有两个。一是几何导入时的坐标系变换没对齐,比如外部网格是以某个角点为原点,而几何定义是以中心为原点,两者差了个平移量,采样器在"错误的位置"上撒点。二是采样密度参数设得太低,对于细长结构,边界采样数不够覆盖整个周长。

排查方法是把采样点导出成可视化文件,直接看。这一步花十分钟,比猜半天有用。确认点位分布之后,再调整采样参数。对于细长或者薄壁结构,建议对边界单独指定采样数,而不是用统一密度。

注意:几何导入后一定要做一次"点是否在体内"的自检,随机撒一批点,检查落入体内的比例是否符合预期。比例明显偏低就说明几何定义有问题。

4.3 多卡之后结果和单卡不一致

分布式训练跑起来不报错,但同样的配置单卡和四卡跑出来的结果差很多。这个问题在物理机器学习里比在普通深度学习里更常见,原因是损失项里包含对输入的导数,而数据划分方式会影响每个进程看到的配点分布。

排查路径:先确认随机种子在各个进程上是否一致,再看数据并行时配点是不是被均匀切分。如果用的是全局采样再切分,各卡看到的配点分布应该等价;如果是各卡独立采样,那配点集合就不一样,梯度会有差异。

另一个常见原因是批归一化或者类似的统计层在多卡下的行为。物理机器学习里其实不太建议用批归一化,因为配点分布不均匀,统计量不稳定,用层归一化或者干脆不用往往更稳。

还有一个隐蔽的点:日志的汇总方式。如果各卡的损失没有正确归约,打印出来的曲线看着正常,实际优化的目标已经变了。检查一下损失聚合的代码,确认是全局平均而不是某个进程的局部值。

4.4 显存不涨但速度不变:数据加载成了瓶颈

训练速度上不去,第一反应是模型太大,于是减层数、减宽度,结果速度还是那样。打开监控一看,GPU 利用率在 20% 上下波动,说明计算单元在等数据。

物理机器学习的数据管线比图像任务复杂得多,涉及几何采样、网格读取、方程残差计算的前置处理。如果这些操作用 Python 循环实现,单线程跑起来会成为硬瓶颈。优化方向有三个:把采样逻辑向量化,尽量用张量操作替代循环;把不依赖梯度的预处理放到后台进程,和训练流水线并行;把采样结果缓存下来,避免每个 epoch 重算。

我做过一次对比,把边界采样从逐点循环改成批量张量采样,端到端的训练速度提升非常明显,而模型结构一行没改。所以遇到速度问题,先看数据侧,再看模型侧。

4.5 导出之后精度掉了

训练时验证集误差 2%,导出成推理格式后误差变成 8%。这种问题在部署阶段特别常见,原因往往出在数值精度和图优化上。

第一个要查的是精度。训练时用混合精度,导出时按单精度转换,某些算子的数值行为会变。对于包含高阶导数的模型,精度损失会被放大。建议导出前用全精度重新验证一遍,确认基线。

第二个要查的是算子支持。如果模型里有自定义算子或者不常见的激活函数,导出工具可能做了近似替换,行为就变了。解决办法是把不支持的部分拆出来,用标准算子重写,或者保留一部分在框架侧计算。

第三个要查的是输入预处理。训练时的归一化是在数据管线里做的,导出后如果忘了把同样的归一化嵌进推理图,输入分布就变了。这个错误极其常见,而且表现得很隐蔽,因为输出看着"差不多",只是精度差一截。

5. 和现有仿真工具链缝合:数据准备、混合建模与交付口径

5.1 训练数据从哪来、怎么裁

做神经算子这条路,数据准备的工作量经常被低估。数据来源无非三类:历史仿真结果、参数化批量计算、以及实验测量。历史数据最省事但覆盖范围往往不均匀,参数化批量计算质量可控但算力成本高,实验数据最珍贵但样本少。

我的建议是先做参数空间设计,用拉丁超立方或者 Sobol 序列在设计空间里撒点,保证覆盖均匀,再跑批量仿真。样本量不是越多越好,关键看覆盖。一个覆盖良好的小数据集,效果往往好过一个集中在某几个工况的大数据集。

数据处理上有几个细节值得说。一是场数据的存储格式,HDF5 比 CSV 高效得多,读取速度差一个量级。二是网格不一致的问题,不同算例的网格节点数不同时,要么插值到统一网格,要么用能处理变拓扑的模型。前者会引入插值误差,后者前处理复杂,需要权衡。三是归一化的统计量必须用训练集算,然后冻结,验证和测试都用同一套,否则会有信息泄漏。

5.2 混合建模:让代理模型只负责它擅长的那一段

纯代理模型替代整个求解器,在工程上风险偏高。更务实的做法是混合建模,让模型只承担它最擅长的部分。

常见的三种缝法。第一种是替代子模块,比如把本构关系、燃烧模型、湍流闭合这些经验性强的部分用网络替代,主求解器保持不变。这样做风险可控,因为守恒律仍然由求解器保证。第二种是初值加速,用代理模型给一个接近收敛的初场,求解器在此基础上迭代,迭代次数大幅减少。第三种是降阶模型耦合,把高维场投影到低维子空间,在子空间里做时间推进。

我倾向于第二种,因为它对精度的影响最小,即使模型不太准,求解器也能把它拉回来。落地时只需要在原来的求解流程里加一个"读初场"的入口,改动量很小。

5.3 交付时怎么证明它可信

代理模型交付给下游用户,最大的障碍不是性能而是信任。用户会问:你这个模型在什么范围内可信?我怎么知道它什么时候会失效?

我的做法是准备一份"适用边界说明",包含四部分内容。一是训练数据覆盖的参数范围,明确画出边界,超出范围不保证。二是验证结果,包括内插区间的误差分布和外推区间的误差退化曲线。三是失效模式说明,比如在某些极端参数下模型会给出非物理的负值,需要在后处理里加保护。四是运行时的置信度指标,比如用集成模型的方差或者残差估计给出一个不确定度,超过阈值就提示回退到求解器。

这份文档比模型本身更能决定它能不能被用起来。我在实际项目里的体会是,用户不怕模型有误差,怕的是不知道误差在哪。把边界说清楚,接受度会高很多。

还有一点值得强调:代理模型上线之后要有持续监控。工况分布会漂移,今天有效的模型半年后可能就不准了。定期抽样跑高保真对照,发现误差抬头就触发重训,这个机制要在上线时就设计好,别等到出问题才补。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进