神經網路中反向傳播演算法(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),引入啟用函式之後很容易驗證,無論你神經網路有多少層,輸出都是輸入的線性組合,與沒有
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工具。官網教程地址: