PINN 基础理论与最简示例(含Python代码)
0、前言
博主略懂一些微分方程的求解方法,主要是用有限元方法。但是最近刷B站老给我推送一些PINN神经网络求解微分方程的方法,因此简单研究下是什么高端的技术。
1、PINN 基础理论
在物理信息神经网络(Physics-Informed Neural Networks, PINN)中,我们希望利用神经网络来求解偏微分方程(PDE)。其核心思想是:
让神经网络同时满足 PDE、边界条件、初始条件,以及(可选的)观测数据。
1.1、PINN 的基本 PDE 形式
假设 PDE 为:
N [ u ] ( x , t ) = 0 , ( x , t ) ∈ Ω \mathcal{N}[u](x,t) = 0,\quad (x,t)\in\Omega N[u](x,t)=0,(x,t)∈Ω
其中:
- u ( x , t ) u(x,t) u(x,t):PDE 的未知解
- N [ ⋅ ] \mathcal{N}[\cdot] N[⋅]:微分算子(如 u t − u x x u_t - u_{xx} ut−uxx、 u t t − c 2 u x x u_{tt} - c^2 u_{xx} utt−c2uxx 等)
神经网络输出记为 u θ ( x , t ) u_\theta(x,t) uθ(x,t)。
1.2、PINN 常见的 Loss 构成
PINN 的总损失一般由以下部分组成。
1.2.1、PDE 残差损失 L PDE L_{\text{PDE}} LPDE
L PDE = 1 N r ∑ i = 1 N r ∣ N ( u θ ( x i , t i ) ) ∣ 2 L_{\text{PDE}} = \frac{1}{N_r} \sum_{i=1}^{N_r} \left| \mathcal{N}\big(u_\theta(x_i, t_i)\big) \right|^2 LPDE=Nr1i=1∑Nr N(uθ(xi,ti)) 2
其中 ( x i , t i ) (x_i, t_i) (xi,ti) 是 PDE 内部点(collocation points)。
1.2.2、 边界条件损失 L BC L_{\text{BC}} LBC
对于 Dirichlet 边界:
L BC = 1 N b ∑ i = 1 N b ∣ u θ ( x i ( b ) , t i ( b ) ) − g ( x i ( b ) , t i ( b ) ) ∣ 2 L_{\text{BC}} = \frac{1}{N_b} \sum_{i=1}^{N_b} \left| u_\theta(x_i^{(b)}, t_i^{(b)})- g(x_i^{(b)}, t_i^{(b)}) \right|^2 LBC=Nb1i=1∑Nb uθ(xi(b),ti(b))−g(xi(b),ti(b)) 2
在边界条件中,Neumann 条件不是规定函数值 u u u,而是规定解在边界方向的导数(通量)。例如:
-
一维情况下:
u x ( x , t ) = h ( x , t ) u_x(x,t) = h(x,t) ux(x,t)=h(x,t) -
多维情况下(法向导数):
∂ u ∂ n = ∇ u ⋅ n = u x n x + u y n y ( + u z n z ) \frac{\partial u}{\partial n} = \nabla u \cdot n = u_x n_x + u_y n_y (+ u_z n_z) ∂n∂u=∇u⋅n=uxnx+uyny(+uznz)
因此,PINN 中的 Neumann 边界损失为:
L BC = 1 N b ∑ i = 1 N b ∣ ∂ u θ ∂ n ( x i ( b ) , t i ( b ) ) − h ( x i ( b ) , t i ( b ) ) ∣ 2 L_{\text{BC}} = \frac{1}{N_b} \sum_{i=1}^{N_b} \left| \frac{\partial u_\theta}{\partial n}(x_i^{(b)}, t_i^{(b)})- h(x_i^{(b)}, t_i^{(b)}) \right|^2 LBC=Nb1i=1∑Nb ∂n∂uθ(xi(b),ti(b))−h(xi(b),ti(b)) 2
1.2.3、初始条件损失 L IC L_{\text{IC}} LIC
用于随时间演化的 PDE(如热方程):
L IC = 1 N 0 ∑ i = 1 N 0 ∣ u θ ( x i , 0 ) − u 0 ( x i ) ∣ 2 L_{\text{IC}} = \frac{1}{N_0} \sum_{i=1}^{N_0} \left| u_\theta(x_i, 0) - u_0(x_i) \right|^2 LIC=N01i=1∑N0∣uθ(xi,0)−u0(xi)∣2
1.2.4、数据点损失(可选) L data L_{\text{data}} Ldata
当有实验数据时,可加入监督项:
L data = 1 N d ∑ i = 1 N d ∣ u θ ( x i ( d ) , t i ( d ) ) − y i ( d ) ∣ 2 L_{\text{data}} = \frac{1}{N_d} \sum_{i=1}^{N_d} \left| u_\theta(x_i^{(d)}, t_i^{(d)}) - y_i^{(d)} \right|^2 Ldata=Nd1i=1∑Nd uθ(xi(d),ti(d))−yi(d) 2
这一项使 PINN 能执行半监督学习。
1.3、总损失函数
总 loss 为加权求和:
L = λ PDE L PDE + λ BC L BC + λ IC L IC + λ data L data L = \lambda_{\text{PDE}} L_{\text{PDE}} + \lambda_{\text{BC}} L_{\text{BC}} + \lambda_{\text{IC}} L_{\text{IC}} + \lambda_{\text{data}} L_{\text{data}} L=λPDELPDE+λBCLBC+λICLIC+λdataLdata
不同 λ \lambda λ 用于平衡各个损失项,常见调法包括:
- 边界条件权重可以更高
- PDE 残差通常是主导项
- 若有数据点,也需调 λ data \lambda_{\text{data}} λdata
1.4、Loss 实现方法(概念层面)
PINN 的实现依赖深度学习框架的自动微分(PyTorch / TensorFlow)。
① 自动微分求导数
例如通过 autograd 求
u
x
,
u
t
,
u
x
x
u_x, u_t, u_{xx}
ux,ut,uxx 等。
② 计算 PDE 残差
将网络输出和求得导数代入 PDE 公式。
③ 构造 MSE 损失
把 PDE、边界、初始条件全部转换成 MSE。
④ 反向传播优化参数
用 Adam 或 LBFGS 等优化器训练神经网络。
✔ 总结
PINN 的核心思想就是:把 PDE、边界条件、初始条件变成损失函数,通过自动微分和优化算法逼近 PDE 的解。
2、PINN 代码与解释(完整示例)
本节我们结合一个简单的偏微分方程(PDE)示例,完整展示 PINN 的代码实现过程,并在每一步给出详细解释。
我们要求解的 PDE 是:
u ′ ′ ( x ) + π 2 sin ( π x ) = 0 , x ∈ ( 0 , 1 ) u''(x) + \pi^2 \sin(\pi x) = 0,\quad x\in(0,1) u′′(x)+π2sin(πx)=0,x∈(0,1)
边界条件:
u ( 0 ) = 0 , u ( 1 ) = 0 u(0)=0,\quad u(1)=0 u(0)=0,u(1)=0
该 PDE 的真实解是:
u ( x ) = sin ( π x ) u(x) = \sin(\pi x) u(x)=sin(πx)
下面用 PINN 来逼近这个解析解。
2.1、导入库与固定随机种子
import torch
import torch.nn as nn
import torch.optim as optim
import math
from matplotlib import pyplot as plt
# 固定随机种子(方便复现)
torch.manual_seed(0)
torch.autograd将用于自动微分计算 u x , u x x u_x, u_{xx} ux,uxxmatplotlib用来可视化最终的结果
2.2、定义 PINN 网络结构
class PINN(nn.Module):
def __init__(self, hidden_dim=3, num_layers=1):
super().__init__()
layers = []
input_dim = 1
output_dim = 1
layers.append(nn.Linear(input_dim, hidden_dim))
layers.append(nn.Tanh())
for _ in range(num_layers - 1):
layers.append(nn.Linear(hidden_dim, hidden_dim))
layers.append(nn.Tanh())
layers.append(nn.Linear(hidden_dim, output_dim))
self.net = nn.Sequential(*layers)
def forward(self, x):
return self.net(x)
说明:
- 输入是 x x x,输出是 u θ ( x ) u_\theta(x) uθ(x)
- 激活函数使用
tanh,因为 PINN 中要求二阶导数连续 - 网络深度非常浅(只有 1 隐藏层)用于演示 PINN 在简单网络下的表现
2.3、构造模型与优化器
model = PINN()
optimizer = optim.Adam(model.parameters(), lr=1e-3)
- 使用 Adam 作为优化器
- 学习率设为 1 × 1 0 − 3 1\times 10^{-3} 1×10−3
2.4、训练参数设置
num_epochs = 5000
n_residual = 100 # PDE 内部点
n_bc = 2 # 边界点:0 和 1
说明:
- 内部点(collocation points)用于 enforcing PDE
- 边界点只有两个: x = 0 x=0 x=0 和 x = 1 x=1 x=1
2.5、PINN 训练循环(核心部分)
下面代码逐行解释 PINN 如何构建 PDE 和 BC 的损失。
① 构造 PDE 残差点(内部点)
x_r = torch.rand(n_residual, 1, requires_grad=True)
u_r = model(x_r)
- 在 ( 0 , 1 ) (0,1) (0,1) 采样 100 个点
- 网络输出 u θ ( x r ) u_\theta(x_r) uθ(xr)
② 自动微分求一阶导 u x u_x ux
u_x = torch.autograd.grad(
outputs=u_r,
inputs=x_r,
grad_outputs=torch.ones_like(u_r),
create_graph=True,
retain_graph=True
)[0]
PINN 用自动微分(autograd)求导:
create_graph=True允许继续求二阶导- 得到 u x u_x ux
③ 自动微分求二阶导 u x x u_{xx} uxx
u_xx = torch.autograd.grad(
outputs=u_x,
inputs=x_r,
grad_outputs=torch.ones_like(u_x),
create_graph=True,
retain_graph=True
)[0]
二阶导数直接使用 autograd 再求一次梯度。
④ 构造 PDE 残差
residual = u_xx + (math.pi ** 2) * torch.sin(math.pi * x_r)
loss_pde = torch.mean(residual ** 2)
这是 PDE:
u x x ( x ) + π 2 sin ( π x ) = 0 u_{xx}(x) + \pi^2\sin(\pi x) = 0 uxx(x)+π2sin(πx)=0
PINN 的做法:
- 把 PDE 当成一个“误差”
- 用 MSE 让残差尽量为 0
2.6、构造边界条件损失
x_bc = torch.tensor([[0.0], [1.0]])
u_bc = model(x_bc)
target_bc = torch.zeros_like(u_bc)
loss_bc = torch.mean((u_bc - target_bc) ** 2)
对应边界条件:
u ( 0 ) = 0 , u ( 1 ) = 0 u(0)=0,\quad u(1)=0 u(0)=0,u(1)=0
使用 MSE 强制网络满足该约束。
2.7、总 Loss 与反向传播
loss = loss_pde + loss_bc
loss.backward()
optimizer.step()
即:
L = L PDE + L BC L = L_{\text{PDE}} + L_{\text{BC}} L=LPDE+LBC
PINN 训练本质上就是最小化这个 loss。
2.8、打印训练日志
if (epoch + 1) % 500 == 0:
print(f"...")
用于监控:
- 总损失
- PDE 残差
- 边界条件残差
2.9、模型测试:与真实解对比
x_test = torch.linspace(0, 1, 5).view(-1, 1)
u_pred = model(x_test)
u_true = torch.sin(math.pi * x_test)
打印:
- u θ ( x ) u_\theta(x) uθ(x) 预测值
- 真实解 sin ( π x ) \sin(\pi x) sin(πx)
用于验证 PINN 是否学到了 PDE 结构。
2.10、绘制对比图(PINN vs 真解)
x_plot = torch.linspace(0, 1, 200).view(-1, 1)
u_pred = model(x_plot)
u_true = torch.sin(math.pi * x_plot)
plt.plot(x_np, u_true_np, label='True u(x) = sin(pi x)')
plt.plot(x_np, u_pred_np, '--', label='PINN prediction')
plt.legend()
plt.xlabel('x')
plt.ylabel('u(x)')
plt.title('PINN vs True Solution')
plt.show()
结果:
- 蓝色曲线是真解
- 虚线曲线是 PINN 拟合结果

尽管网络非常小,PINN 仍能准确复现 PDE 的解析解形状。
3、全部代码
import torch
import torch.nn as nn
import torch.optim as optim
import math
from matplotlib import pyplot as plt
# 固定随机种子(方便复现)
torch.manual_seed(0)
# 简单的全连接网络,输入 x,输出 u(x)
class PINN(nn.Module):
def __init__(self, hidden_dim=3, num_layers=1):
super().__init__()
layers = []
input_dim = 1
output_dim = 1
layers.append(nn.Linear(input_dim, hidden_dim))
layers.append(nn.Tanh())
for _ in range(num_layers - 1):
layers.append(nn.Linear(hidden_dim, hidden_dim))
layers.append(nn.Tanh())
layers.append(nn.Linear(hidden_dim, output_dim))
self.net = nn.Sequential(*layers)
def forward(self, x):
# x: [N, 1]
return self.net(x)
# 构造网络和优化器
model = PINN()
optimizer = optim.Adam(model.parameters(), lr=1e-3)
# 训练参数
num_epochs = 5000
n_residual = 100 # PDE 内部点
n_bc = 2 # 边界点:0 和 1
for epoch in range(num_epochs):
optimizer.zero_grad()
# ===== 1. PDE 残差点(内部点) =====
# 在 (0,1) 随机采样
x_r = torch.rand(n_residual, 1, requires_grad=True) # [N, 1]
u_r = model(x_r) # u(x)
# 一阶导 u_x
u_x = torch.autograd.grad(
outputs=u_r,
inputs=x_r,
grad_outputs=torch.ones_like(u_r),
create_graph=True,
retain_graph=True
)[0]
# 二阶导 u_xx
u_xx = torch.autograd.grad(
outputs=u_x,
inputs=x_r,
grad_outputs=torch.ones_like(u_x),
create_graph=True,
retain_graph=True
)[0]
# PDE 残差:u_xx + pi^2 * sin(pi x) = 0
residual = u_xx + (math.pi ** 2) * torch.sin(math.pi * x_r)
# PDE loss
loss_pde = torch.mean(residual ** 2)
# ===== 2. 边界条件点(x=0,1) =====
x_bc = torch.tensor([[0.0], [1.0]]) # [2,1]
u_bc = model(x_bc) # 应该接近 0
target_bc = torch.zeros_like(u_bc)
loss_bc = torch.mean((u_bc - target_bc) ** 2)
# ===== 3. 总 loss =====
loss = loss_pde + loss_bc
# 反向传播和更新
loss.backward()
optimizer.step()
# 每隔一段时间打印一下
if (epoch + 1) % 500 == 0:
print(f"Epoch {epoch + 1}/{num_epochs} | "
f"Loss: {loss.item():.4e} | "
f"PDE: {loss_pde.item():.4e} | "
f"BC: {loss_bc.item():.4e}")
# 训练结束后,简单测试一下某些点的误差(与真解 sin(pi x) 比较)
with torch.no_grad():
x_test = torch.linspace(0, 1, 5).view(-1, 1)
u_pred = model(x_test)
u_true = torch.sin(math.pi * x_test)
print("\n测试点对比:")
for x_val, up, ut in zip(x_test, u_pred, u_true):
print(f"x={x_val.item():.2f}, u_pred={up.item():.4f}, u_true={ut.item():.4f}")
with torch.no_grad():
x_plot = torch.linspace(0, 1, 200).view(-1, 1)
u_pred = model(x_plot)
u_true = torch.sin(math.pi * x_plot)
x_np = x_plot.numpy().flatten()
u_pred_np = u_pred.numpy().flatten()
u_true_np = u_true.numpy().flatten()
plt.plot(x_np, u_true_np, label='True u(x) = sin(pi x)')
plt.plot(x_np, u_pred_np, '--', label='PINN prediction')
plt.legend()
plt.xlabel('x')
plt.ylabel('u(x)')
plt.title('PINN vs True Solution')
plt.show()
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)