雖然學深度學習有一段時間了,但是對於一些算法的具體實現還是模糊不清,用了很久也不是很了解。因此特意先對深度學習中的相關基礎概念做一下總結。先看看前向傳播算法(Forward propagation)與反向傳播算法(Back propagation)。
1.前向傳播
如圖所示,這里講得已經很清楚了,前向傳播的思想比較簡單。
舉個例子,假設上一層結點i,j,k,…等一些結點與本層的結點w有連接,那么結點w的值怎么算呢?就是通過上一層的i,j,k等結點以及對應的連接權值進行加權和運算,最終結果再加上一個偏置項(圖中為了簡單省略了),最后在通過一個非線性函數(即激活函數),如ReLu,sigmoid等函數,最后得到的結果就是本層結點w的輸出。
最終不斷的通過這種方法一層層的運算,得到輸出層結果。
對於前向傳播來說,不管維度多高,其過程都可以用如下公式表示:
a2=σ(z2)=σ(a1∗W2+b2)a2=σ(z2)=σ(a1∗W2+b2)
其中,上標代表層數,星號表示卷積,b表示偏置項bias,σ表示激活函數。
2.反向傳播算法(Back propagation)
BackPropagation算法是多層神經網絡的訓練中舉足輕重的算法。簡單的理解,它的確就是復合函數求偏導的鏈式法則,但其在實際運算中的意義比鏈式法則要大的多。要回答題主這個問題“如何直觀的解釋back propagation算法?” 需要先直觀理解多層神經網絡的訓練。
機器學習可以看做是數理統計的一個應用,在數理統計中一個常見的任務就是擬合,也就是給定一些樣本點,用合適的曲線揭示這些樣本點隨着自變量的變化關系.
深度學習同樣也是為了這個目的,只不過此時,樣本點不再限定為(x, y)點對,而可以是由向量、矩陣等等組成的廣義點對(X,Y)。而此時,(X,Y)之間的關系也變得十分復雜,不太可能用一個簡單函數表示。然而,人們發現可以用多層神經網絡來表示這樣的關系,而多層神經網絡的本質就是一個多層復合的函數。下圖來直觀描繪一下這種復合關系。
其對應的表達式如下:
上面式中的Wij就是相鄰兩層神經元之間的權值,它們就是深度學習需要學習的參數,也就相當於直線擬合y=k*x+b中的待求參數k和b。
和直線擬合一樣,深度學習的訓練也有一個目標函數,這個目標函數定義了什么樣的參數才算一組“好參數”,不過在機器學習中,一般是采用成本函數(cost function),然后,訓練目標就是通過調整每一個權值Wij來使得cost達到最小。cost函數也可以看成是由所有待求權值Wij為自變量的復合函數,而且基本上是非凸的,即含有許多局部最小值。但實際中發現,采用我們常用的梯度下降法就可以有效的求解最小化cost函數的問題。
梯度下降法需要給定一個初始點,並求出該點的梯度向量,然后以負梯度方向為搜索方向,以一定的步長進行搜索,從而確定下一個迭代點,再計算該新的梯度方向,如此重復直到cost收斂。那么如何計算梯度呢?
假設我們把cost函數表示為H(W11,W12,⋯,Wij,⋯,Wmn)H(W11,W12,⋯,Wij,⋯,Wmn),那么它的梯度向量[2]就等於∇H=∂H∂W11e11+⋯+∂H∂Wmnemn∇H=∂H∂W11e11+⋯+∂H∂Wmnemn, 其中eijeij表示正交單位向量。為此,我們需求出cost函數H對每一個權值Wij的偏導數。而BP算法正是用來求解這種多層復合函數的所有變量的偏導數的利器。
我們以求e=(a+b)*(b+1)的偏導[3]為例。
它的復合關系畫出圖可以表示如下:
在圖中,引入了中間變量c,d。
為了求出a=2, b=1時,e的梯度,我們可以先利用偏導數的定義求出不同層之間相鄰節點的偏導關系,如下圖所示。
利用鏈式法則我們知道:
∂e∂a=∂e∂c⋅∂c∂a以及∂e∂b=∂e∂c⋅∂c∂b+∂e∂d⋅∂d∂b∂e∂a=∂e∂c⋅∂c∂a以及∂e∂b=∂e∂c⋅∂c∂b+∂e∂d⋅∂d∂b
鏈式法則在上圖中的意義是什么呢?其實不難發現,∂e∂a∂e∂a的值等於從a到e的路徑上的偏導值的乘積,而∂e∂b∂e∂b的值等於從b到e的路徑1(b-c-e)上的偏導值的乘積加上路徑2(b-d-e)上的偏導值的乘積。也就是說,對於上層節點p和下層節點q,要求得∂p∂q∂p∂q,需要找到從q節點到p節點的所有路徑,並且對每條路徑,求得該路徑上的所有偏導數之乘積,然后將所有路徑的 “乘積” 累加起來才能得到∂p∂q∂p∂q的值。
大家也許已經注意到,這樣做是十分冗余的,因為很多路徑被重復訪問了。比如上圖中,a-c-e和b-c-e就都走了路徑c-e。對於權值動則數萬的深度模型中的神經網絡,這樣的冗余所導致的計算量是相當大的。
同樣是利用鏈式法則,BP算法則機智地避開了這種冗余,它對於每一個路徑只訪問一次就能求頂點對所有下層節點的偏導值。
正如反向傳播(BP)算法的名字說的那樣,BP算法是反向(自上往下)來尋找路徑的。
從最上層的節點e開始,初始值為1,以層為單位進行處理。對於e的下一層的所有子節點,將1乘以e到某個節點路徑上的偏導值,並將結果“堆放”在該子節點中。等e所在的層按照這樣傳播完畢后,第二層的每一個節點都“堆放”些值,然后我們針對每個節點,把它里面所有“堆放”的值求和,就得到了頂點e對該節點的偏導。然后將這些第二層的節點各自作為起始頂點,初始值設為頂點e對它們的偏導值,以”層”為單位重復上述傳播過程,即可求出頂點e對每一層節點的偏導數。
以上圖為例,節點c接受e發送的1*2並堆放起來,節點d接受e發送的1*3並堆放起來,至此第二層完畢,求出各節點總堆放量並繼續向下一層發送。節點c向a發送2*1並對堆放起來,節點c向b發送2*1並堆放起來,節點d向b發送3*1並堆放起來,至此第三層完畢,節點a堆放起來的量為2,節點b堆放起來的量為2*1+3*1=5, 即頂點e對b的偏導數為5.
舉個不太恰當的例子,如果把上圖中的箭頭表示欠錢的關系,即c→e表示e欠c的錢。以a, b為例,直接計算e對它們倆的偏導相當於a, b各自去討薪。a向c討薪,c說e欠我錢,你向他要。於是a又跨過c去找e。b先向c討薪,同樣又轉向e,b又向d討薪,再次轉向e。可以看到,追款之路,充滿艱辛,而且還有重復,即a, b 都從c轉向e。
而BP算法就是主動還款。e把所欠之錢還給c,d。c,d收到錢,樂呵地把錢轉發給了a,b,皆大歡喜。
3.反向傳播具體計算過程推導
為了方便起見,這里我定義了三層網絡,輸入層(第0層),隱藏層(第1層),輸出層(第二層)。並且每個結點沒有偏置(有偏置原理完全一樣),激活函數為sigmod函數(不同的激活函數,求導不同),符號說明如下:
對應網絡如下:
其中對應的矩陣表示如下
首先我們先走一遍正向傳播,公式與相應的數據對應如下:
那么:
同理可以得到:
那么最終的損失為
我們當然是希望這個值越小越好。這也是我們為什么要進行訓練,調節參數,使得最終的損失最小。這就用到了我們的反向傳播算法,實際上反向傳播就是梯度下降法中鏈式法則的使用。
下面我們看如何反向傳播
根據公式,我們有:
這個時候我們需要求出C對w的偏導,則根據鏈式法則有:
同理有:
到此我們已經算出了最后一層的參數偏導了.我們繼續往前面鏈式推導:
我們現在還需要求
下面給出一個推導其它全都類似
同理可得其它幾個式子:
則最終的結果為:
再按照這個權重參數進行一遍正向傳播得出來的Error為0.165
而這個值比原來的0.19要小,則繼續迭代,不斷修正權值,使得代價函數越來越小,預測值不斷逼近0.5.我迭代了100次的結果,Error為5.92944818e-07(已經很小了,說明預測值與真實值非常接近了),最后的權值為:
bp過程可能差不多就是這樣了,可能此文需要你以前接觸過bp算法,只是還有疑惑,一步步推導后,會有較深的理解。
上面的python代碼實現如下:
#!/usr/bin/env python
#coding:utf-8
import numpy as np
def nonlin(x, deriv = False):
if(deriv == True):
return x * (1 - x)
return 1 / (1 + np.exp(-x))
X = np.array([[0.35], [0.9]])
y = np.array([[0.5]])
np.random.seed(1)
W0 = np.array([[0.1, 0.8], [0.4, 0.6]])
W1 = np.array([[0.3, 0.9]])
print 'original ', W0, '\n', W1
for j in xrange(100):
l0 = X
l1 = nonlin(np.dot(W0, l0))
l2 = nonlin(np.dot(W1, l1))
l2_error = y - l2
Error = 1 / 2.0 * (y-l2)**2
print 'Error:', Error
l2_delta = l2_error * nonlin(l2, deriv=True)
l1_error = l2_delta * W1 #back propagation
l1_delta = l1_error * nonlin(l1, deriv=True)
W1 += l2_delta * l1.T
W0 += l0.T.dot(l1_delta)
print W0, '\n', W1
參考文檔: