附錄D——自動微分(Autodiff)
本文介紹了五種微分方式,最後兩種才是自動微分。
前兩種方法求出了原函數對應的導函數,後三種方法只是求出了某一點的導數。
假設原函數是$f(x,y) = x^2y + y +2$,需要求其偏導數$\frac{\partial f}{\partial x}$和$\frac{\partial f}{\partial y}$,以便應用於梯度下降等算法。
1、手工求導
該方法比較簡單,就是自備紙筆,應用基本的求導規則,以及鏈式求導法則,人工求導。缺點是對於復雜函數容易出錯。幸運的是,這一計算過程可由計算機幫我們完成,這就是符號微分。
2、符號微分(Symbolic Differentiation)
如圖D-1所示,使用符號微分的方法,計算函數$g(x,y) = 5 + xy$的偏導數。該圖左側代表函數$g(x,y)$,右側代表$g(x,y)$關於$x$的偏導數$\frac{\partial g}{\partial x} = 0 + (0 \times x + y \times 1) = y$(同樣的,可以求得$\frac{\partial g}{\partial y}$)。
圖D-1 符號微分
該算法首先求葉子節點關於$x$的偏導數,然後沿著樹向上,求得其他節點關於自變量的偏導數。這與手工求導所使用的規則是一樣的。
如果函數復雜,該算法生成的樹將十分龐大,性能不高。而且無法對很隨意的代碼求導,例如:
def my_func(a, b): z = 0 for i in range(100): z = a * np.cos(z + i) + z * np.sin(b - i) return z
3、數值微分(Numerical Differentiation)
這是根據導數的定義來求解的。函數$h(x)$在$x_0$點的導數為:
$h‘(x) = \lim_{\varepsilon \rightarrow 0} \frac{h(x_0 + \varepsilon) - h(x_0)}{\varepsilon}$
我們取一個很小的$\varepsilon$,帶入公式進行計算即可。該方法所得結果不夠精確,參數過多時計算量也比較大。但是計算起來很簡單,可用於校驗手工算出的導數是否正確。
如果有1000個參數,至少需要調用$h(x)$1001詞,來求得所有偏導數。
4、前向自動微分(Forward-Mode Autodiff)
該算法依賴一個對偶數(dual numbers,這讓我想起來oracle的虛表。難度dual可以表示虛無的意思?) $\varepsilon$,滿足$\varepsilon^2 = 0$但是$\varepsilon \neq 0$(姑且理解為一階無窮小吧)。
由於$\varepsilon$是無窮小,因此滿足$h(a + b \varepsilon) = h(a) + b \times h‘(a)\varepsilon$。因此,算出$h(a + \varepsilon) $可以同時得到$h(a)$和$h‘(a)$,如圖D-2所示。
圖D-2 前向自動微分
上圖值計算了$\frac{\partial f}{\partial x}(3,4)$,同樣的方法可以算的$\frac{\partial f}{\partial y}(3,4)$。
如果有1000個參數,需要遍歷上圖1000次,來求得所有偏導數。
5、反向自動微分(Reverse-Mode Autodiff)
這是TensorFlow所采用的自動微分算法。如圖D-3所示,該算法首先前向(也就是從輸入到輸出)計算每個節點的值,然後反向(從輸出到輸入)計算所有的偏導數。
圖D-3 反向自動微分
反向計算時應用鏈式求導法則:
$\frac{\partial f}{\partial x} = \frac{\partial f}{\partial n_i} \times \frac{\partial n_i}{\partial x}$
由於$n_7$就是輸出節點,$f = n_7$,因此$\frac{\partial f}{\partial n_7} = 1$。
該算法強大且精確,尤其是輸入很多,輸出很少時。假如函數有10個輸出(不管輸入是1千,2萬還是更多),求得所有偏導數需要對上圖遍歷11次。
各個算法比較:
附錄D——自動微分(Autodiff)