1. 程式人生 > >神經網路中反向傳播演算法(backpropagation)的pytorch實現,pytorch教程中的程式碼解讀以及其他一些疑問與解答

神經網路中反向傳播演算法(backpropagation)的pytorch實現,pytorch教程中的程式碼解讀以及其他一些疑問與解答

pytorch的官網上有一段教程,是使用python的numpy工具實現一個簡單的神經網路的bp演算法。下面先貼上自己的程式碼:

import numpy as np

N,D_in,H,D_out = 4,10,8,5

x = np.random.randn(N,D_in)#4x10
y = np.random.randn(N,D_out)#4x5
#print(x)
#print(y)

w1 = np.random.randn(D_in,H)#10x8
w2 = np.random.randn(H,D_out)#8x5

lr = 1e-6

h = x.dot(w1)#h=x*w1,4x8
h_relu = np.maximum(h,0)#h_relu=relu(h),4x8
y_pred = h_relu.dot(w2)#y_pred=h_relu*w2,4x5
print(h)

loss = np.square(y_pred - y).sum()#loss=sum((y_pred-y)^2)
print("loss: %f" %loss)

#grad_y_pred=d(loss)/d(y_pred),4x5
grad_y_pred = 2.0*(y_pred - y)
#grad_w2=d(loss)/d(w2)=grad_y_pred * d(y_pred)/d(w2),8x4 * 4x5=>8x5
grad_w2 = h_relu.T.dot(grad_y_pred)

#grad_h_relu=d(loss)/d(h_relu)=grad_y_pred * d(y_pred)/d(h_relu),4x5 * 5x8
grad_h_relu = grad_y_pred.dot(w2.T)
#grad_h=(relu())'
grad_h = grad_h_relu.copy()
grad_h[h<0] = 0
#grad_w1=d(loss)/d(w1)
#       =d(loss)/d(y_pred) * d(y_pred)/d(h_relu) * d(h_relu)/d(h) * d(h)/d(w1)
#       =grad_y_pred * w2 * (h<0 ? 0 : 1) * x
grad_w1 = x.T.dot(grad_h)#10x4 * 4x5 * 5x8

w1 -= lr*grad_w1
w2 -= lr*grad_w2

首先宣告此程式碼中沒有進行迴圈迭代的重複操作,只是進行了一次前向傳播與反向傳播。

需要注意的知識點有:

1.反向傳播演算法中如何更新權值w1 w2。

在這個過程中最重要的是求取權值的變化對損失函式的影響,而這個影響一般用梯度值來表示。因此這個問題變成了求損失函式相對於權值的梯度。得到了梯度grad_w之後,根據bp演算法更新權值的方式:w=w-lr*grad_w,其中lr是學習率,就可以更新權值w1,w2了。更新完了權值w1,w2之後,完成了一次整個的運算過程,將這整個的過程迴圈迭代多次,即可逐漸最小化損失函式loss的值。

求取grad_w的方式用到了求導的鏈式法則,具體公式在上述程式碼的註釋中,程式碼實現的過程中,需要注意矩陣的轉置是為了保證運算結果的矩陣維度正確。

還需注意的是,因為上述程式碼中實際上是包含了一個隱含層的全連線的神經網路,因此權值w1,w2矩陣中的值,會影響多個輸出層的y的值,同樣地,每一個輸出的y值也會影響與之相連的每一個權值w的梯度,因此在實際求偏導進而求梯度的時候,對於某個w值,所求取的應當是所有y相對於這個w的偏導之和,這也就對應了程式碼中進行運算時,是對兩個矩陣進行乘法運算(而不是矩陣中對應元素的相乘這麼簡單)。

此外,對於每個層的啟用函式,在求梯度的過程中也要記得求其導數。

2.神經網路中為什麼要用到啟用函式

一般啟用函式都是非線性的,如果不用啟用函式,網路中的每一層的輸出相對於輸入都是線性的(相當於y=x),引入啟用函式之後很容易驗證,無論你神經網路有多少層,輸出都是輸入的線性組合,與沒有

隱藏層效果相當,這種情況就是最原始的感知機(Perceptron)了。這樣深度神經網路的“深度”就沒有了意義。具體到某些分類問題,對於非線性可分的樣本則無法進行分類,而採用了非線性的啟用函式之後,才能實現非線性問題的分類。

3.sigmoid函式的缺點

根據該函式的影象可知,sigmoid函式容易飽和,當輸入非常大或者非常小的時候,函式的梯度就接近於0了,很容易就會出現梯度消失的情況。從圖中可以看出梯度的趨勢。這就使得我們在反向傳播演算法中反向傳播接近於0的梯度,導致最終權重基本沒什麼更新。

4.ReLU函式相對於sigmoid函式的優點

(1)使用 ReLU 得到的SGD的收斂速度會比 sigmoid/tanh 快很多(如上圖右)。有人說這是因為它是linear,而且梯度不會飽和。為什麼線性不飽和,就會收斂的快?反向傳播演算法中,下降梯度等於敏感度乘以前一層的輸出值,所以前一層輸出越大,下降的梯度越多。該優點解決了sigmod的梯度消失問題。

(2)sigmoid/tanh需要計算指數等,計算複雜度高,求梯度時涉及到除法運算。ReLU只需要一個閾值就可以得到啟用值。

(3)ReLU會使一部分神經元的輸出為0,這樣就造成了網路的稀疏性,並且減少了引數的相互依存關係,緩解了過擬合問題的發生。

與此同時,該函式的缺點為:

 ReLU在訓練的時候很”脆弱”,一不小心有可能導致神經元”壞死”。舉個例子:由於ReLU在x<0時梯度為0,這樣就導致負的梯度在這個ReLU被置零,而且這個神經元有可能再也不會被任何資料啟用。如果這個情況發生了,那麼這個神經元之後的梯度就永遠是0了,也就是ReLU神經元壞死了,不再對任何資料有所響應。實際操作中,如果你的learning rate 很大,那麼很有可能你網路中的40%的神經元都壞死了。 當然,如果你設定了一個合適的較小的learning rate,這個問題發生的情況其實也不會太頻繁。  

結語:

實際上此程式碼並未使用pytorch工具。官網教程地址: