2. 一维波动方程的波速辨识
任务目标
给定一维波动方程中若干时空点的位移观测数据,利用物理信息神经网络(PINN)反演未知波速参数 \( c \)。观测数据由已知波速 \( c_{\text{true}} = 2.0 \) 的正问题生成,并叠加不同强度的高斯噪声,考察反演结果随噪声水平的变化。
问题设定与观测数据
考虑一维波动方程初边值问题:
\[ u_{tt} = c^2 u_{xx}, \quad x \in (0, 1), \ t \in (0, 1) \]
边界条件:\( u(0,t) = u(1,t) = 0 \)
初始条件:\( u(x,0) = \sin(\pi x) \), \( u_t(x,0) = 0 \)
该问题的解析解为 \( u(x,t) = \sin(\pi x)\cos(c\pi t) \)。取 \( c_{\text{true}} = 2.0 \),在 \( x \in [0,1] \), \( t \in [0,1] \) 内均匀采样 \( N = 2000 \) 个时空点作为观测位置,计算解析解并添加高斯噪声 \( \mathcal{N}(0, \sigma^2) \),其中 \( \sigma \) 分别取 0(无噪声)、0.01、0.05,生成三组观测数据。
PINN结构与损失函数
神经网络 \( \hat{u}(x,t;\theta) \) 以 \( (x,t) \) 为输入,输出位移。总损失为:
\[ \mathcal{L} = \mathcal{L}_{\text{data}} + \lambda \mathcal{L}_{\text{PDE}} \]
其中数据损失:
\[ \mathcal{L}_{\text{data}} = \frac{1}{N}\sum_{i=1}^{N} \left( \hat{u}(x_i,t_i) - u_{\text{obs}}^{(i)} \right)^2 \]
PDE残差损失(在配点 \( (x_r, t_r) \) 上计算,\( M=1000 \)):
\[ \mathcal{L}_{\text{PDE}} = \frac{1}{M}\sum_{j=1}^{M} \left( \hat{u}_{tt}(x_r^{(j)}, t_r^{(j)}) - c^2 \hat{u}_{xx}(x_r^{(j)}, t_r^{(j)}) \right)^2 \]
波速 \( c \) 作为可训练参数参与优化。取 \( \lambda = 1.0 \)。
关键完整代码
import numpy as np
import torch
import torch.nn as nn
# ---------- 1. 生成观测数据 ----------
def generate_data(c_true, N=2000, sigma=0.0, seed=0):
rng = np.random.default_rng(seed)
x = rng.uniform(0, 1, N)
t = rng.uniform(0, 1, N)
u_exact = np.sin(np.pi * x) * np.cos(c_true * np.pi * t)
noise = rng.normal(0, sigma, N)
u_obs = u_exact + noise
return x, t, u_obs
# ---------- 2. PINN 网络 ----------
class PINN(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(2, 50), nn.Tanh(),
nn.Linear(50, 50), nn.Tanh(),
nn.Linear(50, 50), nn.Tanh(),
nn.Linear(50, 1)
)
# 待反演参数 c,初始值 1.0
self.c = nn.Parameter(torch.tensor(1.0, dtype=torch.float32))
def forward(self, x, t):
xt = torch.cat([x, t], dim=1)
return self.net(xt).squeeze(-1)
def pde_residual(self, x, t):
x = x.clone().requires_grad_(True)
t = t.clone().requires_grad_(True)
u = self.forward(x, t)
u_t = torch.autograd.grad(u, t, grad_outputs=torch.ones_like(u), create_graph=True)[0]
u_tt = torch.autograd.grad(u_t, t, grad_outputs=torch.ones_like(u_t), 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]
return u_tt - self.c**2 * u_xx
# ---------- 3. 训练 ----------
def train_pinn(x_obs, t_obs, u_obs, sigma, epochs=3000, lr=1e-3):
model = PINN()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
x_t = torch.tensor(x_obs, dtype=torch.float32).unsqueeze(1)
t_t = torch.tensor(t_obs, dtype=torch.float32).unsqueeze(1)
u_t = torch.tensor(u_obs, dtype=torch.float32)
# 配点(PDE残差位置)
rng = np.random.default_rng(1)
x_r = torch.tensor(rng.uniform(0, 1, 1000), dtype=torch.float32).unsqueeze(1)
t_r = torch.tensor(rng.uniform(0, 1, 1000), dtype=torch.float32).unsqueeze(1)
for epoch in range(epochs):
optimizer.zero_grad()
u_pred = model(x_t, t_t)
loss_data = torch.mean((u_pred - u_t)**2)
res = model.pde_residual(x_r, t_r)
loss_pde = torch.mean(res**2)
loss = loss_data + loss_pde
loss.backward()
optimizer.step()
if (epoch+1) % 1000 == 0:
print(f"epoch {epoch+1}, loss_data={loss_data.item():.2e}, "
f"loss_pde={loss_pde.item():.2e}, c={model.c.item():.4f}")
return model.c.item()
# ---------- 4. 执行反演 ----------
if __name__ == "__main__":
c_true = 2.0
for sigma in [0.0, 0.01, 0.05]:
x, t, u = generate_data(c_true, sigma=sigma)
c_est = train_pinn(x, t, u, sigma)
print(f"sigma={sigma:.2f}: c_est={c_est:.4f}, 相对误差={abs(c_est-c_true)/c_true*100:.2f}%")
运行方式
需要安装 numpy 和 torch(CPU版本即可)。将上述代码保存为 wave_pinn.py,在终端执行:
python wave_pinn.py
验证方法与判定标准
程序依次对三种噪声水平执行反演,每个噪声水平训练3000轮。判定通过条件:
- 训练过程中损失函数(尤其是数据损失)持续下降并趋于稳定;
- 无噪声(σ=0)时,反演波速 \( c \) 接近真值2.0,相对误差小于5%;
- 随着噪声增大(σ=0.01→0.05),反演结果可能出现偏差增大,但应保持在合理范围(σ=0.05时相对误差通常不超过15%),且训练仍能收敛。
注意:由于网络随机初始化和Adam优化器的随机性,每次运行结果略有差异,属正常现象。若σ=0.05时误差明显偏大,可适当增加训练轮数至5000或调整学习率。
常见问题
- 梯度计算失败:确保输入
x、t在计算PDE残差前调用了requires_grad_(True),且网络输出为标量(已通过squeeze(-1)处理)。 - 反演不收敛:尝试增大
lr至 5e-3,或增加网络宽度(如每层80个神经元)。 - 边界条件未显式加入:本实现通过数据点覆盖边界附近区域,由数据损失隐式约束边界行为。若需严格满足边界条件,可在损失中额外添加边界残差项。