M0.3 漂流的溫度計:全導數、微分穿過積分與 JVP
本篇重用M0.0霧中下山:Gradient 與方向·M0.2導航的三十秒:Taylor 展開與「假設直線」
起點:讀數上升了
你把一支溫度計綁在浮球上放進河裡,它跟著水漂,每分鐘回傳一次讀數。十分鐘後,讀數從 18.0 °C 升到 18.6 °C。
問題:河水變熱了嗎?
直覺說「讀數上升了,當然是變熱了」。寫下你有多確定。
再想一下,會冒出第二種可能:溫度計漂到了比較熱的地方——例如下游有一段淺灘,水本來就暖。這時候讀數上升,但任何一個固定位置的水溫可能一度都沒變。兩種解釋都說得通。只有一支會漂的溫度計,你分得出來嗎? 這一篇要回答的正是這件事:這兩種來源在數學上怎麼拆、拆開需要什麼、程式裡怎麼算。
課堂提問Q1
把情境翻成數學:這裡有幾個函數?溫度計的讀數是哪些東西的合成?我們想知道的是哪個量、拆成哪幾塊?
先想一想,再展開看整理後的答案
最直覺的說法只說「溫度是時間的函數」,這漏掉了最重要的一層。
這裡有兩個函數。第一個是溫度場 :在位置 、時間 ,那裡的水溫。它是一個定義在「位置 × 時間」上的函數;「水變熱」說的是 對 的變化,「下游比較暖」說的是 對 的變化。第二個是溫度計的路徑 :時間 時溫度計在哪裡。
溫度計的讀數是這兩個函數套在一起:。它只是時間的函數——這就是為什麼直覺會把它當成「水溫隨時間的變化」;但它裡面藏著 。
我們想知道的是 拆成兩塊:一塊只跟「同一個位置的水溫在變」有關,另一塊只跟「溫度計移動到了溫度不同的位置」有關。翻譯到這裡,工具已經呼之欲出——這是 chain rule,只是要把它用到「有兩種輸入、其中一種自己也在動」的情形。
電扶梯上走路
你在往上的電扶梯上往上走。你的高度在上升,原因有兩個:電扶梯本身在動(就算你站著不動也會上升),以及你自己在走(就算電扶梯停了你也會上升)。兩個效果直接相加。
而且它們可以分開量:站著不動,量到的是電扶梯的速度;等電扶梯停了再走,量到的是你自己的速度。只在動的電扶梯上走,你量到的是總和,拆不開——除非你另外知道其中一個。
河的情形一樣。「同一個位置的水溫在變」是電扶梯,「漂到別處」是走路,漂流的溫度計量到的是總和。
把兩個來源寫成數學
沿路徑的全導數。 設 對 、 都 differentiable, 是一條 differentiable 的路徑。讀數 的變化率是
是把 釘住、只讓 動的 partial derivative; 是 M0.0 的 gradient(對位置各分量的 partial derivative), 是漂速。第二項是 與 的內積——正是 directional derivative 乘上速率:往溫度變化最快的方向漂得越快,這一項越大。左邊叫 沿路徑的 total derivative(全導數)。
展開細節為什麼是相加(一階 Taylor 兩次,五行)
從 到 ,位置從 變成 ,。用一階 Taylor(M0.2)對兩個輸入同時展開:
小位移造成的變化是各方向變化的線性疊加——這就是 differentiable 的意思(局部像一個平面,只是現在的「平面」多了一個時間軸)。代 、除以 、取極限,就得到上面的式子。如果 在某處不 differentiable(例如兩股水溫不同的水流交會、溫度有跳躍),這個線性疊加不成立,式子在那一點失效。
微分穿過積分(Leibniz 法則)。 同一個「微分可以逐項拆開」的邏輯,再往前推一步。假設你關心的不是一支溫度計,而是很多支固定位置的溫度計的加權平均:
積分是「很多項相加」,微分對相加是線性的,所以自然會猜
這就是 Leibniz 法則:微分可以穿過積分符號,變成對被積函數的 partial derivative。 它成立的條件是 存在,並且 能被一個不依賴 、可積的函數壓住(技術名詞是 dominated convergence;直覺是「沒有任何一支溫度計的變化率大到壞掉整個平均」)。一般形式是 ——變數叫什麼不重要,重要的是被微分的變數不是積分變數,而且積分範圍不隨它改變。這條規則在之後很多推導裡是關鍵的一步,值得在這裡先站穩。
回到情境:一支溫度計拆不開
結論。 讀數十分鐘升 0.6 °C,這個數字是 ,是兩項的總和。「水變熱了」對應 ,「漂到暖處」對應 ;單靠 ,兩者的任何組合都能給出同樣的 0.6 °C。直覺「當然是變熱了」隱含了一個假設:,河水處處同溫——這正是需要被檢查的那個假設。
要補什麼才能拆開。 至少再量一個東西。選項一:在起點釘一支不動的溫度計,它讀到的就是 (電扶梯的速度);總和減掉它,剩下的就是漂移那一項。選項二:一張某一時刻的水溫地圖()加上浮球的 GPS(),直接算出第二項。舉個具體的數字:若釘住的溫度計十分鐘升了 0.3 °C,而浮球以 0.5 m/s 漂了 300 m、地圖上下游每公尺暖 0.001 °C,那麼兩項各 0.3 °C——「水變熱」只對了一半。
假設與失效點。 式子要求 對位置與時間都 differentiable。若浮球漂過一條支流的匯入口——那裡兩股不同溫度的水還沒混合、溫度幾乎是跳躍的—— 在匯入口沒有定義,讀數會在一分鐘內跳一大格,那一段的變化不能用兩項之和來解釋,也不該把它算進「水變熱」。
回到情境:程式裡怎麼算全導數
當 不是手寫的式子,而是一個程式(例如一個神經網路 ,輸入輸出都是高維向量),全導數 有一個計算上的陷阱: 是一個 的 Jacobian 矩陣。 是幾萬時,把它整個算出來要 次前向或反向傳播,記憶體 ,完全不可行。
但我們不需要整個 Jacobian,只需要它乘上一個向量 的結果——這叫 Jacobian-vector product(JVP)。Forward-mode 自動微分可以在一次前向傳播裡同時帶著「輸入往 方向動一點點,輸出跟著動多少」這個資訊,成本約是普通前向的兩到三倍,與 無關。而且把 的方向分量設成 1, 那一項會一起算出來:
import torch
from torch.func import jvp
def T(x, t): # 溫度場:x=300 附近有一塊暖區,整體每秒升溫 0.0005 °C
return 20 + 0.3*torch.exp(-((x-300)/100)**2) + 0.0005*t
x0, t0, xdot = torch.tensor(250.), torch.tensor(0.), torch.tensor(0.5)
reading, dg_dt = jvp(T, (x0, t0), (xdot, torch.tensor(1.))) # 全導數 ∂_t T + ∂_x T · ẋ,一次算完
_, dT_dt_fixed = jvp(T, (x0, t0), (torch.tensor(0.), torch.tensor(1.))) # 釘住位置:只剩 ∂_t T
print(f"讀數 {reading:.3f} 全導數 {dg_dt:.6f} 釘住 {dT_dt_fixed:.6f} 漂移項 {dg_dt-dT_dt_fixed:.6f}")
jvp(f, primals, tangents) 回傳 (f(primals), J·tangents):第一個 tangent 是 、第二個是 ,回傳的第二項就是全導數。上面這個例子會印出全導數 、釘住 、漂移項 (手算: °C/m,乘 得 )。把 x0 換成一個幾千維的向量、T 換成一個網路,程式一個字都不用改。
先消化一下
參考文獻
- Strang, G. Calculus. Wellesley-Cambridge Press;MIT OpenCourseWare RES.18-001 免費全文。(多變數 chain rule、微分與積分交換順序的章節。)
- Baydin, A. G., Pearlmutter, B. A., Radul, A. A., Siskind, J. M. Automatic Differentiation in Machine Learning: a Survey. JMLR 2018.(forward mode 與 JVP、reverse mode 與 VJP 的成本比較。)
- PyTorch documentation, torch.func.jvp.(本篇程式碼的 API。)