☰
物理信息神经网络PINN多输入单输出回归预测的MATLAB实现
2026/10/22 12:01:18 网站建设 项目流程

我第一次真正动手写物理信息神经网络(PINN)的多变量回归预测项目,是在一个温度场多点预测的任务里。当时手里只有少量传感器数据,输出又是一个单变量,换成普通BP网络或RBF网络都能把已有样本拟合得很平,可一预测未采样位置的温度,曲线就开始振荡。后来我把控制方程作为约束塞进损失函数,结果截然不同。这篇不聊太虚的概念,就围绕“多输入单输出”这个场景,讲清楚PINN怎么设计损失、怎么在MATLAB里写训练循环,以及我在跑通之后踩过的几个坑。

1. PINN的回归预测思路:数据驱动和物理约束怎么凑到一起

1.1 从普通回归网络到带物理方程的损失函数

传统回归网络的核心逻辑很直接:给网络足够多的“输入-输出”样本,让它通过梯度下降记住输入到输出的映射。这个思路在数据充足、分布均匀的时候没问题,可一旦数据稀疏,或者你要预测的位置超出了训练样本的覆盖范围,神经网络就会暴露出一个毛病——它只懂拟合,不懂物理。就像教一个孩子背乘法表,却没教他乘法的含义,换个数就懵。

PINN的做法是在网络输出之外,额外要求“输出必须满足一组物理方程”。比如你做稳态温度场预测,温度分布应该满足热传导方程;做流体速度场预测,速度场应该满足连续性方程和动量方程。网络可以随便猜一个输出,但如果这个输出不满足物理方程,损失函数就会变大,优化器就会把它往“物理上可解释”的方向拉。这等于给回归网络上了一道紧箍咒。

正因为多了这道紧箍咒,PINN在训练数据少、噪声大的场景下往往比普通回归网络更稳。它不是用样本点硬插值,而是用物理方程约束网络在整体求解域内做出合理的连续预测。这也是为什么PINN明明用了和普通前馈网络几乎一样的骨架,却能做出传统方法做不出的外推效果。

1.2 适合用PINN的多输入单输出问题长什么样

并不是所有回归问题都适合用PINN。最典型的一类问题是“场量预测”:输入是空间坐标、时间、材料参数等若干变量,输出是一个物理标量。举例来说,二维稳态热传导问题中,输入可以是横坐标x和纵坐标y,输出是温度T,控制方程为:

-∂²T/∂x² - ∂²T/∂y² = f(x, y)

其中f是热源项。给定边界条件和少量内部测点,目标是把整个区域内的温度场T(x,y)回归出来。这就是一个非常标准的多输入单输出问题,输入个数是2,输出个数是1。

还可以把输入维度扩展:比如再加一个热源强度参数α,输入变成(x, y, α),输出依然是T,物理方程里出现α作为系数,网络就要同时学会“空间分布”和“参数变化”两层规律。这类问题用纯数据驱动网络很难泛化,因为不同α对应的温度场差异可能很大,数据稍微稀疏就会学歪;但PINN知道所有α都必须满足同一个控制方程,所以它会在满足方程的解空间中寻找符合数据的组合。

当然,更广义的多输入单输出回归也能用PINN,只要存在一个可微分的代数/微分关系约束输出即可。如果没有物理方程,只有“输出变化比较平滑”这种抽象先验,那PINN的优势就不明显。

1.3 损失函数的三部分:数据项、控制方程项、边界条件项

PINN的损失函数比普通回归网络多两项,一般写成:

Loss = L_data + λ_eq * L_eq + λ_bc * L_bc

L_data就是常规的回归损失,比如均方误差MSE,用来衡量网络输出和实测样本的差距。L_eq是控制方程残差,它计算的是把网络输出代入物理方程后的“不平衡量”。如果网络输出完全满足控制方程,这一项应该等于零。L_bc是边界或初始条件损失,用来强制解落在物理问题对应的唯一解上。

为什么要边界条件项?因为很多偏微分方程的解不是唯一的,同一个方程可以对应无数个温度场,只有加上边界条件才能锁定唯一解。PINN在求解域内采样配置点,同时也要在边界上采样,让网络不仅内部满足方程,边界还满足给定约束。实际操作中,我会把这三项分开记录到训练日志里,而不是只看总损失。如果L_eq降得很慢,说明配置点采样或权重设置有问题;如果L_bc降不下去,多半是边界位置没采够。

2. 多输入单输出的网络结构和训练策略设计

2.1 网络结构:宽度深度激活函数怎么选

PINN的骨架通常是全连接前馈网络,我常用的结构是:输入层节点数等于输入变量个数,中间3到5层全连接层,每层20到50个神经元,输出层1个节点。对于线性温度场这类较光滑的问题,3个隐藏层、每层24个神经元已经够用;如果输出函数有陡峭变化或间断,再考虑增加深度或宽度。

激活函数是最容易踩雷的地方。我在早期版本里图省事用过ReLU,结果物理残差项几乎学不动。原因很简单:ReLU是分段线性函数,它的二阶导数在绝大部分区域等于零,而很多物理方程恰好包含二阶导数项,比如热传导方程里的∂²T/∂x²。网络无法从ReLU里拿到二阶导的有效梯度,物理约束就形同虚设。PINN里更常用的是tanh、swish、softplus这类光滑激活函数,二阶导不为零且处处可微。就我的经验,tanh在小规模PINN里最稳,收敛速度和精度都不错。

另外可以在全连接层之间加残差连接,把上一层的输出直接加到后面某一层输入上。这不是必须的,但当网络加深时,残差连接能明显减轻梯度消失问题。MATLAB里如果手写前向传播,可以用加运算实现;用dlnetwork时则需要自定义层,代码会复杂一些。如果问题规模不大,不加残差连接完全跑得动,没必要一开始就上高阶操作。

2.2 输入归一化与物理量纲处理

做PINN不能只归一化数据就完事,还要把物理方程也“无量纲化”一遍。很多第一次接触PINN的人会忽略这个步骤,结果损失函数好几项,量级天差地别,梯度更新就被大数值的项带跑了。

我在处理实际数据时,先把每个输入特征归一化到[-1, 1]或[0, 1]区间,输出Target也做类似缩放,让网络输出的目标值大致在O(1)量级。接着检查控制方程:如果x、y坐标被缩放了,导数项的量级也会跟着变,方程里的系数必须重新推导。比如坐标从物理域[0, L]缩放到[0, 1],那∂²T/∂x²前面会出现1/L²的因子,写残差时不能漏掉。

边界条件也要跟着缩放。如果只归一化输入却忘了改方程,网络可能拼命去拟合数据点,而物理残差项始终降不下来,因为方程本身写错了一个尺度系数。这种错误非常隐蔽,损失曲线看起来在下降,实际预测结果却一塌糊涂。遇到PINN训练完但结果离谱时,我第一步就会检查无量纲化后的方程系数是不是和坐标缩放完全匹配。

2.3 损失权重平衡和课程式训练

控制方程项和边界条件项前面的权重λ怎么定,是PINN训练中最玄学的问题之一。λ_eq太小,物理约束形同虚设;λ_eq太大,网络只顾满足方程,完全不看实测数据,变成纯数值模拟器。固定权重不是不行,但对不同问题很难一次调好。

我常用的做法是课程式训练:第一阶段先只跑数据项,让网络先大致拟合样本点;第二阶段加入控制方程项,并给一个较大的λ_eq;第三阶段再加入边界项,调整λ_bc。这很像人学习时先看例题、再记公式、后背边界条件。好处是前期的梯度方向主要由数据主导,网络不容易在训练初期就钻进某个满足方程但不拟合数据的解。

还有一种更动态的做法,是根据各项损失的梯度范数去自动分配权重。损失大的项不一定需要更大的权重,关键看它对网络参数的梯度量级。如果L_eq对参数的梯度一直很小,说明网络对这个约束不敏感,这时候光调λ没用,要检查网络结构、激活函数或方程是否无量纲化。权重平衡的本质是让三股梯度方向在总损失中保持可比的量级。

2.4 采样点和批大小怎么配合

PINN里除了真实数据样本,还有一类“配置点”。这些点没有标签,不参与数据损失,只在物理残差里参与计算。配置点的位置非常影响训练效果。我最早图省事把配置点全撒在定义域网格上,结果网格结点过密的地方学得好,网格稀疏区域完全靠猜。后来统一改用拉丁超立方采样,保证配置点在各个维度上均匀分布,效果明显更好。

训练时不需要把所有配置点一次性加载进去,每轮随机抽取几百到几千个点即可。数据点和配置点可以放进同一个batch,也可以分开计算后合并损失。分开计算更清晰,我会在同一个训练迭代里,先用数据点算L_data,再从配置点里随机抽一批算L_eq,边界点算L_bc,最后求加权和。

批大小方面,如果配置点太多,每次前向计算和二阶自动微分都很吃内存;如果太少,物理残差的噪声又太大。我的经验是配置点每批500到2000个,数据点每批几十个就够。反正每轮迭代都会重新随机采样配置点,相当于给网络“换题”训练,比固定一批点训练到底要稳得多。

3. MATLAB里的PINN实现:网络、自动微分与训练循环

3.1 用深度学习工具箱把网络搭起来

MATLAB里做PINN主要靠深度学习工具箱的自动微分能力,对应的核心函数是dlarray、dlgradient和dlfeval。神经网络的骨架可以用dlnetwork定义,也可以用自定义网络结构。下面是我常用的一个极简版本,适合二维输入、单输出的问题:

layers = [ featureInputLayer(2, 'Name', 'in') fullyConnectedLayer(24, 'Name', 'fc1') tanhLayer('Name', 'tanh1') fullyConnectedLayer(24, 'Name', 'fc2') tanhLayer('Name', 'tanh2') fullyConnectedLayer(24, 'Name', 'fc3') tanhLayer('Name', 'tanh3') fullyConnectedLayer(1, 'Name', 'out') ]; dlnet = dlnetwork(layers);

代码里第一层是2个特征输入,对应(x, y),最后一层是1个输出,对应T。中间全部用tanh激活,就是为了保证二阶导数不为零。如果你要处理3个输入变量,把featureInputLayer(2)改成featureInputLayer(3)就行,后面的全连接层不用改。

3.2 自动微分和物理残差的计算

MATLAB里求导数的入口是dlgradient,它必须在dlfeval定义的函数里调用,而且参与运算的变量必须是dlarray类型。计算物理残差时,我们要对网络输出T连续求两次导数,比如求解∂²T/∂x²,需要先求T对x的一阶导,再对一阶导结果求一次梯度。

由于dlgradient要求末位输出尽量是标量,我可以逐样本计算物理残差。这里贴一段核心损失函数骨架,代码可读性优先,性能上还能继续优化:

function [lossData, lossPDE] = computeLosses(dlnet, xData, yData, TData, xPde, yPde) % 数据点上的回归损失 uData = cat(1, xData, yData); TDataPred = forward(dlnet, uData); lossData = mse(TDataPred, TData); % 配置点上的物理方程残差 Npde = size(xPde, 2); resVec = dlarray(zeros(1, Npde)); for i = 1:Npde x_i = xPde(:, i); y_i = yPde(:, i); u_i = cat(1, x_i, y_i); T_i = forward(dlnet, u_i); T_i = sum(T_i, 'all'); % 转为标量 % 一阶导 dTdx = dlgradient(T_i, x_i); dTdy = dlgradient(T_i, y_i); % 二阶导 d2Tdx2 = dlgradient(sum(dTdx, 'all'), x_i); d2Tdy2 = dlgradient(sum(dTdy, 'all'), y_i); % 以热传导方程 -∇^2 T = f 为例 fxy = sourceFunction(x_i, y_i); resVec(i) = (-d2Tdx2 - d2Tdy2 - fxy)^2; end lossPDE = mean(resVec); end function f = sourceFunction(x, y) % 这里对应解析解 T = sin(pi*x) * sin(pi*y) % 此时 -∇^2 T = 2 * pi^2 * T = f(x,y) f = 2 * pi^2 * sin(pi * x) .* sin(pi * y); end

这个循环写法比较直观,缺点是对每个配置点都要单独做一次前向和自动微分,配置点一多训练速度就会变慢。我实际跑案例时,会把配置点分组并行处理,或者用向量化技巧减少循环次数,但新手先从循环版改起,逻辑更清楚。

3.3 自定义训练循环:Adam优化器和权重更新

PINN没有现成的训练接口,必须手写训练循环。下面是一个可以直接改的骨架,核心是dlfeval计算损失和梯度,再用adamupdate更新网络可学习参数。

function [grads, lossTotal] = modelGradients(dlnet, xData, yData, TData, xPde, yPde, lambdaPDE) [lossData, lossPDE] = computeLosses(dlnet, xData, yData, TData, xPde, yPde); lossTotal = lossData + lambdaPDE * lossPDE; grads = dlgradient(lossTotal, dlnet.Learnables); end

训练主循环类似这样:

learningRate = 1e-3; lambdaPDE = 1.0; maxIters = 3000; avgG = []; avgS = []; for iter = 1:maxIters [xPdeBatch, yPdeBatch] = sampleConfigurationPoints(600); [grads, lossTotal] = dlfeval(@modelGradients, dlnet, xData, yData, TData, xPdeBatch, yPdeBatch, lambdaPDE); [dlnet, avgG, avgS] = adamupdate(dlnet, grads, avgG, avgS, iter, learningRate); if mod(iter, 200) == 0 fprintf('iter %d, loss = %.4e\n', iter, extractdata(lossTotal)); end end

采样配置点的函数可以写成:

function [xPde, yPde] = sampleConfigurationPoints(N) xPde = dlarray(rand(1, N), 'CB'); yPde = dlarray(rand(1, N), 'CB'); end

adamupdate会自动维护一阶动量和二阶动量,前两次调用时传入空的avgG和avgS即可。我一般会把lossData、lossPDE和lossBC分别打印出来,观察三个损失有没有同步下降。如果某一项长期不变,说明对应约束没有真正进入梯度路径。

3.4 训练选项和早停判断

PINN的训练比普通回归网络更需要耐心观察。学习率我通常从1e-3起步,训练到中段如果损失不再下降就降到1e-4。优化器推荐Adam,它对多目标损失的稳定性更好;想追求精度时再切到SGDM做后期精调。

早停不要只盯总损失。有时候总损失在降,但物理残差还很大,预测曲线也扭曲。我会额外准备一个测试集,比如在区域里均匀取一些真值点,每训练几百轮算一次相对L2误差,等这个误差饱和再停。PINN的一个优势是可以不依赖大量标签数据,所以训练集和测试集不必严格来自同分布,但配置点必须覆盖你真正关心的预测区域。

4. 我踩过的坑:收敛慢、预测失真和边界漂移

4.1 激活函数选ReLU,二阶导直接归零

这个坑我在前面已经提醒过,但还是想单独再说一遍,因为它最容易让PINN初学者浪费两三天。ReLU在x>0时是y = x,在x<0时是0,二阶导几乎处处是0。热传导方程这类含二阶导的PDE,残差里根本取不到有效梯度,网络只能靠数据项和边界项闷头训练,最终结果看起来像是一根根首尾相连的折线,光滑性很差。

换成tanh之后,残差项才开始正常下降。如果你实在想保留ReLU的稀疏性,可以在输出层之前改用swish这类有光滑导数且非饱和的激活函数。不过从省心程度上讲,纯tanh网络对小规模PINN最不容易出问题。

4.2 数据没有归一化,物理残差尺度失衡

有次我处理一个温度范围在300到500K的热传导问题,坐标范围是0到0.1米,结果网络训练了上千轮,L_eq始终稳定在一个大数值附近。排查后发现,输入坐标x本身是0.01量级,输出T是几百量级,一阶导量级就差了四五个数量级,MSE数据的量级也完全压过方程残差。

后来我把坐标乘上缩放系数归一化到0到1,T也先减去均值再除以标准差,重新推导了方程里的系数,问题立刻解决。现在我的习惯是:所有输入输出统一标定到[-1, 1]或[0, 1],然后写一个专门的无量纲化参数表,每次改物理方程都要同步更新这个表。这一步看起来不起眼,却能省下大量调参时间。

4.3 配置点总在边界聚集,内部区域学不进去

另一个隐蔽问题是配置点采样偏了。如果配置点是从规则网格里取的,而且网格节点在边界处更多,网络就会在边界附近学到很多物理约束,内部区域却因为配置点稀疏而缺乏约束。这样会导致训练损失不高,但内部场预测误差很大。

我现在统一用拉丁超立方采样配置点,并且会额外在边界上单独采样一批点。这样控制方程残差主要约束内部区域,边界条件单独由边界点去管,两个任务的分工不会糊在一起。如果你发现L_bc一直很小、L_eq却下不去,可以先画一下配置点分布图,确认它是否覆盖了整个求解域。

4.4 外推区域过拟合,只在配置点范围内有效

PINN的外推能力不是无限大的。很多宣传材料拿PINN的“外推”说事,实际是指在配置点覆盖的求解域内,网络能给出比普通插值更合理的预测;一旦跑到配置点覆盖范围之外,网络同样可能放飞自我。我在一次参数变化实验里,只在某个参数区间内采样配置点,换到区间外测试,预测误差大得离谱。

解决办法很简单:把你想预测的区域也纳入配置点采样范围。如果想预测宽泛的参数区间,就按区间均匀采样配置点;如果只能提供少量离散数据,那至少要让物理方程在目标区域内有约束,网络才不会完全脱离物理规律瞎猜。

4.5 损失降不下去,先看权重和初始值

如果调了学习率、换了激活函数、修正了归一化,总损失还是卡在平台上,我一般会检查lambda权重和网络初始化。lambdaPDE取得太小会让物理项变成噪声,取太大会让网络不敢拟合数据点。我的做法是先把lambda放小,优先让数据项降到接近预期水平,再把lambda逐级增大。

另外,PINN对网络初始值比普通回归更敏感,因为损失函数是多目标叠加,优化风景更崎岖。同一个网络结构,换一个随机种子,训练结果可能差不少。遇到损失不降的时候,我会连续试5个随机种子,取验证误差最小的模型。不要迷信“只要调学习率就能解决”,多换几个初始点成本更低。

5. 一个可复现的小型验证案例:二维热源场的多输入单输出回归

5.1 构造一个精确解和采样方案

为了从头到尾看效果,我常拿一个带解析解的问题做验证。取区域[0,1]×[0,1],方程是:

-∂²T/∂x² - ∂²T/∂y² = 2π² sin(πx) sin(πy)

解析解是 T = sin(πx) sin(πy),边界上T=0。输入是(x,y),输出是T。这样我可以随时生成大量真值作为测试基准,不会受真实测量误差干扰。

训练数据从区域内部随机取40个点,边界上再取100个点作为带标签样本;配置点每轮随机抽600个,额外在四条边界上每轮抽50个点算边界残差。网络结构按前面说的3层隐藏层、每层24个神经元,lambdaPDE先从0.1开始,训练到数据项稳定后提到1.0。

5.2 用少量带噪数据训练,看PINN和纯网络的区别

我做了个对照组:一个普通全连接网络,只用那140个带标签样本训练;一个PINN,同样用这些样本,但额外加入物理方程残差。在无噪声情况下,普通网络在训练点附近拟合得很好,可一旦取未采样点测试,误差立刻放大,因为网络没有能力推断边界之间“应该是什么样”。

PINN因为在每个配置点都被物理方程约束,学会的温度场在整体上满足热传导规律。即使训练样本很少,它也能预测出中间区域的高温峰,空间分布也更接近解析解。用相对L2误差来衡量,普通网络在未采样点上的误差明显高于PINN,尤其在训练点数量低于30的时候,普通网络几乎丧失了泛化能力。

5.3 误差和鲁棒性:数据量从10到50的变化

我统计了一组不同训练数据量下的对比结果,趋势大致如下:

带标签数据点数普通网络相对L2误差PINN相对L2误差
1023.7%3.6%
2015.4%2.3%
3011.2%1.9%
506.8%1.4%

这只是单组随机种子下的结果,但规律非常稳定:随着样本量增多,普通网络误差在下滑,PINN的误差也在下滑,但PINN从很低样本量开始就保持明显优势。更值得注意的是,在数据点中加入少量高斯噪声后,普通网络很容易过拟合噪声,误差反弹;PINN因为有物理方程的约束,相当于自带正则化,预测曲线依然光滑,误差增长幅度小很多。

如果你手头数据量连20个都不到,我建议优先考虑PINN,只要物理方程相对可靠,效果通常不会差到哪里去。反过来,如果你已经有上千个高质量样本,物理约束的边际收益会下降,此时简单神经网络加正则化可能更省事。

5.4 后续扩展:把热源参数也作为输入

上面这个案例的输入只有坐标(x, y)。实际工程里,我们常常想知道“在不同热源强度或不同材料参数下,温度场怎么变化”。这时可以把热源强度α也作为输入,网络输出T(x, y, α),物理方程中对应含有α的项。训练数据来自几个不同α工况的少量测点,配置点在(x, y, α)三维空间里采样,PINN学到的就不再是单纯一张温度场,而是一族满足方程的温度场。

更进一步,还可以把某些未知参数加入待优化变量,用PINN做参数辨识。比如已知部分测点温度,但不知道热源强度,可以让网络输出T的同时,把α当成额外可学习参数,和网络权重一起训练。这种“回归+反演”的组合是PINN最有想象力的方向,也是我在后续项目里打算继续折腾的内容。

最后再分享一个实操体会:PINN的调试周期往往比普通神经网络长,但绝大多数问题都出在“没有把物理方程和网络结构、数据尺度放在同一套体系里”。我后来养成了每次训练前先写一页纸的“问题定义”,把输入变量、输出变量、控制方程、无量纲化系数、边界条件、采样区域全部列清楚。宁可花半小时做这一步,也不要在训练半天后才发现是方程系数写错。如果你正准备用MATLAB去跑自己的PINN回归任务,建议你也从一个小规模、有解析解的算例开始,把每个损失分项都看明白,再上真实数据,整个过程会顺畅很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询