現(xiàn)PINN:一維泊松方程實(shí)操對(duì)比)
PINNPhysics-Informed Neural Networks物理信息神經(jīng)網(wǎng)絡(luò)在最近幾年的工程應(yīng)用里越來越常見很多做流體、固體、熱傳導(dǎo)、電磁場(chǎng)甚至飛行軌跡預(yù)測(cè)的同學(xué)都在嘗試把物理方程直接放進(jìn)神經(jīng)網(wǎng)絡(luò)的損失函數(shù)里。但真正動(dòng)手時(shí)大多數(shù)人遇到的第一個(gè)問題不是方程怎么離散而是框架選 PyTorch 還是 TensorFlow以及框架裝好之后如何用自動(dòng)微分把二階導(dǎo)數(shù)寫對(duì)。作為 PINN 入門系列課程的第 11 講這篇不堆理論而是從環(huán)境配置開始把一個(gè)一維泊松方程的最小 PINN 案例分別用 PyTorch 和 TensorFlow 跑通并把兩者的網(wǎng)絡(luò)定義、求導(dǎo)方式、訓(xùn)練循環(huán)、常見報(bào)錯(cuò)和工程選型放在一起對(duì)比。讀完應(yīng)該能對(duì)“PINN 到底要框架做什么”有一個(gè)完整認(rèn)知也能直接拿這個(gè)最小案例去擴(kuò)展自己的方程。1. 先理解 PINN 的工作機(jī)制再談框架選型1.1 PINN 在做什么讓神經(jīng)網(wǎng)絡(luò)去擬合物理方程普通神經(jīng)網(wǎng)絡(luò)訓(xùn)練時(shí)損失函數(shù)通常來自數(shù)據(jù)標(biāo)簽比如分類用交叉熵、回歸用均方誤差。PINN 的差別在于除了數(shù)據(jù)驅(qū)動(dòng)它還把物理方程本身當(dāng)作約束。假設(shè)要求解一個(gè)微分方程F(u, ?u/?x, ?2u/?x2) 0, x ∈ Ω邊界條件為 B(u) 0, x ∈ ?Ω。PINN 的做法是構(gòu)造一個(gè)神經(jīng)網(wǎng)絡(luò) u_θ(x)其中 θ 是待學(xué)習(xí)的參數(shù)然后定義損失函數(shù)L L_pde λ_bc · L_bc其中 L_pde 是方程殘差的均方誤差也就是把網(wǎng)絡(luò)輸出代入方程后左邊不等于 0 的程度L_bc 是邊界條件的誤差。訓(xùn)練完成后網(wǎng)絡(luò)輸出 u_θ(x) 就近似滿足方程和邊界條件。這里最關(guān)鍵的一步是計(jì)算網(wǎng)絡(luò)輸出對(duì)輸入 x 的導(dǎo)數(shù)。二階甚至更高階導(dǎo)數(shù)在方程里很常見所以框架的自動(dòng)微分Automatic DifferentiationAD能力直接決定了 PINN 代碼的寫法??梢哉fPINN 的代碼復(fù)雜度大部分不在網(wǎng)絡(luò)結(jié)構(gòu)而在“如何把導(dǎo)數(shù)寫對(duì)”。1.2 框架在 PINN 中承擔(dān)哪幾層職責(zé)把 PINN 訓(xùn)練過程拆開看框架至少承擔(dān)五層職責(zé)網(wǎng)絡(luò)結(jié)構(gòu)MLP、卷積、殘差連接等層定義和參數(shù)管理。自動(dòng)微分計(jì)算 u 對(duì) x 的一階、二階導(dǎo)數(shù)這是 PINN 區(qū)別于普通監(jiān)督學(xué)習(xí)的核心。優(yōu)化器Adam、L-BFGS 等參數(shù)更新算法。設(shè)備調(diào)度CPU/GPU 的張量計(jì)算和顯存管理。模型保存、加載、推理和部署。PyTorch 和 TensorFlow 在這五層都能完成但寫法和生態(tài)側(cè)重點(diǎn)不同。選型時(shí)不是看“哪個(gè)更好”而是看“哪個(gè)更適合你的方程、團(tuán)隊(duì)和部署環(huán)境”。1.3 PyTorch 和 TensorFlow 對(duì) PINN 的關(guān)鍵能力差異PyTorch 的自動(dòng)微分基于動(dòng)態(tài)計(jì)算圖每次前向傳播都會(huì)重新建立計(jì)算圖使用torch.autograd.grad可以非常直觀地取出任意中間變量對(duì)輸入的導(dǎo)數(shù)。TensorFlow 在 eager 模式下使用tf.GradientTape記錄前向過程再調(diào)用gradient得到導(dǎo)數(shù)。求二階導(dǎo)數(shù)時(shí)PyTorch 需要create_graphTrueTensorFlow 需要嵌套兩層GradientTape。兩者難度都不高但初次接觸時(shí)容易在 API 細(xì)節(jié)上卡住。能力維度PyTorchTensorFlow自動(dòng)微分入口torch.autograd.gradtf.GradientTape計(jì)算圖模式動(dòng)態(tài)圖為主eager 模式為主也可靜態(tài)圖二階導(dǎo)數(shù)寫法create_graphTrue兩層GradientTape嵌套自定義訓(xùn)練循環(huán)通常手寫for循環(huán)直觀tf.GradientTape內(nèi)手寫也可用 Keras社區(qū)與論文復(fù)現(xiàn)科研論文默認(rèn)選擇多工業(yè)界存量項(xiàng)目多部署生態(tài)TorchScript、TorchServeTF Serving、TFLite鏈路更完整與 PINN 庫結(jié)合DeepXDE 可選 PyTorch 后端DeepXDE 原生支持 TF 后端從 PINN 角度看兩種框架都能完成同樣的事情真正影響體驗(yàn)的是求導(dǎo) API 的書寫習(xí)慣以及后續(xù)要接入的庫是哪個(gè)生態(tài)。2. 環(huán)境準(zhǔn)備PyTorch 與 TensorFlow 安裝注意事項(xiàng)很多 PINN 新手不是死在方程推導(dǎo)而是死在環(huán)境安裝。PyTorch 和 TensorFlow 對(duì) CUDA 版本、Python 版本、驅(qū)動(dòng)版本都有要求裝錯(cuò)之后常見的現(xiàn)象是import torch成功但torch.cuda.is_available()返回False或者 TensorFlow 能裝上但訓(xùn)練時(shí)根本沒用上 GPU。2.1 先確認(rèn) Python、CUDA、GPU 驅(qū)動(dòng)版本安裝之前先用下面三條命令確認(rèn)本機(jī)狀態(tài)python --version nvidia-smi nvcc --version這里要澄清一個(gè)常見誤解nvidia-smi顯示的是當(dāng)前 GPU 驅(qū)動(dòng)支持的最高 CUDA 版本并不代表你已經(jīng)安裝了對(duì)應(yīng)版本的 CUDA 工具包nvcc --version才是已安裝 CUDA 工具包的版本。PyTorch 和 TensorFlow 的 pip 包通常自帶運(yùn)行時(shí)所需的 CUDA 庫不一定需要單獨(dú)安裝完整 CUDA 工具包但驅(qū)動(dòng)版本必須足夠新。如果本機(jī)沒有 NVIDIA GPU或者只是在學(xué)習(xí)階段先用 CPU 版本跑通流程也完全可行。PINN 的最小案例計(jì)算量不大CPU 上幾分鐘就能收斂。生產(chǎn)環(huán)境或大規(guī)模方程再考慮 GPU。2.2 使用虛擬環(huán)境安裝 PyTorch推薦用 Anaconda 創(chuàng)建獨(dú)立虛擬環(huán)境避免把系統(tǒng) Python 環(huán)境搞亂conda create -n pinn python3.10 -y conda activate pinnCPU 版本直接安裝pip install torch torchvision torchaudioGPU 版本需要到 PyTorch 官網(wǎng)選擇 CUDA 版本然后執(zhí)行對(duì)應(yīng)命令。以 CUDA 12.1 為例安裝命令形如pip install torch --index-url https://download.pytorch.org/whl/cu121這里要特別說明上面的cu121只是示例實(shí)際安裝前要到官網(wǎng)安裝頁面確認(rèn)你需要的 CUDA 版本和 Python 版本。如果使用昇騰 NPU 等特定硬件平臺(tái)還需要到對(duì)應(yīng)廠商提供的安裝源獲取適配版本。不要照抄網(wǎng)上任意一條命令版本不匹配是 PINN 環(huán)境問題的主要來源。torchvision和torchaudio對(duì) PINN 本身不是必需的但保持同一套版本安裝可以避免后續(xù)引入其他視覺任務(wù)時(shí)產(chǎn)生依賴沖突。2.3 使用虛擬環(huán)境安裝 TensorFlowTensorFlow 的安裝相對(duì)簡(jiǎn)單但版本策略變動(dòng)較多落地前一定要看官方文檔。常見的安裝命令是pip install tensorflow需要 CUDA 依賴時(shí)TensorFlow 官方也提供了一鍵安裝方式pip install tensorflow[and-cuda]需要注意TensorFlow 在 Windows 原生環(huán)境的 GPU 支持策略在不同版本之間有差異官方推薦在 Linux 或 WSL2 下使用 GPU 版本。如果只是在 Windows 上學(xué)習(xí)驗(yàn)證建議先跑 CPU 版本把 PINN 的求導(dǎo)和訓(xùn)練邏輯確認(rèn)好再遷移到 Linux 環(huán)境做大規(guī)模計(jì)算。2.4 安裝完成后必須做一次 GPU 可用性檢查環(huán)境裝完不要直接寫 PINN 代碼先檢查框架是否真的能調(diào)用 GPUpython -c import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.get_device_name(0))python -c import tensorflow as tf; print(tf.__version__, tf.config.list_physical_devices(GPU))正常的輸出應(yīng)該看到版本號(hào)、True以及 GPU 設(shè)備名。如果輸出False或者空列表通常說明 CUDA 驅(qū)動(dòng)版本過低、框架版本不匹配或者安裝的是 CPU 版。注意框架能import成功不代表能用 GPU。torch.cuda.is_available()和tf.config.list_physical_devices(GPU)才是判斷依據(jù)。3. 用 PyTorch 實(shí)現(xiàn)最小 PINN一維泊松方程環(huán)境就緒后先用一個(gè)最簡(jiǎn)單但有完整物理意義的方程跑通流程。這里選擇一維泊松方程因?yàn)樗芯_解方便驗(yàn)證 PINN 收斂質(zhì)量。3.1 問題定義和精確解求解區(qū)間為 x ∈ (0, 1)方程如下u(x) -π2 · sin(πx)邊界條件為 u(0) 0u(1) 0。這個(gè)方程的精確解是 u(x) sin(πx)所以訓(xùn)練完成后可以直接對(duì)比網(wǎng)絡(luò)輸出和精確解計(jì)算最大誤差和均方誤差。PINN 的損失函數(shù)由兩部分組成PDE 殘差損失將網(wǎng)絡(luò)輸出代入方程計(jì)算 u_θ(x) π2·sin(πx) 的均方誤差。邊界條件損失計(jì)算 u_θ(0) 和 u_θ(1) 的均方誤差。3.2 網(wǎng)絡(luò)結(jié)構(gòu)與激活函數(shù)選擇PINN 最常見的網(wǎng)絡(luò)結(jié)構(gòu)是 MLP隱藏層用 Tanh 激活。這里有一個(gè)關(guān)鍵點(diǎn)微分方程需要二階導(dǎo)數(shù)如果隱藏層使用 ReLU二階導(dǎo)數(shù)在絕大多數(shù)區(qū)域恒為 0網(wǎng)絡(luò)就沒有能力逼近 u(x) 的非零值訓(xùn)練必然失敗。Tanh、Sigmoid、Swish 這類光滑激活函數(shù)二階導(dǎo)數(shù)存在且非平凡更適合 PINN。網(wǎng)絡(luò)結(jié)構(gòu)寫成import torch import torch.nn as nn class PINN(nn.Module): def __init__(self): super().__init__() self.net nn.Sequential( nn.Linear(1, 20), nn.Tanh(), nn.Linear(20, 20), nn.Tanh(), nn.Linear(20, 20), nn.Tanh(), nn.Linear(20, 1) ) def forward(self, x): return self.net(x)輸入是 x輸出是 u_θ(x)。這個(gè)結(jié)構(gòu)沒有任何技巧但對(duì)一維問題已經(jīng)足夠。3.3 二階導(dǎo)數(shù)的自動(dòng)微分寫法PyTorch 求二階導(dǎo)數(shù)核心是torch.autograd.grad的create_graphTrue參數(shù)。第一步求一階導(dǎo)數(shù)時(shí)如果不設(shè)置create_graphTrue計(jì)算圖會(huì)被釋放后續(xù)無法再求二階導(dǎo)數(shù)。def pde_loss(model, x): x.requires_grad_(True) u model(x) u_x torch.autograd.grad( u, x, grad_outputstorch.ones_like(u), create_graphTrue )[0] u_xx torch.autograd.grad( u_x, x, grad_outputstorch.ones_like(u_x), create_graphTrue )[0] residual u_xx (torch.pi ** 2) * torch.sin(torch.pi * x) return torch.mean(residual ** 2)這里grad_outputs是鏈?zhǔn)椒▌t里的上游梯度因?yàn)?u 和 x 都是形狀為 (N, 1) 的張量所以用torch.ones_like(u)。[0]是因?yàn)間rad返回的是一個(gè)元組。3.4 完整訓(xùn)練代碼邊界條件損失、采樣點(diǎn)更新和訓(xùn)練循環(huán)如下import numpy as np model PINN() optimizer torch.optim.Adam(model.parameters(), lr1e-3) def bc_loss(model, x_bc): u_bc model(x_bc) return torch.mean(u_bc ** 2) x_bc torch.tensor([[0.0], [1.0]]) for epoch in range(3000): # 每個(gè) epoch 重新隨機(jī)采樣避免過擬合到固定網(wǎng)格 x_collocation torch.rand(256, 1) optimizer.zero_grad() loss_pde pde_loss(model, x_collocation) loss_bc bc_loss(model, x_bc) loss loss_pde loss_bc loss.backward() optimizer.step() if epoch % 500 0: print(fepoch {epoch}, loss_pde{loss_pde.item():.2e}, floss_bc{loss_bc.item():.2e})訓(xùn)練結(jié)束后用下面的代碼驗(yàn)證預(yù)測(cè)精度import matplotlib.pyplot as plt x_test torch.linspace(0, 1, 100).reshape(-1, 1) u_pred model(x_test).detach().numpy() u_exact np.sin(np.pi * x_test.numpy()) print(max error:, np.max(np.abs(u_pred - u_exact))) plt.plot(x_test.numpy(), u_exact, r-, labelexact) plt.plot(x_test.numpy(), u_pred, b--, labelPINN) plt.legend() plt.show()如果訓(xùn)練正常max error應(yīng)該能達(dá)到 1e-2 到 1e-3 量級(jí)曲線基本重合。注意采樣點(diǎn)放在循環(huán)內(nèi)部每個(gè) epoch 重新隨機(jī)生成這是 PINN 的常見做法。如果采樣點(diǎn)固定不變網(wǎng)絡(luò)容易在有限網(wǎng)格點(diǎn)上過擬合中間區(qū)域的方程殘差可能仍然很大。4. 用 TensorFlow 實(shí)現(xiàn)同一個(gè) PINN同一個(gè)方程用 TensorFlow 再寫一遍。目的是對(duì)比兩個(gè)框架在求導(dǎo)和訓(xùn)練循環(huán)上的差異而不是判斷誰優(yōu)誰劣。4.1 tf.GradientTape 求二階導(dǎo)的方式TensorFlow 中求二階導(dǎo)數(shù)需要使用兩層GradientTape。外層 tape 需要設(shè)置persistentTrue因?yàn)楹竺孢€要通過它計(jì)算二階導(dǎo)數(shù)內(nèi)層 tape 負(fù)責(zé)求一階導(dǎo)數(shù)。import tensorflow as tf import numpy as np class PINN(tf.keras.Model): def __init__(self): super().__init__() self.dense1 tf.keras.layers.Dense(20, activationtanh) self.dense2 tf.keras.layers.Dense(20, activationtanh) self.dense3 tf.keras.layers.Dense(20, activationtanh) self.dense4 tf.keras.layers.Dense(1) def call(self, x): x self.dense1(x) x self.dense2(x) x self.dense3(x) return self.dense4(x)4.2 完整訓(xùn)練代碼def pde_loss(model, x): with tf.GradientTape(persistentTrue) as tape_xx: tape_xx.watch(x) with tf.GradientTape() as tape_x: tape_x.watch(x) u model(x) u_x tape_x.gradient(u, x) u_xx tape_xx.gradient(u_x, x) del tape_xx residual u_xx (np.pi ** 2) * tf.sin(np.pi * x) return tf.reduce_mean(tf.square(residual)) model PINN() optimizer tf.keras.optimizers.Adam(learning_rate1e-3) for epoch in range(3000): x_collocation tf.random.uniform((256, 1)) x_bc tf.constant([[0.0], [1.0]]) with tf.GradientTape() as tape: loss_pde pde_loss(model, x_collocation) u_bc model(x_bc) loss_bc tf.reduce_mean(tf.square(u_bc)) loss loss_pde loss_bc grads tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(grads, model.trainable_variables)) if epoch % 500 0: print(fepoch {epoch}, loss_pde{loss_pde.numpy():.2e}, floss_bc{loss_bc.numpy():.2e})這里要注意幾點(diǎn)。第一外層GradientTape必須persistentTrue否則tape_xx.gradient調(diào)用一次后資源就被釋放。第二用完要del tape_xx手動(dòng)釋放資源避免長(zhǎng)時(shí)間訓(xùn)練時(shí)內(nèi)存累積。第三tf.random.uniform在循環(huán)內(nèi)生成新的采樣點(diǎn)和 PyTorch 版本的行為一致。4.3 兩種框架代碼結(jié)構(gòu)對(duì)比對(duì)比項(xiàng)PyTorchTensorFlow網(wǎng)絡(luò)定義nn.Module子類tf.keras.Model子類一階導(dǎo)數(shù)torch.autograd.grad(u, x, create_graphTrue)內(nèi)層tape.gradient(u, x)二階導(dǎo)數(shù)對(duì)一階結(jié)果再調(diào)grad外層tape.gradient(u_x, x)反向傳播loss.backward()optimizer.step()tape.gradient(loss, trainable_variables)apply_gradients采樣點(diǎn)生成torch.randtf.random.uniform從代碼量看兩者幾乎沒有差別。真正的差別在學(xué)習(xí)曲線PyTorch 的loss.backward()隱藏了梯度流向初學(xué)者更容易理解TensorFlow 需要手動(dòng)區(qū)分tape求的是網(wǎng)絡(luò)參數(shù)的梯度還是網(wǎng)絡(luò)輸出對(duì)輸入的梯度這兩類梯度在 PINN 里同時(shí)存在容易混淆。5. 運(yùn)行驗(yàn)證從損失曲線和預(yù)測(cè)結(jié)果判斷模型是否收斂5.1 期望的輸出和判斷標(biāo)準(zhǔn)訓(xùn)練 3000 個(gè) epoch 后正常情況下的輸出大概呈現(xiàn)這樣的趨勢(shì)epoch 0, loss_pde8.31e00, loss_bc5.62e-01 epoch 500, loss_pde1.20e-02, loss_bc1.90e-03 epoch 1000, loss_pde3.50e-03, loss_bc2.80e-04 epoch 1500, loss_pde1.80e-03, loss_bc5.10e-05 epoch 2000, loss_pde1.10e-03, loss_bc2.30e-05 epoch 2500, loss_pde7.60e-04, loss_bc1.10e-05 epoch 3000, loss_pde5.40e-04, loss_bc8.00e-06判斷標(biāo)準(zhǔn)不要只看總損失要分別看loss_pde和loss_bc。邊界條件損失應(yīng)該快速下降PDE 殘差損失下降會(huì)慢一些這是正?,F(xiàn)象。最終在測(cè)試點(diǎn)上的最大絕對(duì)誤差在 1e-2 到 1e-3 量級(jí)說明模型已經(jīng)學(xué)到方程的解。5.2 訓(xùn)練過程常見現(xiàn)象解讀如果訓(xùn)練曲線不符合預(yù)期可以從以下幾類現(xiàn)象判斷問題方向現(xiàn)象可能原因處理方向loss_pde一開始就很大且不降采樣點(diǎn)包含邊界附近非光滑區(qū)域或?qū)W習(xí)率過大降低學(xué)習(xí)率到 1e-4或先固定采樣點(diǎn)觀察loss_bc很快到 0但loss_pde幾乎不動(dòng)邊界條件主導(dǎo)了梯度方程殘差被忽略給loss_pde乘以較大權(quán)重例如 10 或 100loss_pde震蕩劇烈采樣點(diǎn)每個(gè) epoch 隨機(jī)變化損失本身有噪聲增大批大小或改用固定網(wǎng)格加隨機(jī)擾動(dòng)總損失很小但預(yù)測(cè)誤差很大PINN 出現(xiàn)平凡解或近似零解檢查激活函數(shù)是否光滑邊界條件權(quán)重是否過低5.3 學(xué)習(xí)環(huán)境與生產(chǎn)環(huán)境的差異學(xué)習(xí)階段CPU 跑這個(gè)最小案例完全夠用重點(diǎn)是理解求導(dǎo)和訓(xùn)練循環(huán)。但進(jìn)入生產(chǎn)環(huán)境后至少要補(bǔ)齊以下內(nèi)容GPU 訓(xùn)練和混合精度方程規(guī)模大時(shí)顯存和速度差異明顯。損失權(quán)重自適應(yīng)調(diào)整簡(jiǎn)單相加在復(fù)雜方程上很難收斂。模型保存、加載和版本管理訓(xùn)練好的 PINN 需要固化下來做推理。日志、監(jiān)控和結(jié)果校驗(yàn)不只是打印 loss還要定期對(duì)比已知工況的精確解或數(shù)值解?;貪L方案PINN 訓(xùn)練不穩(wěn)定時(shí)能回到上一次可用模型。6. PINN 實(shí)現(xiàn)中的 6 個(gè)常見坑6.1 激活函數(shù)選擇錯(cuò)誤導(dǎo)致二階導(dǎo)數(shù)恒為零現(xiàn)象訓(xùn)練正常執(zhí)行但loss_pde下降緩慢甚至不降最終預(yù)測(cè)結(jié)果是一條直線或接近零的曲線。原因隱藏層使用 ReLUReLU 的二階導(dǎo)數(shù)在正區(qū)間為 0負(fù)區(qū)間為 0僅在 x0 處未定義。對(duì)二階微分方程網(wǎng)絡(luò)根本無法表達(dá)非零的 u。解決換成 Tanh、Sigmoid、Swish 等光滑激活函數(shù)。PINN 的激活函數(shù)選型要優(yōu)先考慮“導(dǎo)數(shù)是否非平凡”而不是“效果好不好”。6.2 采樣點(diǎn)固定導(dǎo)致過擬合到網(wǎng)格點(diǎn)現(xiàn)象訓(xùn)練時(shí) loss 很低但在非采樣點(diǎn)的測(cè)試位置誤差明顯偏大。原因采樣點(diǎn)集合在整個(gè)訓(xùn)練過程中固定不變網(wǎng)絡(luò)只需在有限點(diǎn)上滿足方程殘差中間區(qū)域沒有被約束。解決每個(gè) epoch 重新隨機(jī)采樣或者采用固定網(wǎng)格加隨機(jī)擾動(dòng)的方式。這也是 DeepXDE 等庫默認(rèn)的做法。6.3 PDE 殘差和邊界條件損失量級(jí)失衡現(xiàn)象loss_bc降到 1e-5loss_pde還在 1e-1總損失被邊界條件主導(dǎo)方程約束形同虛設(shè)。原因兩類損失的量級(jí)天然不同直接相加時(shí)梯度被大項(xiàng)主導(dǎo)。解決給損失加權(quán)L λ_pde · L_pde λ_bc · L_bc。簡(jiǎn)單做法是固定權(quán)重例如 λ_pde10、λ_bc1復(fù)雜做法是訓(xùn)練過程中根據(jù)梯度統(tǒng)計(jì)自適應(yīng)調(diào)整。先做固定權(quán)重再考慮自適應(yīng)方案。6.4 PyTorch 沒有設(shè)置 create_graph 導(dǎo)致二階導(dǎo)數(shù)報(bào)錯(cuò)現(xiàn)象運(yùn)行到u_xx求導(dǎo)時(shí)報(bào)錯(cuò)信息包含element 0 of tensors does not require grad或類似提示。原因第一次調(diào)用torch.autograd.grad時(shí)沒有設(shè)置create_graphTrue計(jì)算圖被釋放后續(xù)無法繼續(xù)求導(dǎo)。解決一階求導(dǎo)時(shí)必須寫create_graphTrue。TensorFlow 版本則是外層GradientTape必須設(shè)persistentTrue使用后手動(dòng)del釋放。6.5 PyTorch 2.6 之后 torch.load 的 weights_only 報(bào)錯(cuò)現(xiàn)象加載模型權(quán)重時(shí)出現(xiàn)類似Weights only load failed ...的報(bào)錯(cuò)或者提示weights_only參數(shù)相關(guān)的 warning。原因PyTorch 2.6 開始torch.load默認(rèn)把weights_only設(shè)為True這是為了安全考慮防止加載 pickle 文件時(shí)執(zhí)行任意代碼。直接用舊方式加載包含非張量對(duì)象的 checkpoint 就會(huì)失敗。解決保存模型時(shí)優(yōu)先使用torch.save(model.state_dict(), path)加載時(shí)用model.load_state_dict(torch.load(path))。如果確實(shí)需要加載完整 checkpoint可以顯式設(shè)置torch.load(path, weights_onlyFalse)但要確認(rèn)文件來源可信。6.6 輸入特征未歸一化導(dǎo)致訓(xùn)練發(fā)散現(xiàn)象輸入 x 的取值范圍很大例如模擬區(qū)域是毫米到米級(jí)別訓(xùn)練過程中 loss 出現(xiàn)nan。原因PINN 的輸入和輸出尺度差異過大時(shí)網(wǎng)絡(luò)權(quán)重初始化與梯度更新不匹配容易發(fā)散。這一點(diǎn)在物理量綱差異大的問題上尤其明顯。解決把空間坐標(biāo)歸一化到 [0, 1] 或 [-1, 1]輸出量大時(shí)也做量綱歸一化。訓(xùn)練開始前先打印輸入、輸出和 loss確認(rèn)量級(jí)合理再訓(xùn)練。7. 框架選型建議與 PINN 工程化清單7.1 什么時(shí)候選 PyTorch什么時(shí)候選 TensorFlow從當(dāng)前工程實(shí)踐看可以按以下邏輯選擇如果你是科研方向、需要大量參考論文代碼選 PyTorch。PINN 相關(guān)論文的官方實(shí)現(xiàn)使用 PyTorch 的比例更高復(fù)現(xiàn)和研究阻力小。如果你在工業(yè)系統(tǒng)里做模型部署并且團(tuán)隊(duì)已經(jīng)熟悉 TF Serving 或 TFLite選 TensorFlow。TensorFlow 的部署鏈路更完整但前提是你的方程場(chǎng)景能容忍它的自定義訓(xùn)練循環(huán)復(fù)雜度。如果你不想自己寫網(wǎng)絡(luò)和求導(dǎo)直接用 DeepXDE 這類 PINN 框架。DeepXDE 支持多種后端上層 API 統(tǒng)一底層后端可以切換。如果只是學(xué)習(xí) PINN 概念兩個(gè)都裝一遍把同一個(gè)方程各跑一次。這個(gè)對(duì)比過程對(duì)理解自動(dòng)微分非常有幫助。選型沒有絕對(duì)答案關(guān)鍵是團(tuán)隊(duì)維護(hù)能力和部署環(huán)境。不要讓框架選擇成為 PINN 落地的阻礙。7.2 PINN 從原型到生產(chǎn)的工程清單寫一個(gè)可復(fù)用的清單每次開始新的 PINN 任務(wù)前逐項(xiàng)確認(rèn)檢查項(xiàng)確認(rèn)內(nèi)容方程形式明確最高階導(dǎo)數(shù)、邊界/初始條件、定義域范圍激活函數(shù)是否為光滑激活函數(shù)二階導(dǎo)數(shù)是否非平凡輸入歸一化空間坐標(biāo)是否歸一化物理量綱是否統(tǒng)一采樣策略是否每個(gè) epoch 重新采樣邊界點(diǎn)數(shù)量是否足夠損失權(quán)重PDE 殘差和邊界條件是否分開展示權(quán)重是否可調(diào)導(dǎo)數(shù)寫法PyTorch 是否create_graphTrueTF 是否persistentTrueGPU 驗(yàn)證cuda.is_available()或list_physical_devices是否為真結(jié)果校驗(yàn)是否有精確解、數(shù)值解或?qū)嶒?yàn)數(shù)據(jù)用于對(duì)比模型保存是否保存state_dict或完整模型是否記錄訓(xùn)練參數(shù)和損失曲線日志監(jiān)控是否記錄每個(gè) epoch 的分項(xiàng)損失是否能在訓(xùn)練異常時(shí)快速定位7.3 后續(xù)學(xué)習(xí)路徑跑通一維泊松方程后建議按這個(gè)順序擴(kuò)展把一維方程換成二維 Laplace 方程或 Poisson 方程理解多變量求導(dǎo)。加入時(shí)間維嘗試 Burgers 方程理解初始條件和時(shí)空采樣。引入更復(fù)雜的方程比如 Navier-Stokes 方程或彈道軌跡參數(shù)辨識(shí)類問題理解多損失項(xiàng)平衡。嘗試 DeepXDE 或 NVIDIA Modulus 這類專業(yè) PINN 庫把網(wǎng)絡(luò)結(jié)構(gòu)、采樣、損失加權(quán)交給成熟工具自己專注方程和物理約束設(shè)計(jì)。學(xué)習(xí)領(lǐng)域自適應(yīng)和權(quán)重點(diǎn)火這類進(jìn)階技巧它們能明顯提升復(fù)雜方程的收斂質(zhì)量。這一講的核心結(jié)論是PINN 與框架的關(guān)系不在于框架本身多強(qiáng)大而在于自動(dòng)微分 API 用得是否順手。把 PyTorch 和 TensorFlow 的最小案例各寫一遍二階導(dǎo)數(shù)、采樣點(diǎn)、損失權(quán)重的坑都踩過一遍之后再回到自己的方程思路會(huì)清楚很多。