PINN入门:物理信息神经网络原理详解

🎙️ 语音朗读 当前: 晓晓 (温柔女声)

PINN入门:物理信息神经网络原理详解

引言

物理信息神经网络(Physics-Informed Neural Networks, PINNs)是一种将物理定律嵌入神经网络训练过程的新型深度学习方法。2019年由Raissi等人提出后,PINNs迅速成为科学计算和工程仿真的重要工具。本文将系统介绍PINN的基本原理、实现方法和典型应用。

传统科学计算 vs PINN

传统方法的局限

传统科学计算方法(如有限元法、有限差分法)在解决偏微分方程时面临以下挑战:

  1. 网格依赖:计算复杂度随网格数量指数增长
  2. 高维问题:维度灾难导致计算不可行
  3. 复杂边界:不规则边界处理困难
  4. 参数辨识:反问题求解效率低

PINN的创新思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 传统方法 vs PINN 对比
comparison = {
'traditional_method': {
'approach': '离散化求解',
'grid': '需要精细网格划分',
'dimensionality': '高维计算困难',
'boundary': '边界处理复杂',
'flexibility': '参数变化需重算'
},
'pinn_method': {
'approach': '连续函数近似',
'grid': '无网格或稀疏点',
'dimensionality': '天然处理高维',
'boundary': '边界条件软约束',
'flexibility': '易于迁移学习'
}
}

PINN核心原理

问题定义

考虑一个一般的偏微分方程:

$$
u_t + \mathcal{N}[u] = 0, \quad (x,t) \in \Omega
$$

其中:

  • $u(x,t)$ 是未知函数
  • $\mathcal{N}[\cdot]$ 是非线性微分算子
  • $\Omega$ 是定义域
  • 配合初始条件和边界条件

神经网络架构

PINN使用神经网络 $u_\theta(x,t)$ 来近似解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import torch
import torch.nn as nn

class PINN(nn.Module):
"""
物理信息神经网络
近似解: u(x,t; θ) ≈ u(x,t)
"""

def __init__(self, input_dim=2, output_dim=1,
hidden_layers=[64, 64, 64, 64]):
super().__init__()

# 通用函数逼近器(FFC)
layers = []
in_features = input_dim

for hidden_dim in hidden_layers:
layers.extend([
nn.Linear(in_features, hidden_dim),
nn.Tanh() # 或Swish, GELU等
])
in_features = hidden_dim

layers.append(nn.Linear(in_features, output_dim))

self.network = nn.Sequential(*layers)

# 存储训练点
self.collocation_points = None

def forward(self, x, t):
"""前向传播"""
# 合并输入
inputs = torch.cat([x, t], dim=1)
return self.network(inputs)

物理定律嵌入:残差计算

PINN的核心创新是将偏微分方程作为损失函数的一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class PINNWithPhysics(nn.Module):
"""带物理约束的PINN"""

def __init__(self, input_dim, output_dim, pde_type='burgers'):
super().__init__()
self.network = PINN(input_dim, output_dim)
self.pde_type = pde_type

def compute_pde_residual(self, x, t):
"""
计算偏微分方程残差
这是PINN的核心!
"""
x.requires_grad = True
t.requires_grad = True

# 网络预测
u = self.network(x, t)

# 计算导数(自动微分)
u_t = torch.autograd.grad(
u, t, grad_outputs=torch.ones_like(u),
create_graph=True
)[0]

# 计算空间导数
u_x = torch.autograd.grad(
u, x, grad_outputs=torch.ones_like(u),
create_graph=True
)[0]

# 计算二阶导数
u_xx = torch.autograd.grad(
u_x, x, grad_outputs=torch.ones_like(u_x),
create_graph=True
)[0]

# 根据不同PDE计算残差
if self.pde_type == 'burgers':
# Burgers方程: u_t + u*u_x = ν*u_xx
residual = u_t + u * u_x - 0.01 * u_xx
elif self.pde_type == 'heat':
# 热方程: u_t = α*u_xx
residual = u_t - 0.01 * u_xx
elif self.pde_type == 'wave':
# 波动方程: u_tt = c^2*u_xx
u_tt = torch.autograd.grad(
u_t, t, grad_outputs=torch.ones_like(u_t),
create_graph=True
)[0]
residual = u_tt - 0.1 * u_xx
else:
raise ValueError(f"Unknown PDE type: {self.pde_type}")

return residual

损失函数设计

PINN的损失函数由多部分组成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def compute_loss(self, x, t, u_train, x_bc, t_bc, u_bc, 
x_ic, t_ic, u_ic):
"""
计算总损失函数

Loss = w_ic * Loss_ic + w_bc * Loss_bc + w_pde * Loss_pde + w_data * Loss_data
"""

# 1. PDE残差损失(支配项)
pde_residual = self.compute_pde_residual(x, t)
loss_pde = torch.mean(pde_residual ** 2)

# 2. 边界条件损失
u_bc_pred = self.network(x_bc, t_bc)
loss_bc = torch.mean((u_bc_pred - u_bc) ** 2)

# 3. 初始条件损失
u_ic_pred = self.network(x_ic, t_ic)
loss_ic = torch.mean((u_ic_pred - u_ic) ** 2)

# 4. 数据驱动损失(如果有观测数据)
if u_train is not None:
u_data_pred = self.network(x_data, t_data)
loss_data = torch.mean((u_data_pred - u_train) ** 2)
else:
loss_data = torch.tensor(0.0)

# 加权求和
total_loss = (
self.w_ic * loss_ic +
self.w_bc * loss_bc +
self.w_pde * loss_pde +
self.w_data * loss_data
)

return total_loss, {
'loss_pde': loss_pde.item(),
'loss_bc': loss_bc.item(),
'loss_ic': loss_ic.item(),
'loss_data': loss_data.item()
}

完整训练流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class PINNTrainer:
"""PINN训练器"""

def __init__(self, pinn_model, optimizer_config):
self.model = pinn_model
self.optimizer = torch.optim.Adam(
self.model.parameters(),
lr=optimizer_config.get('lr', 1e-3)
)
self.scheduler = torch.optim.lr_scheduler.StepLR(
self.optimizer, step_size=1000, gamma=0.95
)

def generate_collocation_points(self, n_points, domain_bounds):
"""生成配置点"""
x = torch.linspace(
domain_bounds['x_min'], domain_bounds['x_max'],
n_points
).reshape(-1, 1)
t = torch.linspace(
domain_bounds['t_min'], domain_bounds['t_max'],
n_points
).reshape(-1, 1)
return x, t

def train(self, n_iterations, n_collocation_points):
"""训练循环"""
self.model.train()
history = {'total_loss': [], 'components': []}

for iteration in range(n_iterations):
# 1. 生成/更新配置点
x, t = self.generate_collocation_points(n_collocation_points,
self.domain_bounds)

# 2. 生成边界和初始条件点
x_bc, t_bc, u_bc = self.get_boundary_points()
x_ic, t_ic, u_ic = self.get_initial_points()

# 3. 前向传播
loss, components = self.model.compute_loss(
x, t, None, x_bc, t_bc, u_bc, x_ic, t_ic, u_ic
)

# 4. 反向传播
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
self.scheduler.step()

# 5. 记录历史
history['total_loss'].append(loss.item())
history['components'].append(components)

if iteration % 100 == 0:
print(f"Iter {iteration}: Loss={loss.item():.6f}")

return history

实际应用示例:Burgers方程

问题描述

Burgers方程:
$$
u_t + u u_x = \nu u_{xx}
$$

初始条件:$u(x,0) = -\sin(\pi x)$
边界条件:$u(-1,t) = u(1,t) = 0$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def solve_burgers_equation():
"""
求解Burgers方程
"""
# 定义PINN模型
model = PINNWithPhysics(
input_dim=2,
output_dim=1,
pde_type='burgers'
)

# 创建训练器
trainer = PINNTrainer(model, {'lr': 1e-3})
trainer.domain_bounds = {
'x_min': -1.0, 'x_max': 1.0,
't_min': 0.0, 't_max': 1.0
}

# 训练
history = trainer.train(
n_iterations=20000,
n_collocation_points=1000
)

# 预测
model.eval()
x_test = torch.linspace(-1, 1, 100).reshape(-1, 1)
t_test = torch.ones(100, 1) * 0.5 # t = 0.5

with torch.no_grad():
u_pred = model(x_test, t_test)

return u_pred.numpy()

PINN的优势与挑战

优势

特性 说明
无网格 避免复杂网格生成
高维友好 适合高维反问题
数据融合 可结合物理模型和数据
端到端 无需特征工程
迁移学习 跨问题迁移

挑战与解决方案

挑战 解决方案
训练不稳定 课程学习、变换学习
欠约束 多样化采样策略
精度不足 自适应权重、残差自适应
计算成本 并行计算、硬件加速

自适应采样策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class AdaptiveSampling:
"""自适应采样提高精度"""

@staticmethod
def residual_based_sampling(model, n_points, n_iterations):
"""
基于残差的自适应采样
在残差大的区域增加采样点
"""
all_points = []
all_residuals = []

for _ in range(n_iterations):
# 随机采样
x = torch.rand(n_points, 1) * 2 - 1 # [-1, 1]
t = torch.rand(n_points, 1) # [0, 1]

# 计算残差
residual = model.compute_pde_residual(x, t)

all_points.append(torch.cat([x, t], dim=1))
all_residuals.append(residual.abs())

# 合并并选择残差大的点
points = torch.cat(all_points, dim=0)
residuals = torch.cat(all_residuals, dim=0)

# 选择top-k个高残差点
k = n_points
_, indices = torch.topk(residuals.squeeze(), k)
return points[indices]

PINN的扩展与变体

1. 动态系统:Neural ODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class NeuralODE_PINN(nn.Module):
"""用于常微分方程的PINN"""

def __init__(self, hidden_dim=64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(1, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, 1)
)

def forward(self, t):
return self.net(t)

def compute_ode_residual(self, t, y0=1.0):
"""
求解 dy/dt = -0.5*y
"""
t.requires_grad = True
y = self.net(t)

dy_dt = torch.autograd.grad(
y, t, grad_outputs=torch.ones_like(y),
create_graph=True
)[0]

# ODE残差: dy/dt + 0.5*y = 0
residual = dy_dt + 0.5 * y
return residual

2. 分数阶微分方程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class FractionalPINN(nn.Module):
"""分数阶PINN"""

def __init__(self, alpha=0.5):
super().__init__()
self.alpha = alpha # 分数阶导数的阶

def fractional_derivative(self, x, y):
"""
近似计算Riemann-Liouville分数阶导数
使用短时记忆公式
"""
n = len(x)
h = (x[-1] - x[0]) / n

frac_der = torch.zeros_like(y)
for i in range(n):
for j in range(i + 1):
coeff = ((i - j + 1) ** self.alpha -
(i - j) ** self.alpha)
frac_der[i] += coeff * y[j]

return h ** (-self.alpha) * frac_der

实践建议

1. 架构选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 不同PDE类型推荐的网络架构
architecture_guide = {
'低维光滑解': {
'layers': [2] + [20] * 3 + [1],
'activation': 'tanh',
'learning_rate': 1e-3
},
'高维问题': {
'layers': [d] + [128] * 5 + [1],
'activation': 'tanh',
'learning_rate': 1e-4
},
'多尺度解': {
'layers': [2] + [64, 128, 64, 32] + [1],
'activation': 'sin', # Fourier特征
'learning_rate': 1e-3
}
}

2. 损失权重调节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 动态权重调整策略
class DynamicWeighting:
"""动态损失权重"""

@staticmethod
def suga_next_weight(gradients, prev_weights, lr=1e-3):
"""
SGDA权重更新
基于梯度调整权重
"""
new_weights = prev_weights + lr * torch.stack(gradients)
return torch.relu(new_weights) + 1e-8

@staticmethod
def gradnorm_weight(losses, model):
"""
GradNorm权重平衡
"""
weights = []
for loss in losses:
grad = torch.autograd.grad(
loss, model.parameters(),
retain_graph=True
)[0]
weights.append(grad.norm())
return weights

总结

PINN是一种革命性的科学计算方法,它将深度学习的表达能力与物理定律的约束相结合,为求解偏微分方程提供了一种全新的范式。尽管面临一些挑战,但随着算法和硬件的不断进步,PINN在科学计算、工程仿真等领域的应用前景广阔。


推荐阅读:

  • Raissi et al. “Physics-Informed Neural Networks” (2019)
  • 《Deep Learning for PDEs》
  • 《Scientific Machine Learning》
© 2019-2026 ovo$^{mc^2}$ All Rights Reserved. | 站点总访问 28969 次 | 访客 19045
Theme by hiero