[神經網絡]反向傳播梯度計算數學原理
1 文章概述
本文通過一段來自於Pytorch官方的warm-up的例子:使用numpy來實現一個簡單的神經網絡。使用基本的數學原理,對其計算過程進行理論推導,以揭示這幾句神奇的代碼后面所包含的原理。
估計對大多數的同學來說,看完這個文章,肯定會是這樣的感覺:字都認識,但是就是不知道講的是啥~!不過對於有心人來說,本文確實能起到點睛之筆,就是你研究很久后,還差一點火候就頓悟了,希望本文能夠幫你頓悟。
關鍵字:Numpy,神經網絡,矩陣分析,反射傳播,梯度下降
如果發現圖片裂了,請左轉至 其它平台查看:
https://zhuanlan.zhihu.com/p/32368246
2 實現代碼
numpy作為一個科學計算庫,並不包含:計算圖,嘗試學習,梯度等等功能,但是我們可以簡單的通過numpy去擬合一個二層的網絡。
解決的問題:
- 隨機生成一組輸入數據,一組輸出數據。
- 定義一個神經網絡結構及其參數
- 根據輸入數據正向傳播,求出誤差
- 根據誤差反向傳播梯度,更新神經元的各個節點的參數
代碼如下:
# -*- coding: utf-8 -*- import numpy as np # N is batch size; D_in is input dimension; # H is hidden dimension; D_out is output dimension. N, D_in, H, D_out = 64, 1000, 100, 10 # Create random input and output data x = np.random.randn(N, D_in) y = np.random.randn(N, D_out) # Randomly initialize weights w1 = np.random.randn(D_in, H) w2 = np.random.randn(H, D_out) learning_rate = 1e-6 for t in range(500): # Forward pass: compute predicted y h = x.dot(w1) h_relu = np.maximum(h, 0) y_pred = h_relu.dot(w2) # Compute and print loss loss = np.square(y_pred - y).sum() print(t, loss) # Backprop to compute gradients of w1 and w2 with respect to loss grad_y_pred = 2.0 * (y_pred - y) grad_w2 = h_relu.T.dot(grad_y_pred) grad_h_relu = grad_y_pred.dot(w2.T) grad_h = grad_h_relu.copy() grad_h[h < 0] = 0 grad_w1 = x.T.dot(grad_h) # Update weights w1 -= learning_rate * grad_w1 w2 -= learning_rate * grad_w2
原文見:Learning PyTorch with Examples
3 網絡結構
將上面的代碼結構及相應的參數維度繪圖后如下所示:
然后本代碼使用的是一個大小為64的batch,所以輸入的值實際的大小實際上是(64,1000)。
把以上的代碼轉化成數學公式如下,括號里面是相應的矩陣的形狀:
4 正向計算
數據流的正向傳播
最后計算出損失函數loss,是實際預測值和先驗數據矩陣的二范數,作為兩組矩陣的距離測度。
正向傳播比較簡單,基本上大學的線性代數的基本知識看幾章,就能很好的理解。這也是后續如果在深度學習框架下面設計網絡的時候,注意設計神經元大小的時候,需要考慮矩陣乘法的可行性,即維度相容。
PS:關於矩陣的范數的定義,詳情見P32的《1.4.3矩陣的內積和范數》
5 反向傳播
5.1 實現代碼
下面是反射傳播的代碼實現:
5.2 數學基礎
關於反射傳播的數學原理,可能就不是那么好理解了,因為這里面需要用到矩陣的高級算法,一般的理工科數學的《線性代數》甚至《高等代數》里面都沒有提到相關的內容,所以基本上已經超過了大多數高校學生的知識范圍了。在這個時候,就要祭出張賢達的《矩陣分析》了。
最開始我把自己大學時候的數學書《數學分析》,《高等代數》,《數值計算》都翻了一遍,但是都沒有找到相關的內容。感覺對於矩陣的微分是一個“三不管”的地帶,但是這個內容又是深度學習神經網絡中用得最多的數學原理。然后到網上發現了《矩陣分析與應用》,想想這么厚的一本像百科全書的書,應該是無所不包吧,果然在里面找到了想要的內容。
當然在看書之前,也看了無數的網絡文章,相對比較有價值的就下面兩篇:
《矩陣求導-上》https://zhuanlan.zhihu.com/p/24709748
《矩陣求導-下》https://zhuanlan.zhihu.com/p/24863977
當然,像數學工具這種內容,建議大家還是去看書,因為書作為幾十年的經典教材,其推導過程,內容的完整性,認證的嚴密性都是經得起推敲的。網絡文章只能幫大家啟蒙一下,學幾個術語,但是具體想深入了解細節,建議還是看書。
言歸正傳。
上述的不到10行的反向傳播梯度,更新參數的代碼,在外行人看來是比較神來之筆,完全摸不着頭腦,這是很正常的。因為要理解上述的代碼,需要預先儲備如下知識(《矩陣分析與應用》):
- 矩陣的基本運算。頁面P4,章節編號1.1.2
- 矩陣的內積與范數。P32, 1.4.3
- 矩陣的跡。P49, 1.6.4
- 向量化和矩陣化。 P74, 1.11
- Jacobian矩陣和梯度矩陣。 P143, 3.1
- 一階實矩陣微分與Jacobian矩陣辨識。 P152, 3.2
注意事項:函數有不同的分類,所以請大家不要全用《線性代數》里面變元全為實數標量的眼光來看待矩陣的變元和矩陣函數的運算。因為它們是不同的,即使你勉強得到符合代碼的結論,那很可能也是“瞎貓碰到死耗子”。關於函數的微分的討論,光實值函數的分類,就可以分如下幾類(P143, 3.1):
矩陣和Jacobian矩陣在實值區間內是互為轉置。在進行數學推導時,都是先根據Jacobian矩陣的辨識方法求出Jacobian矩陣,然后轉置后就是相應的梯度。
當定義一個標量函數關於變量的偏導數時:
Jacobian矩陣和梯度矩陣是關於偏導的不同定義方式,分別是行向量偏導和列向量偏導。只是Jacobian矩陣是一種研究思維上更自然的選擇,但是梯度向量卻是優化和實際工程計算時更自然的選擇。
5.3 預測值梯度
grad_y_pred = 2.0 * (y_pred - y)
下面是推導過程,紅色筆跡是推導過程的依據,請查閱《矩陣分析與應用》
接着前面的公式,繼續求微分:
5.4 參數W2梯度
grad_w2 = h_relu.T.dot(grad_y_pred)
5.5 參數H_relu 梯度
grad_h_relu = grad_y_pred.dot(w2.T)
5.6 Relu梯度
grad_h = grad_h_relu.copy() grad_h[h < 0] = 0 grad_w1 = x.T.dot(grad_h)
5.7 參數W1梯度
然后后面就是使用梯度和學習率去批量更新參數,實現整個訓練過程了。
6 參考資料
《矩陣分析與應用》(第2版) 張賢達 著,清華大學出版社,2011-11,第2版
本文中所有的引用注解,頁面標識都來自於本書。