3. 案例3:PINN求解一维Burgers方程并分析激波捕捉
问题设定与方程无量纲化
一维含粘性 Burgers 方程是检验 PINN 激波捕捉能力的经典基准:
u_t + u·u_x = ν·u_xx, x ∈ [-1, 1], t ∈ [0, 1]
u(x, 0) = -sin(πx)
u(-1, t) = u(1, t) = 0
粘性系数 ν 控制陡峭程度:ν 越小,t≈0.5 附近梯度越陡,趋近于激波;ν 越大,解越平滑。本例取 ν = 0.01/π ≈ 0.00318 与 ν = 0.1/π ≈ 0.0318 两组做对照,前者是 PINN 文献中的标准算例,后者用于观察平滑化后的误差分布差异。
PINN 的损失由三部分组成:物理残差损失 L_r(在配点上计算 PDE 残差)、初值损失 L_0、边界损失 L_b。关键难点在于:均匀随机配点在激波附近密度不足,残差难以被压低,因此需要引入基于残差的自适应配点或在陡峭区加密的采样策略。
网络结构与训练实现
采用 4 层隐藏层、每层 64 个神经元的 tanh MLP,输入 (x, t) 归一化到 [-1, 1],输出 u。使用一阶自动微分计算 u_t、u_x,二阶自动微分计算 u_xx。优化分两阶段:先用 Adam(学习率 1e-3,10000 步)粗收敛,再用 L-BFGS 精调。
import torch
import torch.nn as nn
class PINN(nn.Module):
def __init__(self, width=64, depth=4):
super().__init__()
layers = [nn.Linear(2, width), nn.Tanh()]
for _ in range(depth - 1):
layers += [nn.Linear(width, width), nn.Tanh()]
layers += [nn.Linear(width, 1)]
self.net = nn.Sequential(*layers)
def forward(self, x, t):
return self.net(torch.cat([x, t], dim=1))
def pde_residual(model, x, t, nu):
x.requires_grad_(True); t.requires_grad_(True)
u = model(x, t)
u_t = torch.autograd.grad(u, t, torch.ones_like(u), create_graph=True)[0]
u_x = torch.autograd.grad(u, x, torch.ones_like(u), create_graph=True)[0]
u_xx = torch.autograd.grad(u_x, x, torch.ones_like(u_x), create_graph=True)[0]
return u_t + u * u_x - nu * u_xx
def sample_uniform(n, device):
x = torch.rand(n, 1, device=device) * 2 - 1
t = torch.rand(n, 1, device=device)
return x, t
def sample_shock_refined(n, device, x_c=0.0, sigma=0.15):
# 在 x≈0 的陡峭区按高斯密度加密,其余均匀
n_shock = n // 2
x_s = torch.randn(n_shock, 1, device=device) * sigma + x_c
x_s = x_s.clamp(-1, 1)
x_u, t_u = sample_uniform(n - n_shock, device)
x = torch.cat([x_s, x_u], dim=0)
t = torch.cat([torch.rand(n_shock, 1, device=device), t_u], dim=0)
return x, t
训练循环中,物理损失用均方残差,初值与边界损失用 MSE。下面给出单步损失计算的核心片段:
def total_loss(model, nu, n_col, n_ic, n_bc, device, refined=False):
if refined:
xc, tc = sample_shock_refined(n_col, device)
else:
xc, tc = sample_uniform(n_col, device)
r = pde_residual(model, xc, tc, nu)
loss_r = (r ** 2).mean()
x_ic = torch.rand(n_ic, 1, device=device) * 2 - 1
t_ic = torch.zeros_like(x_ic)
u_ic = -torch.sin(torch.pi * x_ic)
loss_0 = ((model(x_ic, t_ic) - u_ic) ** 2).mean()
t_bc = torch.rand(n_bc, 1, device=device)
xL = -torch.ones_like(t_bc); xR = torch.ones_like(t_bc)
loss_b = ((model(xL, t_bc)) ** 2).mean() + ((model(xR, t_bc)) ** 2).mean()
return loss_r + 100 * loss_0 + 100 * loss_b
初值与边界损失权重取 100,是因为它们的量级远小于残差项,需要放大才能有效约束。若权重过小,网络会优先压低残差而忽略边界,导致解在边界附近漂移。
激波区误差分布与配点策略对比
训练完成后,在规则网格(x 方向 256 点、t 方向 100 点)上评估预测解,并与高分辨率数值解(如 Chebfun 或有限差分细网格)对比。重点观察 t ∈ [0.4, 0.7]、x ∈ [-0.3, 0.3] 的陡峭梯度区域。下图展示了两种配点策略下误差在空间上的分布差异:
从图中可以读出两个关键结论:其一,误差峰值位置与解的陡峭区高度重合,说明 PINN 的困难不在于全局拟合,而在于局部高频结构的捕捉;其二,加密配点能显著压低峰值误差,但若加密范围过窄,平滑区可能因配点减少而精度下降,因此需要平衡。
粘性系数对误差分布的影响
固定配点策略(均匀 10000 点 + 激波区加密 5000 点),分别训练 ν = 0.01/π 与 ν = 0.1/π 两组模型,在相同测试网格上统计误差。下表给出示例性的相对 L2 误差与激波区最大误差(数值为教学示例,实际结果随随机种子与训练步数波动):
| 粘性系数 ν | 配点策略 | 全域相对 L2 误差 | 激波区最大绝对误差 |
|---|---|---|---|
| 0.01/π ≈ 0.00318 | 均匀 | 约 8.5e-2 | 约 1.2e-1 |
| 0.01/π ≈ 0.00318 | 激波区加密 | 约 3.2e-2 | 约 4.5e-2 |
| 0.1/π ≈ 0.0318 | 均匀 | 约 2.1e-2 | 约 3.0e-2 |
| 0.1/π ≈ 0.0318 | 激波区加密 | 约 1.5e-2 | 约 1.8e-2 |
规律很清晰:ν 越小,解的梯度越陡,均匀配点的误差越大,加密带来的收益也越明显;ν 增大后解本身更平滑,两种策略的差距收窄。这说明配点策略的选择应与问题的陡峭程度匹配,而不是一味加密。
实操要点与验证方法
- 残差监控:训练过程中分别记录 L_r、L_0、L_b,若 L_b 长期不降,说明边界权重偏小或网络容量不足。
- 自适应加密:可在训练若干步后,按残差大小对配点做重采样(RAD 类方法),把新配点集中到残差高的区域,比固定高斯加密更通用。
- 验证基准:用有限差分细网格或 Chebfun 生成参考解,在 t=0.5 的剖面上对比预测曲线,重点看激波位置与幅值是否偏移。
- 随机性控制:固定随机种子,至少跑 3 次取均值,避免单次训练结果误导结论。