1. 程式人生 > >神經網路反向傳播梯度計算數學原理

神經網路反向傳播梯度計算數學原理

[神經網路]反向傳播梯度計算數學原理

1 文章概述

本文通過一段來自於Pytorch官方的warm-up的例子:使用numpy來實現一個簡單的神經網路。使用基本的數學原理,對其計算過程進行理論推導,以揭示這幾句神奇的程式碼後面所包含的原理。

估計對大多數的同學來說,看完這個文章,肯定會是這樣的感覺:字都認識,但是就是不知道講的是啥~!不過對於有心人來說,本文確實能起到點睛之筆,就是你研究很久後,還差一點火候就頓悟了,希望本文能夠幫你頓悟。

關鍵字:Numpy,神經網路,矩陣分析,反射傳播,梯度下降

2 實現程式碼

numpy作為一個科學計算庫,並不包含:計算圖,嘗試學習,梯度等等功能,但是我們可以簡單的通過numpy去擬合一個二層的網路。

解決的問題:

  1. 隨機生成一組輸入資料,一組輸出資料。
  2. 定義一個神經網路結構及其引數
  3. 根據輸入資料正向傳播,求出誤差
  4. 根據誤差反向傳播梯度,更新神經元的各個節點的引數

程式碼如下:

# -*- 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行的反向傳播梯度,更新引數的程式碼,在外行人看來是比較神來之筆,完全摸不著頭腦,這是很正常的。因為要理解上述的程式碼,需要預先儲備如下知識(《矩陣分析與應用》):

  1. 矩陣的基本運算。頁面P4,章節編號1.1.2
  2. 矩陣的內積與範數。P32, 1.4.3
  3. 矩陣的跡。P49, 1.6.4
  4. 向量化和矩陣化。 P74, 1.11
  5. Jacobian矩陣和梯度矩陣。 P143, 3.1
  6. 一階實矩陣微分與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版