Maximize Likelihood Estimation (MLE) 是一種最佳化手段使得機器模型的輸出越接近真實。這個做法的核心之一就是梯度。今天讓我們花點時間暸解模型內的參數對損失函數的微分是怎麼算的。首先從單層神經網路模型開始,接著推廣到多層的深度神經網路 (Deep Neural Network, a.k.a. DNN) 。
模型的最小單位:神經元
模型的功能是把某一群內的數據轉換成另一群。模型通常是由許多神經元組成的、每一個神經元所做的運算如圖一所示:輸入為$x$、乘上權重$w$、加上偏差$b$後就是輸出$z$。廣義上,每一個神經元也都是可以是一種轉換。假設有一個模型只具備一個神經元,其方程式可以寫作式一:
$T: x \rightarrow z=w \cdot x + b$ (1)
where $T$ is a neuron, $z$ is output, $x$ is input, $w$ is weight and $b$ is bias
每一個神經網路模型裡頭都包含著成千上萬的神經元,每一個神經元的功能都如同圖一。為了避免畫面混亂,我簡化圖一中的神經元如圖二。
反向誤差傳遞
接著讓我們看看要怎麼計算損失函數對參數的微分。損失函數是指模型的預測與現實數據之間的偏差值。反向誤差傳遞的意思是使用連鎖律 (chain rule) 計算參數對損失函數的梯度的線性項,同時意指它忽略參數之間在二次項以上的互相影響。
具體的說,忽略二次項的梯度只有在計算時的參數空間附近成立,稍微走遠一點就不準了。想像把每一個參數都當作空間中的一個維度,每一個維度的微分其實都會受到其他維度的影響。某一個參數改變了,模型位於參數空間的座標與相對應的微分也會隨著改變。因此,如果想在更新參數時忽略梯度的非線性項 (二次項以上的項) ,則更新幅度$d\theta$必須非常非常小。數學推導過程可以看這一篇,本篇便不再贅述。
單層神經網路模型的反向誤差傳遞
舉個實際的例子會比較容易懂,讓我們考慮一個只有兩個輸入與兩個神經元的簡單機器模型吧。如下圖三:
我們的目的是找到最好的參數組合$\theta = \{w_1, w_2, w_3, w_4, b_1, b_2\}$,使得模型的輸出$y$越接近正解$y_{true}$越好。由於我們計算的是梯度的線性項,所以理論上他們是可以同時被計算、同時被更新且不會互相影響的。圖三的神經網路可以寫成式二:
$L = \Sigma_i \quad loss(y_i)$ (2)
where $y_i = Activation(w_{i,j} x_j + b_i) $
這邊的$loss$與$Activation$指的是損失函數 (loss function) 與活化函數 (activation function)。儘管他們有很多種選擇與可能,這裡我們分別使用KL divergence與Softmax,這也是最常見的組合。凡事總需要有個開頭,那就從$w_1$開始算吧!根據式一,$\frac{dL}{dw_1}$可以寫作式三:
$\frac{dL}{dw_1}
= \frac{dz_1}{dw_1}\cdot\frac{dL}{dz_1}
+ \frac{dz_2}{dw_1}\cdot\frac{dL}{dz_2}
= \frac{dz_1}{dw_1}\cdot\frac{dL}{dz_1} $ (3)
因為$z_2$與$w_1$無關,所以我們忽略掉第二大項$ \frac{dz_2}{dw_1}\cdot\frac{dL}{dz_2} $。我們接著用連鎖律把 $\frac{dL}{dw_1} $拆分為 $ \frac{dz_1}{dw_1} $與 $\frac{dL}{dz_1}$。後者在 「更新模型參數與Adam」 的式八算過,我把結果引用過來並寫作式四。讓我們繼續用連鎖律計算前者。根據神經網路的結構,每一個神經元都會接受一個輸入、乘上一個數、加上另一個數、接著輸出,因此$z_1$可以寫作式五:
$\frac{dL}{dz_1} = p_1-q_1$ (4)
$z_1 = w_1 \cdot x_1 + w_2 \cdot x_2 +b_1$ (5)
計算式五的梯度就很簡單了、寫作式六。同時我們也發現該輸入$x_1$恰恰是該神經元$z_1$對參數$w_1$的微分。我們把式三的兩個連鎖律產生的項總結一下,就可以得到損失函數對參數的微分、寫作式七:
$\frac{dz_1}{dw_1} = x_1$ (6)
$\frac{dL}{dw_1} = x_1\cdot (p_1 – q_1)$ (7)
偏差$b$的反向誤差傳遞
我們已經算出損失函數對$w_1$的微分是多少,由於權重$w_1$是隨意選的,所以這個規則在每一個神經元都成立。$w_1$換成$w_2$, $w_3$, $w_4$都可以用一樣的方法算出來。如果換成$b_1$或$b_2$則稍稍有點不同。拿$b_1$舉例,損失函數$L$對$b_1$的微分如下式八:
$\frac{dL}{db_1} = \frac{dz_1}{db_1} \cdot \frac{dL}{dz_1}$ (8)
根據式五, $\frac{dz_1}{db_1}$ 的值是1;$\frac{dL}{dz_1}$ 我們之前算過了,是$p_1 – q_1$ 。所以式八可以改寫為式九。式九相比於式七,除了少乘了$x_1$,其他都一樣,也是很容易計算的。
$\frac{dL}{db_1} = p_1 – q_1 $ (9)
暸解了如何用反向誤差傳遞計算梯度後,讓我們把它應用到比較複雜的神經網路中吧!秉持的一次學一點點的原則,我們先在數學型態上最簡單的深度神經網路 (Deep Neural Network, a.k.a. DNN) 試試看吧。不過,我們至今仍不太清楚DNN是什麼?
深度神經網路的反向誤差傳遞
深度神經網路是一個大型二維矩陣,裡面的每一個元素都是由一個神經元組成。令該層的輸入為$x_i$、輸出為$z_j$,這一層深度神經網路的工作流 (workflow) 可以寫作下式七:
$z_j = w_{j, i} \cdot x_i + b_j$ (10)
單層深度神經網路的梯度的計算方式就如同上面做過的計算,所以這次我們直接來試看看兩層深度神經網路。我們令$z^1_j$為第一層神經網路的輸出,$z^2_k$為第二層神經網路的輸出。損失函數對第二層神經網路的參數的梯度可以參考式七與式九,算法是一樣的。所以我們討論損失函數對第一層神經網路的參數的梯度就好。雙層深度神經網路可以寫成式十一;其中$dL$對$dw^1_{j,i}$ 的微分可以寫作式十二:
$1_{st}$ layer: $z^1_j = w^1_{j,i}\cdot x_i + b^1_j $
$2_{nd}$ layer: $z^2_k = w^2_{k,j} \cdot z^1_j + b^2_k $ (11)
$\frac{dL}{dw^1_{j,i}} = \frac{dL}{dz^2_k} \cdot \frac{dz^2_k}{dw^1_{j,i}} $ (12)
要注意到參數的上標指的是該參數來自第幾層,而不是幾次方。式十二右邊第一項我們已經在式四介紹過了,這次就也不多做介紹;根據式十、式十二右邊第二項可以拆解成式十三:
$\frac{dz^2_k}{dw^1_{j,i}}
= \frac{dz^2_k}{dz^1_j} \cdot \frac{dz^1_j}{dw^1_{j,i}}
= w^2_{k,j} \cdot x_i $ (13)
綜合式十二、式十三可以寫作式十四:
$\frac{dL}{dw^1_{j,i}} = \frac{dL}{dz^2_k} \cdot w^2_{k,j} \cdot x_i
= (p_i – q_i) \cdot w^2_{k,j} \cdot x_i $ (14)
反向誤差傳遞在計算上的規律
讀者發現規律了嗎?第一、$p_i – q_i$來自損失函數與最後一層的活化函數;第二、參數所在的神經元的輸出$z$對參數$w$的微分等於該神經元的輸入;第三、如果該神經元與損失函數之間有其他神經元,那麼還要乘上這些神經元的輸出之間的微分。讓我們練習算看看第一層神經網路的參數在三層的深度神經網路的梯度吧。令三層深度神經網路寫作式十五;損失函數對第一層神經網路的參數們的梯度寫作式十六:
$1_{st}$ layer: $z^1_j = w^1_{j,i}\cdot x_i + b^1_j $
$2_{nd}$ layer: $z^2_k = w^2_{k,j} \cdot z^1_j + b^2_k $
$3_{nd}$ layer: $z^3_l = w^3_{l,k} \cdot z^2_k + b^3_l $ (15)
$\frac{dL}{dw^1_{j,i}}
= \frac{dL}{dz^3_l} \cdot \frac{dz^3_l}{dz^2_k} \cdot \frac{dz^2_k}{dz^1_j} \cdot \frac{dz^1_j}{dw^1_{j,i}}
= (p_i – q_i) \cdot w^3_{l,k} \cdot w^2_{k,j} \cdot x_i $ (16)
活化函數
上述提過的雙層神經網路依舊是簡化過的。現實中為了增加非線性的程度,通常還會在每一層神經網路之間再加上活化函數。活化函數改變的是一個神經元的輸出,並做為另一個神經元的輸入。常見的有ReLU, Tanh, Sigmoid, 與Softmax,未來有空會介紹介紹他們。這裡讀者可以先記得兩件事,神經網路之間的活化函數放ReLU通常會比較好;在最後一層神經網路之後放Softmax的話,會使得模型的輸出符合機率的定義。
平行化梯度運算
由於線性梯度之間不會互相影響,所以我們可以利用平行運算減少總運算時間。令最靠近輸入的那一層我們稱之為第一層,最接近輸出的稱之為最後一層。位於層數越靠前的參數的梯度越難算,因為其連鎖律產生的項會比較多。同一層的神經元們經由連鎖律後產生的項會有一大部分重複。重疊的項越多代表效率越差,因為同時計算的程序們無法共享資訊。避免重複計算,電腦會從最後一層的參數的梯度算回去第一層。
怎麼算比較快?
順著算與反著算梯度,會有多少性能上的差異呢?我們拿式十一的雙層深度神經網路做示範。如果我先算第一層的權重與偏差的梯度,我需要計算$\frac{dL}{dz^2_k}$, $\frac{dz^2_k}{dz^1_j}$, $\frac{dz^1_j}{dw^1_{j,i}}$。因為第一層的參數們的梯度是同時被計算的,所以$\frac{dL}{dz^2_k}$會在該層被重複算過$j$次。在第一層我們一共需要進行$2ij(2k + 1)$ 次微分計算。然後是第二層,該層的梯度需要$\frac{dL}{dz^2_k}$及$\frac{dz^2_k}{dw^2_{k,j}}$,因為$\frac{dL}{dz^2_k}$剛才我們都算過了,只要代入就好,所以微分計算量是$2kj$ 次。整體微分計算量為 $2ij(2k + 1)+2kj$ 次。
如果我們從最後一層開始算的話。第二層的微分計算量是$4kj$。因為第二層的梯度已經算好了,所以在計算第一層梯度的時候可以少算$\frac{dL}{dz^2_k}$。於是第一層微分計算量是$2ij(k+1)$。整體微分計算量是 $2ij(k+1)+4kj$ 。當 $i>1$ 時,從最後一層開始算的微分計算量會小於從第一層開始算。又因為 $i$ 在深度學習模型設計上必大於一,所以後者肯定是比較便宜的。同樣的結果可以推廣到更多層的深度神經網路模型。
其他的模型呢?
其他的神經網路也可以用同樣的邏輯來計算梯度,不同的是其網路與網路之間的關係跟DNN不同。例如CNN一次只會跟相鄰的輸入相乘,可以把CNN的反向誤差傳遞當作是DNN的距離限制版;RNN的輸入具有時序性,意思是產生輸出需要照順序放入多個輸入。可以把單層RNN內部的反向誤差傳遞當作是DNN的時間維度版(實際還要再更複雜)。
小結
本篇介紹了如何利用反向誤差傳遞、也就是連鎖律、來計算梯度。我們從神經元的結構學到了深度神經網路的微分的規則,並且從平行運算的角度討論怎麼算微分比較快。本篇內容有相當大的程度是參考自李宏毅老師的影片。十分推薦讀者去李宏毅老師的Youtube頻道,我自己從那裡學到了很多。既然已經有人講過這個主題,本篇寫作的目的比較多是落實費曼學習法。
參考
ML Lecture7: Backpropagation, Hung-yi Lee
所有文章分類