【逆解析】実験データから「未知の物性値」を特定する技術|CAEエンジニアのための実装ガイド Vol.6
はじめに
CAEエンジニアの皆さん、**「実験と解析が合わない」**という悪夢に悩まされていませんか?
その原因の多くは、入力パラメータ(熱伝導率、摩擦係数、境界の熱伝達率など)の不確かさにあります。これらを合わせ込むために、何度もパラメータを変えて再計算していませんか?
第6章では、PINNsの最も強力な機能である**「逆解析(Inverse Problem)」**を実装します。
これは、「少数の実験データ(温度など)と物理法則を与え、未知の物性値をAIに自動推定させる」技術です。これこそが、物理シミュレーションとAIを融合させる最大のメリットです。
1. 目的(Physics)
**「1次元の熱伝導現象において、素材の熱拡散率
- 既知: 棒の数カ所に設置した温度センサーの時系列データ。
-
未知: 方程式内のパラメータ
(熱拡散率)。\alpha -
ゴール: 温度データと物理法則の整合性が取れるような
を、AIに逆算させる。\alpha
今回は、真の値(正解)を
2. 数式(Theory)
支配方程式(パラメータ \lambda を含む)
通常、
損失関数(Loss Function)
-
観測データの誤差 (
):L_{Data}
センサーの温度データと、AIの予測値が合っているか。
L_{Data} = \frac{1}{N} \sum (u_{pred} - u_{measured})^2 -
物理法則の誤差 (
):L_{PDE}
予測された温度分布と、現在推定中の を使って方程式が成立しているか。\lambda
L_{PDE} = \frac{1}{N} \sum \left| \frac{\partial u}{\partial t} - \lambda \frac{\partial^2 u}{\partial x^2} \right|^2
AIは
3. 実装(Code)
PyTorchでは、未知の物理定数を nn.Parameter で定義するだけで、ニューラルネットワークの重み(Weights)と同様に自動的に最適化されます。
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
# ==========================================
# 0. 設定 & 真のデータの作成
# ==========================================
torch.manual_seed(1234)
np.random.seed(1234)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 真のパラメータ (答え)
alpha_true = 0.01
# 解析解を使って「擬似的な実験データ」を作成する関数
def exact_solution(x, t):
# u(x,t) = e^(-alpha * pi^2 * t) * sin(pi * x)
return np.exp(-alpha_true * (np.pi**2) * t) * np.sin(np.pi * x)
# センサーデータの作成 (空間・時間でランダムに観測したとする)
N_obs = 100 # 観測点数 (たった100点!)
x_obs = np.random.rand(N_obs, 1)
t_obs = np.random.rand(N_obs, 1)
u_obs = exact_solution(x_obs, t_obs)
# テンソル化
x_train = torch.tensor(x_obs, dtype=torch.float32, requires_grad=True).to(device)
t_train = torch.tensor(t_obs, dtype=torch.float32, requires_grad=True).to(device)
u_train = torch.tensor(u_obs, dtype=torch.float32).to(device)
# ==========================================
# 1. PINNsモデル (パラメータ推定機能付き)
# ==========================================
class PINN_Inverse(nn.Module):
def __init__(self):
super(PINN_Inverse, self).__init__()
# 通常のネットワーク層
self.net = nn.Sequential(
nn.Linear(2, 40), nn.Tanh(),
nn.Linear(40, 40), nn.Tanh(),
nn.Linear(40, 40), nn.Tanh(),
nn.Linear(40, 1)
)
# ★ここが最重要ポイント★
# 未知パラメータ alpha を「学習可能な変数」として登録
# 初期値は 0.05 (正解の0.01とは大きく違う値を入れておく)
self.alpha = nn.Parameter(torch.tensor([0.05], device=device))
def forward(self, x, t):
return self.net(torch.cat([x, t], dim=1))
# ==========================================
# 2. 損失関数の定義
# ==========================================
def physics_loss(model, x, t):
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), create_graph=True)[0]
# 残差計算 (ここで model.alpha が使われる!)
# u_t - alpha * u_xx = 0
f = u_t - model.alpha * u_xx
return torch.mean(f**2)
# ==========================================
# 3. 学習ループ (逆解析の実行)
# ==========================================
model = PINN_Inverse().to(device)
# optimizerには model.parameters() が渡されるため、
# ネットワークの重みと一緒に self.alpha も更新される
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
epochs = 10000
alpha_history = []
loss_history = []
print(f"True Alpha: {alpha_true}")
print(f"Initial Guess: {model.alpha.item()}")
print("Start Inverse Analysis...")
for epoch in range(epochs):
optimizer.zero_grad()
# Loss 1: 観測データとの誤差 (Data Loss)
u_pred = model(x_train, t_train)
loss_data = torch.mean((u_pred - u_train)**2)
# Loss 2: 物理法則の誤差 (Physics Loss)
loss_physics = physics_loss(model, x_train, t_train)
loss = loss_data + loss_physics
loss.backward()
optimizer.step()
# 履歴の保存
current_alpha = model.alpha.item()
alpha_history.append(current_alpha)
loss_history.append(loss.item())
if epoch % 1000 == 0:
print(f"Epoch {epoch}: Loss {loss.item():.6f}, Estimated Alpha: {current_alpha:.5f}")
print("Training Finished.")
print(f"Final Estimated Alpha: {model.alpha.item():.5f}")
print(f"Error Rate: {abs(model.alpha.item() - alpha_true)/alpha_true * 100:.2f}%")
# ==========================================
# 4. 結果の可視化
# ==========================================
plt.figure(figsize=(10, 5))
# Alphaの収束履歴
plt.subplot(1, 2, 1)
plt.plot(alpha_history, label='Estimated Alpha', color='red', linewidth=2)
plt.axhline(y=alpha_true, color='black', linestyle='--', label='True Alpha (0.01)')
plt.title("Parameter Identification Process")
plt.xlabel("Epoch")
plt.ylabel("Alpha Value")
plt.legend()
plt.grid(True)
# 温度分布の比較 (t=0.5における断面)
x_test = torch.linspace(0, 1, 100).view(-1, 1).to(device)
t_test = torch.ones_like(x_test) * 0.5
with torch.no_grad():
u_pred_final = model(x_test, t_test).cpu().numpy()
u_true_final = exact_solution(x_test.cpu().numpy(), 0.5)
plt.subplot(1, 2, 2)
plt.plot(x_test.cpu(), u_true_final, 'k--', label='Exact Solution')
plt.plot(x_test.cpu(), u_pred_final, 'r-', label='PINN Prediction')
plt.title("Temperature Profile at t=0.5")
plt.xlabel("x")
plt.ylabel("u")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
4. 検証(Validation)
出力されたグラフ(左側)を見てください。 初期値 0.05 からスタートした赤い線(推定値)が、学習回数を重ねるごとに急降下し、最終的に黒い点線(正解 0.01)にピタリと吸い付いていく様子が確認できます。
私の環境での実行結果は以下の通りです。
True Alpha: 0.01000
Estimated: 0.00998
誤差: わずか 0.2%
これは、「温度分布のデータ」だけを見て、背後にある支配方程式の係数をAIが発見したことを意味します。
5. 考察(Discussion)
なぜこれが「10,000円の価値」なのか?
実務では、材料の物性値(熱伝導率やヤング率)が正確にわからないことが多々あります。 従来の手法(最小二乗法など)でこれを特定するには、何度もシミュレーションを回して比較する「反復計算」が必要で、膨大な時間がかかりました。
PINNsによる逆解析は、**「たった1回の学習」**でパラメータ特定とシミュレーション(代理モデル構築)を同時に完了させます。
応用アイデア
劣化診断: 配管の「摩擦係数」を逆解析し、新品時と比べて値が悪化していれば「錆や詰まりがある」と判断する。
医療応用: 血流データから血管の「硬さ(弾性係数)」を推定し、動脈硬化のリスクを診断する。
この技術は、単なるシミュレーションを超えて、現実世界の「デジタルツイン」を構築するためのコア技術となります。
(この記事は執筆中の書籍『CAEエンジニアのためのPINNs実装バイブル』の第6章です)
Discussion