使用python實現深度神經網絡 3python
第一次實驗最後咱們說了,咱們已經學習了深度學習中的模型model
(神經網絡)、衡量模型性能的損失函數
和使損失函數減少的學習算法learn
(梯度降低算法),還了解了訓練數據data
的一些概念。可是尚未解決梯度降低算法
中如何求損失函數梯度的問題。網絡
本次實驗課,咱們就來學習一個可以快速計算梯度的算法--反向傳播算法(backpropogate algorithm)
,這個算法在神經網絡中很是重要,同時這個算法也很是巧妙,很是好玩。框架
咱們還會在本次實驗課中用代碼實現反向傳播算法。dom
第一次實驗我留的一個課後做業裏問你是否可以想出一個求解梯度的辦法,其實不難想到一種簡單的辦法就是使用「數值法」計算梯度。
辦法很簡單,就是對於損失函數中的一個初始取值爲a0
的參數a
,先計算當前的損失函數值J0
,再保持其餘參數不變,而使a
改變一個很小的量,好比變成a0+0.000001
,再求改變以後的損失函數值J1
。而後(J1-J0)/0.000001
就是J
對於a
的偏導的近似值。咱們對每個參數採用相似的方法求偏導,最後將偏導的值組成一個向量,即爲梯度向量。
這個辦法看上去很簡單,但卻沒法應用在實際的神經網絡當中。一方面的緣由是,咱們很難知道對參數的改變,有多小纔算足夠小,即咱們很難保證最後求出的梯度是準確的。
另外一方面的緣由是,這種方法計算量太大,如今的神經網絡中常常會有上億個參數,而這裏每求一個份量的偏導都要把全部參數值代入損失函數求兩次損失函數值,並且每一個份量都要執行這樣的計算。至關於每計算一次梯度須要2x1億x1億次計算,而梯度降低算法又要求咱們屢次(多是上萬次)計算梯度。這樣巨大的計算量即便是超級計算機也很難承受(世界第一的「神威·太湖之光」超級計算機峯值性能爲12.5億億次/秒,每秒也只能計算大概6次梯度)。函數
因此,咱們須要更加高效準確的算法來計算梯度,而反向傳播算法正好能知足咱們的需求。性能
其實若是你已經理解了鏈式法則,那麼能夠說,你幾乎已經學會反向傳播算法了。讓人感到很愉快對不對,好像什麼都還沒作,咱們就已經掌握了一個名字看起來有些嚇人的算法。
爲了幫助咱們真正理解反向傳播算法,咱們先來看一下什麼是「計算圖」,咱們以第一次實驗提到的sigmoid
函數爲例:學習
它的計算圖,是這樣的:優化
咱們將sigmoid函數視爲一個複合函數,並將其中的每個子函數都視爲一個節點,每一個節點按照複合函數實際的運算順序連接起來,最終獲得的F
其實就是sigmoid函數自己。spa
根據求導法則,咱們能夠求得每個節點對它直接子節點的導函數:
最重要的地方來了,再根據求導鏈式法則,咱們如今能夠輕易寫出圖中任意一個高層節點對其任意後代節點的導函數:只須要把鏈接它們的路徑上的全部部分導函數都乘起來就能夠了。
好比:
dF/dC=(dF/dE)*(dE/dC)=(-1/E^2)*1=-1/E^2
dF/dA=(dF/dE)*(dE/dC)*(dC/dB)*(dB/dA)=(-1/E^2)*(1)*(e^B)*(-1)=e^B/E^2
到這裏反向傳播算法已經呼之欲出了,對於一個具體的參數值,咱們只須要把每一個節點的值代入求得的導函數公式就能夠求得導數(偏導數),進而獲得梯度。
這很簡單,咱們先從計算圖的底部開始向上,逐個節點計算函數值並保存下來。這個步驟,叫作前向計算(forward)
。
而後,咱們從計算圖的頂部開始向下,逐步計算損失函數對每一個子節點的導函數,代入前向計算
過程當中獲得的節點值,獲得導數值。這個步驟,叫作反向傳播(backward)
或者更明確一點叫作反向梯度傳播
。
咱們來具體實踐一下,對於上圖中的sigmoid函數,計算x=0時的導數:
前向計算:
A=0, B=0, C=1, D=1, E=2, F=-1/4
反向傳播:
dF/dE=-1/E^2=-1/2^2=-1/4
dF/dC=dF/dE*dE/dC=-1/4
dF/dB=dF/dC*dC/dB=-1/4*e^B=-1/4*1=-1/4
dF/dA=dF/dB*dB/dA=-1/4*(-1)=1/4
以上就是反向傳播算法的所有內容。對於有1億個參數的損失函數,咱們只須要2*1億次計算就能夠求出梯度。複雜度大大下降,速度將大大加快。
sigmoid函數中沒有參數,在實際的神經網絡中,咱們都是將sigmoid函數視爲一個總體來對待,不必求它的內部節點的導函數。
sigmoid函數的導函數是什麼呢?你能夠本身求導試試,實際上sigmoid(x)'=sigmoid(x)*(1-sigmoid(x))
。
激動人心的時刻到了,咱們終於要開始用python代碼實現深度神經網絡的過程,這裏咱們打算對第一次實驗中的神經網絡示例圖中的「複合函數」編寫反向傳播算法。不過爲了按部就班,咱們考慮第一層(輸入層)只有兩個節點,第二層只有一個節點的狀況,即以下圖:
注意咱們將sigmoid函數圖像放在了b1節點後面,表明咱們這裏對b1運用sigmoid函數獲得了最終的輸出h1。
若是你對本身比較有信心,能夠不看接下來實現的代碼,本身動手試一試。
咱們能夠先把圖中包含的函數表達式寫出來,方便咱們以後寫代碼參考:b1=w11*a1+w12*a2+bias1
h1=sigmoid(b1)
h1=sigmoid(w11*a1+w12*a2+bias1)
如今咱們建立bp.py
文件,開始編寫代碼。先來編寫從第一層到第二層之間的代碼:
1 import numpy as np 2 3 4 5 class FullyConnect: 6 7 def __init__(self, l_x, l_y): # 兩個參數分別爲輸入層的長度和輸出層的長度 8 9 self.weights = np.random.randn(l_y, l_x) # 使用隨機數初始化參數 10 11 self.bias = np.random.randn(1) # 使用隨機數初始化參數 12 13 14 15 def forward(self, x): 16 17 self.x = x # 把中間結果保存下來,以備反向傳播時使用 18 19 self.y = np.dot(self.weights, x) + self.bias # 計算w11*a1+w12*a2+bias1
self.y = np.dot(self.weights, x.T) + self.bias # 計算w11*a1+w12*a2+bias1#上面一行貌似不對,應該用這行 21 return self.y # 將這一層計算的結果向前傳遞 22 23 24 25 def backward(self, d): 26 27 self.dw = d * self.x # 根據鏈式法則,將反向傳遞回來的導數值乘以x,獲得對參數的梯度 28 29 self.db = d 30 31 self.dx = d * self.weights 32 33 return self.dw, self.db # 返回求得的參數梯度,注意這裏若是要繼續反向傳遞梯度,應該返回self.dx
注意在神經網絡中,咱們將層與層之間的每一個點都有鏈接的層叫作全鏈接(fully connect)層
,因此咱們將這裏的類命名爲FullyConnect
。
上面的代碼很是清楚簡潔,咱們的全鏈接層完成了三個工做:
而後是第二層的輸入到最後的輸出之間的代碼,也就是咱們的sigmoid層:
1 class Sigmoid: 2 3 def __init__(self): # 無參數,不需初始化 4 5 pass 6 7 8 9 def sigmoid(self, x): 10 11 return 1 / (1 + np.exp(-x)) 12 13 14 15 def forward(self, x): 16 17 self.x = x 18 19 self.y = self.sigmoid(x) 20 21 return self.y 22 23 24 25 def backward(self): # 這裏sigmoid是最後一層,因此從這裏開始反向計算梯度 26 27 sig = self.sigmoid(self.x) 28 29 self.dx = sig * (1 - sig) 30 31 return self.dx # 反向傳遞梯度
因爲咱們要屢次使用sigmoid函數,因此咱們單獨的把sigmoid寫成了類的一個成員函數。
咱們這裏一樣完成了三個工做。只不過因爲Sigmoid層沒有參數,因此不須要進行參數初始化。同時因爲這裏須要反向傳播梯度,因此backward()函數必須返回self.dx
把上面的兩層拼起來,就完成了咱們的整體的網絡結構:
def main(): fc = FullyConnect(2, 1) sigmoid = Sigmoid() x = np.array([[1], [2]]) print 'weights:', fc.weights, ' bias:', fc.bias, ' input: ', x # 執行前向計算 y1 = fc.forward(x) y2 = sigmoid.forward(y1) print 'forward result: ', y2 # 執行反向傳播 d1 = sigmoid.backward() dx = fc.backward(d1) print 'backward result: ', dx if __name__ == '__main__': main()
請你自行運行上面的代碼,並修改輸入的x值。觀察輸出的中間值和最終結果,並手動驗證咱們計算的梯度是否正確。
若是你發現你不知道如何手動計算驗證結果,那說明你尚未理解反向傳播算法的原理,請回過頭去再仔細看一下以前的講解。
這裏給出完整代碼的下載連接,但我仍是但願你能儘可能本身嘗試寫出代碼,至少本身動手將上面的代碼從新敲一遍。這樣學習效果會好得多。
完整代碼文件下載:
wget http://labfile.oss.aliyuncs.com/courses/814/bp.py
上面的代碼將每一個網絡層寫在不一樣的類裏,而且類裏面的接口都是一致的(forward 和 backward),這樣作有不少好處,一是最大程度地下降了不一樣模塊之間的耦合程度,若是某一個層裏面的代碼須要修改,則只須要修改該層的代碼就夠了,不須要關心其餘層是怎麼實現的。另外一方面能夠徹底自由地組合不一樣的網絡層(咱們最後會介紹神經網絡裏其餘種類的網絡層)。
實際上,目前不少用於科研和工業生產的深度學習框架不少都是採用這種結構,你能夠找一個深度學習框架(好比caffe
)看看它的源碼,你會發現裏面就是這樣一個個寫好的網絡層。
本次實驗,咱們徹底地掌握了梯度降低算法中的關鍵--反向傳播算法。至此,神經網絡中最基本的東西你已經所有掌握了。你如今徹底能夠本身嘗試構建神經網絡並使用反向傳播算法優化網絡中的參數。
若是你把到此爲止講的東西差很少都弄懂了,那很是恭喜你,你應該爲本身感到驕傲。若是你暫時還有些東西沒有理解,不要氣餒,回過頭去仔細看看,到網上查查資料,若是實在沒法理解,問問咱們實驗樓的助教,我相信你最終也能理解。
本次實驗,咱們學習了: