智慧型控制 Intelligent Control

物理資訊神經網路 PINN

前面的神經網路模型完全靠資料學習,對物理一無所知:資料沒涵蓋的地方就只能亂猜。但工程上我們常常知道系統大致遵守的方程式,只是不知道某些參數,或量測點很少。物理資訊神經網路(physics-informed neural network,PINN;Raissi、Perdikaris 與 Karniadakis,2019)把已知的微分方程式放進訓練的損失函數,讓網路同時符合資料與物理。

做法 Formulation

用網路 \(h_\theta(t)\) 直接表示未知的解。若物理定律是 \(\mathcal N[h; c] = 0\)(例如一條微分方程式,\(c\) 是未知參數),損失函數有兩部分:

$$L(\theta, c) = \underbrace{\frac{1}{N_d}\sum_{i=1}^{N_d} \big(h_\theta(t_i) - h_i\big)^2}_{\text{資料誤差}} + \lambda\, \underbrace{\frac{1}{N_c}\sum_{j=1}^{N_c} \mathcal N[h_\theta; c](\tau_j)^2}_{\text{物理誤差}}$$ t神經網路1→32→32→1hθ​(t)資料誤差:在量測時刻Σ (hθ​(tᵢ) − hᵢ)² (只有 10 點)物理誤差:在 0~10 s 的配置點Σ (dhθ​/dt + c√(2g·hθ​))²dh/dt 由自動微分算出;c 也一起學總損失 = 資料誤差 + 物理誤差:網路要通過量測點,同時處處滿足微分方程式

範例:水槽排水 Example

同一個水槽,閥關閉、從 2 m 開始排水,水位遵守

$$\frac{dh}{dt} = -c\sqrt{2gh}$$

出水係數 \(c\) 當作未知(真值 0.05)。只在前 4 秒量到 10 個水位,量測雜訊的標準差 2 cm。目標是預測 0~10 s 的水位,並估計 \(c\)。比較兩個相同架構(1→32→32→1,tanh)、相同訓練步數的網路:一般網路只用資料誤差,PINN 再加上 0~10 s 間 101 個配置點的物理誤差:

# 物理資訊神經網路(PINN):水槽關閥排水,只量到前 4 秒的 10 個(有雜訊的)水位,
# 要預測到 10 秒,並同時估計未知的出水係數 c。已知的物理:dh/dt = −c√(2gh)
import json
import math
from pathlib import Path

import numpy as np
import torch
from torch import nn

torch.set_default_dtype(torch.float64)
G, C_TRUE, H0 = 9.8, 0.05, 2.0
truth = lambda t: np.maximum(math.sqrt(H0) - C_TRUE * math.sqrt(G / 2) * t, 0.0) ** 2   # 解析解(只用來產生資料與評分)

rng = np.random.default_rng(1)
t_d = np.linspace(0, 4, 10)
h_d = truth(t_d) + rng.normal(0, 0.02, len(t_d))                         # 量測:標準差 2 cm 的雜訊
td, hd = torch.tensor(t_d)[:, None], torch.tensor(h_d)[:, None]
tc = torch.linspace(0, 10, 101)[:, None].requires_grad_(True)           # 檢查物理的「配置點」:0~10 s
t_all = np.linspace(0, 10, 101)

def make_net(seed):
    torch.manual_seed(seed)
    return nn.Sequential(nn.Linear(1, 32), nn.Tanh(), nn.Linear(32, 32), nn.Tanh(), nn.Linear(32, 1))

def train(physics, iters=6000, lr=2e-3, snap=200):
    net = make_net(0)
    log_c = torch.tensor(math.log(0.1), requires_grad=True)             # 未知參數 c 也當成要學的量(初猜 0.1)
    opt = torch.optim.Adam(list(net.parameters()) + ([log_c] if physics else []), lr=lr)
    h = lambda t: net(t / 10)                                           # 時間先縮放到 0~1
    snaps = []
    for it in range(iters + 1):
        loss = torch.mean((h(td) - hd) ** 2)                            # 資料誤差
        if physics:
            hc = h(tc)
            dh = torch.autograd.grad(hc.sum(), tc, create_graph=True)[0]  # 自動微分求 dh/dt
            res = dh + log_c.exp() * torch.sqrt(2 * G * hc.clamp(min=0) + 1e-12)
            loss = loss + torch.mean(res ** 2)                          # 物理誤差:方程式在配置點上要成立
        if it % snap == 0:
            with torch.no_grad():
                snaps.append({"it": it, "h": h(torch.tensor(t_all)[:, None])[:, 0].tolist(), "c": float(log_c.exp())})
        if it < iters:
            opt.zero_grad(); loss.backward(); opt.step()
    return snaps

nn_s = train(False)
pinn_s = train(True)
far = t_all > 4
rm = lambda h, sel: float(np.sqrt(np.mean((h[sel] - truth(t_all[sel])) ** 2))) * 100
for name, s in (("一般神經網路(只用資料)", nn_s), ("PINN(資料+物理)", pinn_s)):
    hh = np.array(s[-1]["h"])
    print(f"{name}:0~4 s 的 RMSE {rm(hh, ~far):.2f} cm,4~10 s(沒有資料)的 RMSE {rm(hh, far):.2f} cm")
print(f"PINN 估計的出水係數 c = {pinn_s[-1]['c']:.4f}(真值 {C_TRUE})")

# 對照:這個例子剛好有解析解,√h 是時間的直線,直接對 √h 做直線擬合也能估 c
a, b = np.polyfit(t_d, np.sqrt(np.maximum(h_d, 0)), 1)
print(f"對照(解析解+直線擬合):c = {-a / math.sqrt(G / 2):.4f}")

Path(__file__).with_name("pinn_tank.json").write_text(json.dumps({
    "t": t_all.tolist(), "truth": truth(t_all).tolist(), "td": t_d.tolist(), "hd": h_d.tolist(),
    "nn": nn_s, "pinn": pinn_s}), encoding="utf-8")

⬇ 下載 pinn_tank.py(Python 3+PyTorch)

實際執行的輸出:

一般神經網路(只用資料):0~4 s 的 RMSE 0.61 cm,4~10 s(沒有資料)的 RMSE 22.50 cm
PINN(資料+物理):0~4 s 的 RMSE 0.76 cm,4~10 s(沒有資料)的 RMSE 0.60 cm
PINN 估計的出水係數 c = 0.0501(真值 0.05)
對照(解析解+直線擬合):c = 0.0499
024681000.511.52時間(s)水位(m)沒有量測資料0300060000.040.060.080.1訓練步數PINN 估計的 c真值 0.05真實水位量測(10 點,雜訊 2 cm)一般網路:4~10 s 誤差 22.5 cmPINN:0.6 cm,c = 0.0501

下方動畫播放兩個網路的訓練過程(範例每 200 步存下的預測),PINN 估計的 \(c\) 從初猜 0.1 逐步下降,沒有資料那一段的曲線跟著修正:第 600 步時 \(c = 0.064\),4~10 s 的誤差還有 4.5 cm;約 2400 步後 \(c\) 收斂到 0.05 附近,誤差降到 0.3 cm,之後在 0.15~0.6 cm 之間小幅擺動(Adam 的步長固定,接近最小值時會來回跳動,範例最後一步剛好落在 0.6 cm):

自製動畫(依控制理論數值模擬)

什麼時候該用 PINN When to Use

這個例子其實有解析解:\(\sqrt h = \sqrt{h_0} - c\sqrt{g/2}\,t\),也就是 \(\sqrt h\) 是時間的直線。直接對量測值的平方根做直線擬合,就得到 \(c = 0.0499\),又快又準。對於模型已知、低維、方程式好解的問題,傳統的參數估計(最小平方、卡爾曼濾波)通常比 PINN 簡單可靠;用 PINN 求解已知參數的方程式,也常比傳統數值方法(如 Runge–Kutta、有限元素)慢而且不準。

PINN 的價值在傳統方法不好處理的情況:

PINN 也有已知的困難:資料誤差與物理誤差的權重 \(\lambda\) 不好選;解變化很快(剛性)或時間範圍很長時常常訓練不起來;收斂慢,常需搭配 L-BFGS 等最佳化器。

相關的做法:灰箱模型 Gray-Box Models

另一種結合物理與網路的方式,是保留已知的方程式,只用網路取代未知的部分。例如水槽的進水量確定是 \(0.5u\),但出水口的特性不清楚,就寫成

$$\frac{dh}{dt} = 0.5u - \text{NN}(h)$$

再用數值積分模擬並對資料訓練(神經常微分方程式 neural ODE、universal differential equations 都屬於這類)。和純資料的 NARX 相比,這種模型自動滿足「質量守恆」等物理結構,外插也較可靠;和 PINN 相比,它直接得到一個可以模擬、可以拿去設計控制器的動態模型。