文章目录
-
- [🌅 序章:预报员的困惑与物理学家的执着](#🌅 序章:预报员的困惑与物理学家的执着)
- [📖 第一章:从"纯数据拟合"到"物理约束"](#📖 第一章:从“纯数据拟合”到“物理约束”)
-
- [1.1 传统神经网络的困境:数据再多,也填不满知识的缝隙](#1.1 传统神经网络的困境:数据再多,也填不满知识的缝隙)
- [1.2 物理方程的优点:放之四海而皆准](#1.2 物理方程的优点:放之四海而皆准)
- [1.3 结合的直觉:物理约束作为"正则化器"](#1.3 结合的直觉:物理约束作为“正则化器”)
- [🔧 第二章:PINN 的底层逻辑](#🔧 第二章:PINN 的底层逻辑)
-
- [2.1 自动微分:让神经网络自己"求导"](#2.1 自动微分:让神经网络自己“求导”)
- [2.2 损失函数的三部分:数据、物理、边界](#2.2 损失函数的三部分:数据、物理、边界)
- [2.3 总损失函数:三者的加权和](#2.3 总损失函数:三者的加权和)
- [🧮 第三章:热传导方程的 PINN 求解------从原理到 MATLAB 实现](#🧮 第三章:热传导方程的 PINN 求解——从原理到 MATLAB 实现)
-
- [3.1 问题定义:一维非稳态热传导](#3.1 问题定义:一维非稳态热传导)
- [3.2 为什么用MATLAB实现PINN?](#3.2 为什么用MATLAB实现PINN?)
- [💻 第四章:MATLAB脚本实现](#💻 第四章:MATLAB脚本实现)
- [📊 第五章:结果解读与参数规律](#📊 第五章:结果解读与参数规律)
-
- [5.1 可视化结果的意义](#5.1 可视化结果的意义)
- [5.2 网络结构参数调整规律](#5.2 网络结构参数调整规律)
- [5.3 实用避坑指南](#5.3 实用避坑指南)
- [🌌 尾声:当机器开始理解自然](#🌌 尾声:当机器开始理解自然)
🌅 序章:预报员的困惑与物理学家的执着
海边的气象站里,一位经验丰富的预报员正盯着屏幕上密密麻麻的传感器数据。海风速度、温度梯度、气压变化------这些数字他再熟悉不过,但要准确预测三小时后的海浪高度,依然像在玩一场胜算有限的赌博。
他手里有两样工具:一样是物理方程 ------纳维-斯托克斯方程,精确描述了流体的运动规律,但它太复杂,真实海况的边界条件永远无法完全获知;另一样是历史观测数据------过去十年每一分钟的风速与浪高记录,但它们只能告诉"曾经发生过什么",却无法直接回答"接下来会发生什么"。
预报员叹了口气:如果能将物理方程的理性与观测数据的经验结合起来,该多好。
这正是物理启发神经网络(Physics-Informed Neural Networks, PINN)诞生的初衷------让机器不仅从数据中学习,还要遵守物理定律。
📖 第一章:从"纯数据拟合"到"物理约束"
1.1 传统神经网络的困境:数据再多,也填不满知识的缝隙
传统深度学习的工作方式,本质上是在做高维空间的复杂插值。给模型成千上万张猫的图片,它能学会识别猫;给模型无数段语音,它能学会转录文字。但这里有一个根本性局限:
如果训练数据中没有覆盖某种情况,模型就无法正确处理它。
比如,用一个只在平原城市道路上训练过的自动驾驶模型去跑山路,它很可能在发卡弯前直接冲出去------因为它的"经验"里没有这种弯道。数据的边界,就是模型能力的边界。
1.2 物理方程的优点:放之四海而皆准
物理方程恰好相反。热传导方程在任何时空都成立,纳维-斯托克斯方程在无论北京还是纽约的河流中都适用。物理定律是普适的,它们不需要"见过"某个具体场景才能做出判断。
但物理方程也有软肋:真实世界太复杂,边界条件难以精确刻画,初始值无法完全测量。方程是"对"的,但我们往往没法把它算出来。
1.3 结合的直觉:物理约束作为"正则化器"
PINN的核心想法非常简单直接:训练神经网络时,不仅让它拟合观测数据,还要求它满足物理方程。
可以把物理方程看作一种无处不在的监督信号 。在传统神经网络中,你只在有标签的位置给模型反馈;而在PINN中,你在整个计算域内任意位置都可以给反馈------因为物理方程告诉你,在任何一个时空点上, u u u 的各阶导数之间必须满足某种关系。
这就好比教一个学生画画:你不仅给他看名画让他临摹(数据驱动),还教他透视原理 和色彩理论(物理约束)。这样即使让他画一个从未见过的角度,他也能画出基本合理的作品。
🔧 第二章:PINN 的底层逻辑
2.1 自动微分:让神经网络自己"求导"
PINN能够工作的技术基石是自动微分 。在TensorFlow、PyTorch等现代深度学习框架中,我们可以计算神经网络输出对输入的任意阶导数,而且结果是精确的(不是数值差分近似)。
这个能力至关重要。因为物理方程本质上就是导数之间的关系。比如热传导方程说的是温度对时间的变化率与温度对空间的二阶导数成正比------如果神经网络能自动算出这些导数,我们就能直接检查它是否满足方程。
2.2 损失函数的三部分:数据、物理、边界
PINN的训练目标被组织为三个损失项:
① 数据损失
L d a t a = 1 N d ∑ i = 1 N d ∣ u θ ( x i , t i ) − u i ∣ 2 \mathcal{L}{data} = \frac{1}{N_d} \sum{i=1}^{N_d} |u_{\theta}(x_i, t_i) - u_i|^2 Ldata=Nd1i=1∑Nd∣uθ(xi,ti)−ui∣2
这部分与普通神经网络无异:在已知观测值的位置,让网络输出尽量接近真实值。它负责拟合已有的实验或观测数据。
② 物理残差损失
L P D E = 1 N f ∑ j = 1 N f ∣ N u θ ( x j , t j ) − f ( x j , t j ) ∣ 2 \mathcal{L}{PDE} = \frac{1}{N_f} \sum{j=1}^{N_f} |\mathcal{N}u_{\\theta}(x_j, t_j) - f(x_j, t_j)|^2 LPDE=Nf1j=1∑Nf∣Nuθ(xj,tj)−f(xj,tj)∣2
这是PINN的灵魂所在。在计算域内部随机采样大量"配点"(collocation points),要求网络输出在这些点上满足控制方程。 N \mathcal{N} N 是微分算子,它把 u θ u_{\theta} uθ 映射成 PDE 的左侧表达式; f f f 是方程右侧的源项。
物理残差损失的本质是:在任意位置,我们可以通过自动微分计算网络输出的各阶导数,然后检查它们是否满足物理方程。 如果不满足,就施加惩罚。
③ 边界/初始条件损失
L B C / I C = 1 N b ∑ k = 1 N b ∣ B u θ ( x k , t k ) − g ( x k , t k ) ∣ 2 \mathcal{L}{BC/IC} = \frac{1}{N_b} \sum{k=1}^{N_b} |\mathcal{B}u_{\\theta}(x_k, t_k) - g(x_k, t_k)|^2 LBC/IC=Nb1k=1∑Nb∣Buθ(xk,tk)−g(xk,tk)∣2
边界条件和初始条件是物理问题的"定解条件"。没有它们,PDE的解不唯一。这部分损失确保网络的输出满足问题的边界和初始约束。
2.3 总损失函数:三者的加权和
L = w d a t a ⋅ L d a t a + w P D E ⋅ L P D E + w B C ⋅ L B C / I C \mathcal{L} = w_{data} \cdot \mathcal{L}{data} + w{PDE} \cdot \mathcal{L}{PDE} + w{BC} \cdot \mathcal{L}_{BC/IC} L=wdata⋅Ldata+wPDE⋅LPDE+wBC⋅LBC/IC
三个权重 w w w 需要人工设定或自适应调整。它们是PINN调参中最微妙的部分------权重太小,对应的约束不起作用;权重太大,可能导致训练不稳定。
🧮 第三章:热传导方程的 PINN 求解------从原理到 MATLAB 实现
3.1 问题定义:一维非稳态热传导
我们考虑一个经典的一维热传导问题:
∂ u ∂ t = α ∂ 2 u ∂ x 2 , x ∈ 0 , 1 , t ∈ 0 , 1 \frac{\partial u}{\partial t} = \alpha \frac{\partial^2 u}{\partial x^2}, \quad x \in 0, 1, \ t \in 0, 1 ∂t∂u=α∂x2∂2u,x∈0,1, t∈0,1
物理含义:一根长度为1的细杆,温度 u ( x , t ) u(x,t) u(x,t) 随时间 t t t 和位置 x x x 变化, α \alpha α 是热扩散系数(本文取 α = 0.01 \alpha = 0.01 α=0.01)。
定解条件:
- 初始条件: u ( x , 0 ) = sin ( π x ) u(x,0) = \sin(\pi x) u(x,0)=sin(πx)(初始时刻杆上的温度分布)
- 边界条件: u ( 0 , t ) = u ( 1 , t ) = 0 u(0,t) = u(1,t) = 0 u(0,t)=u(1,t)=0(两端保持零度)
这个问题的解析解是 u ( x , t ) = sin ( π x ) ⋅ e − α π 2 t u(x,t) = \sin(\pi x) \cdot e^{-\alpha \pi^2 t} u(x,t)=sin(πx)⋅e−απ2t,我们可以用这个精确解来验证PINN的效果。
3.2 为什么用MATLAB实现PINN?
虽然PINN的主流实现基于Python/TensorFlow/PyTorch,但MATLAB的深度学习工具箱同样支持自动微分。更重要的是,MATLAB的矩阵运算和可视化能力让代码更易读、调试更直观。对于工程领域的读者而言,MATLAB往往是第一选择。
💻 第四章:MATLAB脚本实现
代码功能说明
本脚本实现了一个基于物理启发神经网络(PINN)的一维热传导方程求解器。通过构建一个全连接神经网络,利用自动微分计算温度对时间和空间的导数,将热传导方程作为物理约束嵌入损失函数。训练完成后,输出预测温度场并与解析解对比。
matlab
%% PINN 求解一维热传导方程 (已修复 dlfeval 跟踪问题)
% 方程: ∂u/∂t = alpha * ∂²u/∂x²
% 域: x ∈ [0,1], t ∈ [0,1]
% 初值: u(x,0) = sin(pi*x)
% 边界: u(0,t) = u(1,t) = 0
% 解析解: u(x,t) = sin(pi*x) * exp(-alpha * pi^2 * t)
clear; clc; close all;
rng(42);
%% ========== 1. 参数设定 ==========
alpha = 0.01;
num_hidden_layers = 3;
num_neurons = 32;
num_epochs = 5000;
learning_rate = 0.001;
N_data = 50;
N_f = 2000;
N_test = 100;
%% ========== 2. 生成训练数据 ==========
x_ic = rand(N_data, 1);
t_ic = zeros(N_data, 1);
u_ic = sin(pi * x_ic);
t_bc = rand(N_data, 1);
x_bc_left = zeros(N_data, 1);
x_bc_right = ones(N_data, 1);
u_bc = zeros(N_data, 1);
X_data = [x_ic; x_bc_left; x_bc_right];
T_data = [t_ic; t_bc; t_bc];
U_data = [u_ic; u_bc; u_bc];
X_f = rand(N_f, 1);
T_f = rand(N_f, 1);
% 在传入 dlfeval 之前,将输入数据预先转为 dlarray 格式
XT_data_dl = dlarray([X_data'; T_data'], 'CB');
U_data_dl = dlarray(U_data', 'CB');
XT_f_dl = dlarray([X_f'; T_f'], 'CB');
%% ========== 3. 构建神经网络 ==========
layers = featureInputLayer(2, 'Normalization', 'none', 'Name', 'input');
for i = 1:num_hidden_layers
layers = [
layers
fullyConnectedLayer(num_neurons, 'Name', sprintf('fc%d', i))
tanhLayer('Name', sprintf('tanh%d', i))
];
end
layers = [layers; fullyConnectedLayer(1, 'Name', 'output')];
net = dlnetwork(layers);
net = initialize(net);
%% ========== 4. 训练循环 ==========
avgGrad = [];
avgSqGrad = [];
loss_history = zeros(num_epochs, 1);
loss_data_history = zeros(num_epochs, 1);
loss_pde_history = zeros(num_epochs, 1);
fprintf('开始训练 PINN...\n');
fprintf('迭代次数: %d\n', num_epochs);
fprintf('配点数量: %d\n', N_f);
tic;
for epoch = 1:num_epochs
% 调用 computeAll 计算损失与梯度
[loss, loss_data, loss_pde, grads] = dlfeval(@computeAll, net, ...
XT_data_dl, U_data_dl, XT_f_dl, alpha);
[net, avgGrad, avgSqGrad] = adamupdate(net, grads, avgGrad, avgSqGrad, epoch, learning_rate);
loss_history(epoch) = extractdata(loss);
loss_data_history(epoch) = extractdata(loss_data);
loss_pde_history(epoch) = extractdata(loss_pde);
if mod(epoch, 500) == 0
fprintf('Epoch %d: Loss = %.4e (Data = %.4e, PDE = %.4e)\n', ...
epoch, loss_history(epoch), loss_data_history(epoch), loss_pde_history(epoch));
end
end
elapsed_time = toc;
fprintf('训练完成! 耗时: %.2f 秒\n', elapsed_time);
%% ========== 5. 预测与可视化 ==========
x_test = linspace(0, 1, N_test)';
t_test = linspace(0, 1, N_test)';
[X_test, T_test] = meshgrid(x_test, t_test);
XT_test = dlarray([X_test(:)'; T_test(:)'], 'CB');
U_pred = predict(net, XT_test);
U_pred = reshape(extractdata(U_pred), N_test, N_test);
U_exact = sin(pi * X_test) .* exp(-alpha * pi^2 * T_test);
Error = abs(U_pred - U_exact);
max_err = max(Error(:));
figure('Color', 'w', 'Position', [50, 50, 1200, 800]);
subplot(2, 3, 1);
imagesc(x_test, t_test, U_pred);
axis xy; colorbar; colormap(jet);
xlabel('x'); ylabel('t');
title('PINN 预测温度场');
subplot(2, 3, 2);
imagesc(x_test, t_test, U_exact);
axis xy; colorbar; colormap(jet);
xlabel('x'); ylabel('t');
title('解析解');
subplot(2, 3, 3);
imagesc(x_test, t_test, Error);
axis xy; colorbar; colormap(hot);
xlabel('x'); ylabel('t');
title(sprintf('绝对误差 (最大 = %.4e)', max_err));
subplot(2, 3, 4);
hold on;
t_slices = [0.25, 0.5, 0.75];
colors = {'r', 'g', 'b'};
for i = 1:length(t_slices)
[~, idx_t] = min(abs(t_test - t_slices(i)));
plot(x_test, U_pred(idx_t, :), colors{i}, 'LineWidth', 1.5);
plot(x_test, U_exact(idx_t, :), [colors{i}, '--'], 'LineWidth', 1);
end
xlabel('x'); ylabel('u(x,t)');
legend('t=0.25 (PINN)', 't=0.25 (Exact)', ...
't=0.5 (PINN)', 't=0.5 (Exact)', ...
't=0.75 (PINN)', 't=0.75 (Exact)', 'Location', 'best');
title('温度剖面');
grid on;
subplot(2, 3, 5);
semilogy(1:num_epochs, loss_history, 'b-', 'LineWidth', 1.5);
hold on;
semilogy(1:num_epochs, loss_data_history, 'r--', 'LineWidth', 1);
semilogy(1:num_epochs, loss_pde_history, 'g--', 'LineWidth', 1);
xlabel('迭代次数'); ylabel('损失值');
title('损失收敛曲线');
legend('总损失', '数据损失', 'PDE残差', 'Location', 'best');
grid on;
subplot(2, 3, 6);
scatter(X_data, T_data, 30, 'b', 'filled');
hold on;
scatter(X_f, T_f, 10, 'r', 'filled');
xlabel('x'); ylabel('t');
title('训练点分布 (蓝:数据, 红:配点)');
legend('数据点', 'PDE配点', 'Location', 'best');
xlim([-0.05, 1.05]); ylim([-0.05, 1.05]);
grid on;
sgtitle('PINN 求解一维热传导方程');
%% ========== 6. 参数对比实验 ==========
fprintf('\n===== 参数对比实验 =====\n');
configs = [
struct('layers', 2, 'neurons', 16, 'label', '2x16');
struct('layers', 3, 'neurons', 32, 'label', '3x32');
struct('layers', 4, 'neurons', 64, 'label', '4x64');
];
figure('Color', 'w', 'Position', [50, 50, 1000, 400]);
for cfg_idx = 1:length(configs)
cfg = configs(cfg_idx);
layers_cfg = featureInputLayer(2, 'Normalization', 'none');
for i = 1:cfg.layers
layers_cfg = [layers_cfg; fullyConnectedLayer(cfg.neurons); tanhLayer];
end
layers_cfg = [layers_cfg; fullyConnectedLayer(1)];
net_cfg = dlnetwork(layers_cfg);
net_cfg = initialize(net_cfg);
avgGrad_cfg = [];
avgSqGrad_cfg = [];
loss_cfg = zeros(2000, 1);
for epoch = 1:2000
[loss_cfg(epoch), ~, ~, grads_cfg] = dlfeval(@computeAll, net_cfg, ...
XT_data_dl, U_data_dl, XT_f_dl, alpha);
[net_cfg, avgGrad_cfg, avgSqGrad_cfg] = adamupdate(net_cfg, grads_cfg, ...
avgGrad_cfg, avgSqGrad_cfg, epoch, learning_rate);
end
U_pred_cfg = predict(net_cfg, XT_test);
U_pred_cfg = reshape(extractdata(U_pred_cfg), N_test, N_test);
err_cfg = mean(abs(U_pred_cfg(:) - U_exact(:)));
subplot(1, 3, cfg_idx);
semilogy(1:2000, loss_cfg, 'LineWidth', 1.5);
xlabel('迭代次数'); ylabel('损失值');
title(sprintf('%s: 平均误差 = %.4e', cfg.label, err_cfg));
grid on;
end
sgtitle('不同网络结构对收敛速度与精度的影响');
%% ========== 结果输出 ==========
fprintf('\n===== 最终结果 =====\n');
fprintf('网络结构: %d 层, 每层 %d 神经元\n', num_hidden_layers, num_neurons);
fprintf('最终总损失: %.4e\n', loss_history(end));
fprintf('最终数据损失: %.4e\n', loss_data_history(end));
fprintf('最终PDE残差: %.4e\n', loss_pde_history(end));
fprintf('最大绝对误差: %.4e\n', max_err);
fprintf('平均绝对误差: %.4e\n', mean(Error(:)));
%% ==================== 所有函数定义(必须在脚本末尾) ====================
function [loss, loss_data, loss_pde, grads] = computeAll(net, XT_data, U_target, XT_f, alpha)
% ---- 数据损失 ----
U_pred = forward(net, XT_data);
loss_data = mean((U_pred - U_target).^2, 'all');
% ---- PDE 残差损失 ----
U_f = forward(net, XT_f);
% 一阶导数:使用 dlgradient 对输入的 XT_f 求导
gradU = dlgradient(sum(U_f, 'all'), XT_f, 'EnableHigherDerivatives', true);
dU_x = gradU(1, :);
dU_t = gradU(2, :);
% 二阶导数:对 dU_x 再次关于 XT_f 求导
grad2U_x = dlgradient(sum(dU_x, 'all'), XT_f, 'EnableHigherDerivatives', true);
d2U_x2 = grad2U_x(1, :);
% 计算 PDE 残差: du/dt - alpha * d^2u/dx^2
residual = dU_t - alpha * d2U_x2;
loss_pde = mean(residual.^2, 'all');
% ---- 总损失 ----
loss = loss_data + loss_pde;
% ---- 计算网络参数梯度 ----
grads = dlgradient(loss, net.Learnables);
end
运行说明
- 环境要求 :MATLAB R2020b 及以上版本(需要
dlnetwork、dlarray、dlgradient等深度学习自动微分函数) - 工具箱要求:Deep Learning Toolbox
- 执行方式:直接点击运行,无需额外输入
- 输出内容 :
-
6个子图组成的可视化窗口:预测温度场、解析解、误差分布、温度剖面、损失收敛曲线、配点分布

-
第二个窗口:3种不同网络结构的收敛对比

-
控制台打印:损失值、最大/平均绝对误差
-
开始训练 PINN... 迭代次数: 5000 配点数量: 2000 Epoch 500: Loss = 4.0142e-04 (Data
= 2.7885e-04, PDE = 1.2257e-04) Epoch 1000: Loss = 7.9103e-05 (Data = 5.4099e-05, PDE = 2.5004e-05) Epoch 1500: Loss = 4.2876e-05 (Data = 2.5004e-05, PDE = 1.7872e-05) Epoch 2000: Loss = 2.0524e-05 (Data = 8.4983e-06, PDE = 1.2026e-05) Epoch 2500: Loss = 1.5032e-05 (Data = 5.4303e-06, PDE = 9.6020e-06) Epoch 3000: Loss = 1.1996e-05 (Data = 3.9865e-06, PDE = 8.0092e-06) Epoch 3500: Loss = 6.9332e-05 (Data = 2.9896e-05, PDE = 3.9436e-05) Epoch 4000: Loss = 8.6271e-06 (Data = 2.6761e-06, PDE = 5.9510e-06) Epoch 4500: Loss = 1.3567e-05 (Data = 7.1678e-06, PDE = 6.3996e-06) Epoch 5000: Loss = 6.7349e-06 (Data = 2.1319e-06, PDE = 4.6030e-06) 训练完成! 耗时: 368.68 秒
===== 参数对比实验 =====
===== 最终结果 ===== 网络结构: 3 层, 每层 32 神经元 最终总损失: 6.7349e-06 最终数据损失: 2.1319e-06 最终PDE残差: 4.6030e-06 最大绝对误差: 4.3615e-03 平均绝对误差: 9.4808e-04
📊 第五章:结果解读与参数规律
5.1 可视化结果的意义
预测温度场与解析解的对比:如果颜色分布基本一致,说明PINN成功地同时拟合了初始/边界条件和PDE约束。热传导的物理本质是温度从高温区域向低温区域"扩散",图中温度峰值随时间逐渐降低并向两端传播,这正是热扩散现象的直观体现。
误差分布图:误差最大区域通常出现在初始条件附近或温度梯度较大的位置,因为在这些区域,PDE残差的计算对导数的精度更敏感。
不同时刻的温度剖面 :实线(PINN预测)与虚线(解析解)的吻合程度,直接反映了PINN在时间维度上的泛化能力。一个好的PINN应该在训练时间范围 0 , 1 0,1 0,1 内的任意时刻都能给出准确预测。
损失收敛曲线:数据损失和PDE残差同时下降,说明网络在拟合观测数据的同时也在"学习"物理规律。如果两者方向相反(一个下降另一个上升),说明损失权重设置不合理。
5.2 网络结构参数调整规律
| 参数变化 | 对效果的影响 |
|---|---|
| 增加隐藏层数 | 模型表达能力增强,能拟合更复杂的解函数,但训练变慢且可能过拟合(PDE残差反而增大) |
| 增加神经元数 | 类似增加层数,但提升效果更明显,在 PINN 中通常优先增加神经元而非层数 |
| 增加配点数量 | PDE约束更强,解更精确,但计算开销线性增加。对于1D问题,2000~5000个配点已足够 |
| 增大学习率 | 收敛更快,但可能不稳定;PINN的损失函数通常较崎岖,建议从 10 − 3 10^{-3} 10−3 开始 |
5.3 实用避坑指南
- 权重初始化 :PINN对初始化敏感,建议使用
Glorot初始化(MATLAB的initialize默认使用) - 激活函数选择 :
tanh是PINN中最常用的激活函数,因为它的导数光滑且值域为 − 1 , 1 -1,1 −1,1,适合物理场的平滑性 - 配点采样:边界附近可以适当增加采样密度,因为很多PDE的解在边界处变化剧烈
- 损失权重调参 :如果数据量极少,应增大 w P D E w_{PDE} wPDE;如果观测数据噪声大,应减小 w d a t a w_{data} wdata
🌌 尾声:当机器开始理解自然
PINN让我们看到了一条融合数据驱动 与机理驱动的道路。它提醒我们:深度学习的未来,不一定是更大模型、更多数据;在物理定律已知的领域,把规律"教"给神经网络,或许能用更少的数据、更小的模型,获得更可靠的结果。
预报员最终得到了他想要的工具------一个既尊重物理方程、又能从观测中学习的模型。从此,面对变幻莫测的大海,他不再仅仅依赖经验或理论,而是让两者在神经网络中握手言和。
技术是一座桥,它连接的不只是输入与输出,更是人类对自然的理解与机器对世界的感知。
你在实现PINN或其他物理信息机器学习方法时,是否遇到过损失不收敛或PDE残差震荡的问题?或者你对多物理场耦合的PINN实现有独到见解?欢迎留言交流。