
如果你最近在折腾物理信息神经网络或者手里正好有传热类的偏微分方程需要求解那这个“平板间二维稳态对流传热方程的软物理信息神经网络实现”很值得仔细过一遍。这个项目解决的是一个很具体的问题在两个平行平板之间的通道里流体沿板面方向流动并伴随热量交换需要把温度场 T(x,y) 求出来。而它的特别之处在于整个求解过程不生成网格、不做有限差分或有限体积离散直接把偏微分方程、边界条件作为惩罚项塞进神经网络的损失函数里用自动微分去逼近温度场。这就是所谓“软物理信息神经网络Soft-PINN”的核心思路。本文把这个项目的背景、设计思路、Python 实现细节和我在实际复现中踩过的坑完整拆开讲一遍适合刚入门 PINN 的开发者也适合想把这个方法迁移到其他热流问题上的研究者参考。1. 项目背景与核心问题拆解1.1 平板间稳态对流传热到底在算什么平板间对流传热在工程里很常见比如换热器里的板式通道、电子设备散热风道、平板式太阳能集热器内部流动本质都是流体在上下两个平行壁面之间流动壁面有温度差或者热流密度流体一边流动一边和壁面换热。稳态情况下温度场不随时间变化控制方程是带对流项的能量方程u(x,y)·∂T/∂x v(x,y)·∂T/∂y α·(∂²T/∂x² ∂²T/∂y²)其中 u、v 是流场速度分量α 是热扩散系数。如果是充分发展的层流V 方向速度常常近似为零u 沿 y 方向呈抛物线分布方程会继续简化成u(y)·∂T/∂x α·∂²T/∂x² α·∂²T/∂y²这属于典型的二维稳态对流-扩散问题。传统做法是用 CFD 工具画网格在网格上用有限体积法或有限差分法迭代求解。但这个项目选了一条完全不同的路用神经网络直接拟合 T(x,y) 这个函数再把控制方程残差作为约束加进去让网络自己去“学习”满足物理规律的温度场。适合做这个方向的场景很明确要么你不想花大量时间在网格划分上要么你解的区域边界形状比较奇怪要么你后续要做参数反演、快速预测之类的任务。PINN 的思路天然适合这些情况因为它不依赖网格只需要在计算域内和边界上撒点。1.2 为什么强调“软”物理信息约束在 PINN 里物理约束有两种常见加法硬约束和软约束。硬约束的思路是改造网络输出结构让输出本身精确满足某些边界条件比如通过输出变换把边界值直接构造进去模型再怎么训练都不会违反边界。这种方式听起来很完美但实现起来限制很多尤其是边界条件复杂、多个边界类型混合的时候输出变换很难构造还容易让网络的可表达性变差。软约束则是把物理残差当作损失函数的一项。PDE 残差、边界残差、数据残差各自算一个均方误差再加权求和优化器通过最小化总损失让解逼近真实温度场。这个项目的标题明确点是“软”物理信息神经网络也就是说它走的是后一条路把密码方程“软性”地编入训练目标而不是硬编码进网络结构。软约束的最大好处是灵活。边界条件想换就换想加一个数据拟合项也无所谓本质上就是多目标优化问题里的一个目标项。它不需要针对特定边界条件重新设计网络结构写代码的时候更通用。代价是训练时要反复调节各项损失的权重否则 PDE 残差和边界残差之间会“打架”训练容易不稳定。这一点后面代码部分会具体讲。2. 方案设计与核心机制2.1 神经网络结构与输入输出设计这个项目用的是一个很常规的全连接前馈网络输入是坐标 (x, y)输出是温度 T。网络结构不需要太复杂常见的配置是 2 个输入神经元、3 到 5 个隐藏层、每层 32 到 64 个神经元、输出 1 个神经元。我复现时用的是一组稳定可靠的配置4 个隐藏层每层 40 个神经元。激活函数选型是个关键点。传热方程里有二阶偏导这要求激活函数至少二阶可导而且二阶导数不能恒等于零。如果选 ReLU它的一阶导是阶跃函数二阶导几乎处处为零PDE 残差直接退化成对流项只依赖一阶导导热项完全失效所以 ReLU 在这里不能用。常见的做法是用 tanh输出平滑、导数有界训练初期不容易出现梯度爆炸。后来我也试过 Swish 和正弦激活在某些问题上收敛更快但默认配置还是 tanh 最稳。这里有个容易被忽略的预处理坐标必须归一化。如果 x 范围是 [0, 1]、y 范围是 [0, 0.02]直接喂给网络y 轴方向的信息会被网络当成“不太重要”的特征梯度更新也很难平衡。我习惯把 x、y 都线性缩放到 [-1, 1] 区间温度也做一个无量纲化处理比如 T* (T - T_cold) / (T_hot - T_cold)。缩放之后PDE 残差各项的数值量级会接近训练收敛明显更快。2.2 损失函数四件套配置Soft-PINN 的损失函数通常由三部分组成总损失 λ_pde × L_pde λ_bc × L_bc λ_data × L_dataL_pde 是 PDE 残差的均方误差。做法是随机撒一批内部配点计算网络输出对输入的各阶偏导数代入控制方程左边减右边求平方和的均值。L_bc 是边界条件的均方误差包括入口温度、壁面温度或者壁面热流。L_data 是如果有实验测量点或者高精度数值解参考点就把网络输出和真实值的误差加进来这个项目里一般作为可选项。权重 λ_pde、λ_bc、λ_data 是训练里最需要花心思的地方。PDE 残差的量级和边界残差的量级经常差很多。比如温度归一化后是 0 到 1边界残差天然就是 0 到 1 的量级而 PDE 残差是温度的二阶导乘以扩散系数再减去对流项可能只有 1e-3 甚至更小。如果直接用等权相加边界条件会被优化得很好但内部物理规律完全没学到。所以权重不是随便拍的而是根据各项Loss的初始量级反推目标是让各项在总损失里的贡献大致同量级。我一般先跑 50 到 200 轮打印各分项损失值再设 λ。边界条件处理还要注意一个细节边界配点数量不能太少。每条边界至少 50 到 100 个点四个边界加起来要有几百个点否则网络容易“钻空子”边界上某些区域完全没被约束算出来的温度场在边界局部明显失真。2.3 配点采样策略与训练流程配点采样是 Soft-PINN 容易被低估的关键环节。固定点训练有一个明显问题网络会记住这些点的位置导致在点之间区域出现振荡。更好的做法是每个训练轮次重新随机采样内部点。也就是说每轮迭代都从均匀分布或拉丁超立方采样里重新生成一组内部配点这样网络在每个 epoch 面对的都是“新”样本泛化能力会强很多。这个项目里我采用的是每轮重新采样的策略内部点 1000 个边界点每条边 100 个。训练流程一般分两阶段。第一阶段用 Adam 优化器学习率从 1e-3 开始训练 2 万到 5 万轮把损失压到一个相对稳定的平台。第二阶段切换成 L-BFGS 优化器利用它对高精度解的强局部搜索能力再优化几千轮往往能把 PDE 残差再降低一到两个数量级。这种两阶段策略在 PINN 社区里几乎是标配比单纯用 Adam 死磕更高效。3. Python 代码实现与关键环节3.1 环境准备与依赖建议直接用 PyTorch自动微分这一块最顺手。TensorFlow 也能做但 PyTorch 的torch.autograd.grad接口在处理高阶导数时更直观。需要安装的库很少核心就torch和numpy绘图用matplotlib。我用的 PyTorch 2.x 版本CPU 也能跑这个规模的例子只是训练稍慢GPU 会快很多。import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt3.2 构建 Soft-PINN 模型网络结构用 PyTorch 的nn.Module封装代码不长但有几个地方值得注意每一层都接tanh激活最后一层输出不加激活函数因为温度可以是负值不需要限制。class SoftPINN(nn.Module): def __init__(self, layers[2, 40, 40, 40, 40, 1]): super().__init__() self.layers nn.ModuleList() for i in range(len(layers) - 1): self.layers.append(nn.Linear(layers[i], layers[i1])) self.activation nn.Tanh() def forward(self, x, y): x_in torch.hstack([x, y]) for layer in self.layers[:-1]: x_in self.activation(layer(x_in)) out self.layers[-1](x_in) return out输入坐标要hstack拼成一个两列的张量网络才能同时看到 x 和 y。输出层不加激活函数是为了避免把输出限制在某个区间给网络更大的表达自由度。3.3 PDE 残差的计算计算 PDE 残差是整个项目最核心的代码块。核心是自动微分求一阶和二阶偏导用torch.autograd.grad而不是loss.backward()这样不会污染网络的梯度状态。二阶导需要在一阶导的基础上再求一次所以第一次求导时create_graphTrue必须打开。def pde_residual(net, x, y, alpha, U_max, H): x.requires_grad_(True) y.requires_grad_(True) T net(x, y) dT_dx torch.autograd.grad(T, x, grad_outputstorch.ones_like(T), create_graphTrue)[0] dT_dy torch.autograd.grad(T, y, grad_outputstorch.ones_like(T), create_graphTrue)[0] d2T_dx2 torch.autograd.grad(dT_dx, x, grad_outputstorch.ones_like(dT_dx), create_graphTrue)[0] d2T_dy2 torch.autograd.grad(dT_dy, y, grad_outputstorch.ones_like(dT_dy), create_graphTrue)[0] # 充分发展层流抛物线速度分布v0 u 4.0 * U_max * y * (H - y) / (H * H) residual u * dT_dx - alpha * (d2T_dx2 d2T_dy2) return residual这里我直接使用了简化的稳态能量方程速度剖面采用了平板通道充分发展层流的抛物线分布。如果你的流场不是这种形式把u替换成已知速度场的表达式即可。边界条件也类似写一个函数把边界配点上的预测值和给定值做差求均方误差。3.4 训练循环与权重调整训练循环是标准的 PyTorch 套路但要时刻记住内部配点每轮都重新采样边界配点可以固定也可以重新采样。我把边界点固定内部点每轮重新生成这样既保证边界约束稳定又能让内点覆盖更均匀。def train_soft_pinn(net, optimizer, n_iter, alpha, U_max, H): # 边界配点固定一次 x_left torch.zeros(100, 1) y_left torch.linspace(0, H, 100).reshape(-1, 1) T_left torch.full_like(x_left, T_hot) # 其他边界同类处理此处省略具体赋值 for it in range(n_iter): # 内部配点 x_int torch.rand(1000, 1) * L y_int torch.rand(1000, 1) * H # 边界损失 PDE损失 loss_pde torch.mean(pde_residual(net, x_int, y_int, alpha, U_max, H) ** 2) T_pred net(x_left, y_left) loss_bc torch.mean((T_pred - T_left) ** 2) # 加权组合 loss 1.0 * loss_pde 10.0 * loss_bc optimizer.zero_grad() loss.backward() optimizer.step() if it % 1000 0: print(fiter{it}, loss_pde{loss_pde.item():.3e}, floss_bc{loss_bc.item():.3e})注意我这里损失权重暂时是λ_pde1、λ_bc10这只是一个起始值。实际操作中要先跑一小段观察两个分项损失的量级再反过来调整权重。如果loss_pde一直比loss_bc大好几个数量级说明边界约束太弱或者温度尺度归一化没做好。L-BFGS 的阶段在 PyTorch 里不能简单地循环optimizer.step()了事需要把整个损失计算封装成一个闭包函数传给optimizer.step(closure)。不过整体代码结构和 Adam 大同小异关键是设置line_search_fnstrong_wolfe能明显提升收敛稳定性。3.5 后处理与结果验证训练结束后把计算域划分成密集网格网格点上直接前向传播得到温度场预测值用contourf或者pcolormesh画等温线图。x_plot np.linspace(0, L, 200) y_plot np.linspace(0, H, 80) xx, yy np.meshgrid(x_plot, y_plot) x_tensor torch.tensor(xx.ravel(), dtypetorch.float32).reshape(-1, 1) y_tensor torch.tensor(yy.ravel(), dtypetorch.float32).reshape(-1, 1) with torch.no_grad(): T_pred net(x_tensor, y_tensor).numpy().reshape(xx.shape) plt.contourf(xx, yy, T_pred, levels50, cmapjet) plt.colorbar() plt.show()验证阶段不要只看 Loss 曲线一定要和参考解做对比。如果拿不到解析解就用成熟的 CFD 工具在同一工况下跑一个高精度网格解作为基准然后计算预测温度和基准解的最大绝对误差、平均绝对误差。我复现时经常发现 Loss 已经压到 1e-5 了但温度场局部还是有异常尖峰这说明网络在个别区域过拟合了采样点此时需要检查采样密度和网络层数。4. 实操中踩过的坑与避坑技巧4.1 激活函数选错二阶导等于零这是新手最容易踩的坑。最开始我用 LeakyReLU一阶导是常数二阶导全为零PDE 残差里导热项直接消失网络只能靠对流项拟合温度场完全对不上。换成 tanh 或正弦激活后二阶导才有意义。这里建议大家检查自己的激活函数二阶导是否恒为零不要只看网络能不能收敛还要看 PDE 残差里的每一项是不是真的被计算到了。4.2 边界损失权重失衡内部物理规律学不到我试过一组参数边界损失和 PDE 损失等权叠加训练结束后边界温度非常准确但内部温度分布是完全错误的等温线扭曲得离谱。原因就是 PDE 残差量级远小于边界残差被边界损失完全掩盖了。解决办法不是盲目调权重而是先把温度归一化、坐标归一化做完再看残差量级。如果边界残差已经到 1e-2PDE 残差还在 1e-1那权重比还说得过去如果差了三个数量级就要考虑是不是热扩散系数 α 太小导致导热项在整个方程里没存在感。这时候可以适当把 α 从方程里提取出来或者重新归一化。4.3 内部配点固定不变出现记忆效应如果内部配点固定不变训练到后期 Loss 看似很低但换一组测试点去算温度误差立刻变大。这是典型的过拟合到配点位置网络记住了这些点的值而不是真正学到了函数关系。我现在的习惯是每 500 轮重新采样一次内部点甚至在小型问题上每轮都采样。采样次数增加后训练时间没涨多少但泛化效果明显改善。4.4 Adam 后不切 L-BFGS精度卡在平台Soft-PINN 训练到中后期Adam 的收敛速度会变得非常慢Loss 在一个平台上来回震荡怎么降都降不下去。这个项目里我最早只用 Adam 跑 5 万轮PDE 残差停在 1e-4 左右。后来改成 Adam 先跑 2 万轮再切 L-BFGS 跑 3000 轮残差直接降到 1e-6。L-BFGS 是一种拟牛顿方法善于利用损失函数的曲率信息在 PINN 这类优化目标接近收敛时表现非常突出。切换时注意要把所有张量从自动求导状态整理干净否则会报RuntimeError。4.5 高频分量拟合不动温度场过平滑传统 tanh 网络对低频信号拟合很好对局部高频温度变化通常无能为力。平板间对流传热如果在入口区域温度剧烈变化网络给出的结果往往是“磨平”的过渡带被拉宽。这个问题的根源是神经网络的光谱偏差现象也可以说是“谱偏差”网络天然先学低频、后学高频。解决办法有两种一是加 Fourier 特征映射把坐标输入先做正弦余弦变换再送进网络二是加大内部点采样密度并让采样点偏向温度梯度大的区域。项目里我采用后者在近壁面处加密采样效果比较明显。5. 常见问题排查速查表现象可能原因解决方法Loss 始终不降激活函数不可二阶导或网络初始化不当换 tanh 或正弦激活缩小初始权重方差边界准确但内部不对边界损失权重过高或 PDE 残差量级过小打印分项损失反推权重归一化温度、坐标训练后期 Loss 震荡Adam 学习率过大降低学习率到 1e-4 或切 L-BFGS 精调换测试点误差大内部配点固定导致过拟合每轮重新采样内部点温度场局部尖峰采样点不足或网络容量过大加密内部点减小隐藏层宽度或增加正则化二阶导计算出 NaN自动微分create_graph未开启确保一阶导create_graphTrue入口区域温度过平滑高频分量拟合不了加入 Fourier 特征映射或加密高梯度区采样这个表基本覆盖了用 Soft-PINN 解传热方程时最常遇到的七类问题。每次训练不收敛先按这个表逐条排查比盲目调参有效率得多。6. 实操心得与一点扩展建议这个项目我前前后后复现了好几遍最有感触的是Soft-PINN 的“软”字既是优点也是麻烦。优点在于灵活换任何边界条件都不需要动网络结构麻烦在于权重调节环节太依赖经验参数稍微给错训练就直接跑偏。现在我自己做类似问题时会在一开始就把所有量纲约化干净把温度、坐标、速度全部无量纲化这样不仅损失权重好调不同工况之间的模型迁移也更方便。还有一个很实用的小技巧训练前先不急着跑完整模型把内部配点数量压到 100 个、训练轮数压到 500 轮快速跑一遍看看三个分项损失的初始量级和下降趋势。如果初始残差比例就不对直接停下来调权重等比例合理了再上完整规模。这样能节省大量试错时间。再往后扩展的话这个思路完全可以延伸到非稳态问题加入时间 t 作为第三个输入同时把时间导数项加到 PDE 残差里。也可以用来做参数反演把热扩散系数 α 设成未知参数让网络一边拟合温度场一边反推物性参数。这些方向都是在现有这套编码框架上做小改动不需要推翻重写。对真正想拿 PINN 解决工程传热问题的人来说先把稳态、二维、软约束这个基础版本吃透后面很多路都会顺很多。