深度學習與計算機視覺系列(5)_反向傳播與它的直觀理解

作者:寒小陽 && 龍心塵
時間:2015年12月。
出處:
http://blog.csdn.net/han_xiaoyang/article/details/50321873
http://blog.csdn.net/longxinchen_ml/article/details/50323183
聲明:版權所有,轉載請聯繫作者並註明出處

1. 引言

其實一開始要講這部分內容,我是拒絕的,原因是我覺得有一種寫高數課總結的感覺。而一般直觀上理解反向傳播算法就是求導的一個鏈式法則而已。但是偏偏理解這部分和其中的細節對於神經網絡的設計和調整優化又是有用的,所以硬着頭皮寫寫吧。

問題描述與動機:

  • 大家都知道的,其實我們就是在給定的圖像像素向量x和對應的函數f(x) ,然後我們希望能夠計算fx 上的梯度(f(x) )

  • 我們之所以想解決這個問題,是因爲在神經網絡中,f 對應損失函數L ,而輸入x 則對應訓練樣本數據和神經網絡的權重W 。舉一個特例,損失函數可以是SVM loss function,而輸入則對應樣本數據(xi,yi),i=1N 和權重以及bias W,b 。需要注意的一點是,在我們的場景下,通常我們認爲訓練數據是給定的,而權重是我們可以控制的變量。因此我們爲了更新權重的等參數,使得損失函數值最小,我們通常是計算f 對參數W,b 的梯度。不過我們計算其在xi 上的梯度有時候也是有用的,比如如果我們想做可視化以及瞭解神經網絡在『做什麼』的時候。

2.高數梯度/偏導基礎

好了,現在開始複習高數課了,從最簡單的例子開始,假如f(x,y)=xy ,那我們可以求這個函數對xy 的偏導,如下:

f(x,y)=xyfx=yfy=x

2.1 解釋

我們知道偏導數實際表示的含義:一個函數在給定變量所在維度,當前點附近的一個變化率。也就是:

df(x)dx=limh 0f(x+h)f(x)h

以上公式中的ddx 作用在f 上,表示對x求偏導數,表示的是x維度上當前點位置周邊很小區域的變化率。舉個例子,如果x=4,y=3 ,而f(x,y)=12 ,那麼x上的偏導fx=3 ,這告訴我們如果這個變量(x)增大一個很小的量,那麼整個表達式會以3倍這個量減小。我們把上面的公式變變形,可以這麼看:f(x+h)=f(x)+hdf(x)dx 。同理,因爲fy=4 ,我們將y的值增加一個很小的量h,則整個表達式變化4h。

每個維度/變量上的偏導,表示整個函數表達式,在這個值上的『敏感度』

哦,對,我們說的梯度f 其實是一個偏導組成的向量,比如我們有f=[fx,fy]=[y,x] 。即使嚴格意義上來說梯度是一個向量,但是大多數情況下,我們還是習慣直呼『x上的梯度』,而不是『x上的偏導』

大家都知道加法操作上的偏導數是這樣的:

f(x,y)=x+yfx=1fy=1

而對於一些別的操作,比如max函數,偏導數是這樣的(後面的括號表示在這個條件下):

f(x,y)=max(x,y)fx=1(x>=y)fy=1(y>=x)

3. 複雜函數偏導的鏈式法則

考慮一個麻煩一點的函數,比如f(x,y,z)=(x+y)z 。當然,這個表達式其實還沒那麼複雜,也可以直接求偏導。但是我們用一個非直接的思路去求解一下偏導,以幫助我們直觀理解反向傳播中。如果我們用換元法,把原函數拆成兩個部分q=x+yf=qz 。對於這兩個部分,我們知道怎麼求解它們變量上的偏導:fq=z,fz=q qx=1,qy=1 ,當然q是我們自己設定的一個變量,我們對他的偏導完全不感興趣。
那『鏈式法則』告訴我們一個對上述偏導公式『串聯』的方式,得到我們感興趣的偏導數:fx=fqqx

看個例子:

x = -2; y = 5; z = -4

# 前向計算
q = x + y # q becomes 3
f = q * z # f becomes -12

# 類反向傳播:
# 先算到了 f = q * z
dfdz = q # df/dz = q
dfdq = z # df/dq = z
# 再算到了 q = x + y
dfdx = 1.0 * dfdq # dq/dx = 1 恩,鏈式法則
dfdy = 1.0 * dfdq # dq/dy = 1

鏈式法則的結果是,只剩下我們感興趣的[dfdx,dfdy,dfdz],也就是原函數在x,y,z上的偏導。這是一個簡單的例子,之後的程序裏面我們爲了簡潔,不會完整寫出dfdq,而是用dq代替。

以下是這個計算的示意圖:


例1

4. 反向傳播的直觀理解

一句話概括:反向傳播的過程,實際上是一個由局部到全部的精妙過程。比如上面的電路圖中,其實每一個『門』在拿到輸入之後,都能計算2個東西:

  • 輸出值
  • 對應輸入和輸出的局部梯度

而且很明顯,每個門在進行這個計算的時候是完全獨立的,不需要對電路圖中其他的結構有了解。然而,在整個前向傳輸過程結束之後,在反向傳播過程中,每個門卻能逐步累積計算出它在整個電路輸出上的梯度。『鏈式法則』告訴我們每一個門接收到後向傳來的梯度,同時用它乘以自己算出的對每個輸入的局部梯度,接着往後傳。

以上面的圖爲例,來解釋一下這個過程。加法門接收到輸入[-2, 5]同時輸出結果3。因爲加法操作對兩個輸入的偏導都應該是1。電路後續的部分算出最終結果-12。在反向傳播過程中,鏈式法則是這樣做的:加法操作的輸出3,在最後的乘法操作中,獲得的梯度爲-4,如果把整個網絡擬人化,我們可以認爲這代表着網絡『想要』加法操作的結果小一點,而且是以4*的強度來減小。加法操作的門獲得這個梯度-4以後,把它分別乘以本地的兩個梯度(加法的偏導都是1),1*-4=-4。如果輸入x減小,那加法門的輸出也會減小,這樣乘法輸出會相應的增加。

反向傳播,可以看做網絡中門與門之間的『關聯對話』,它們『想要』自己的輸出更大還是更小(以多大的幅度),從而讓最後的輸出結果更大。

5. Sigmoid例子

上面舉的例子其實在實際應用中很少見,我們很多時候見到的網絡和門函數更復雜,但是不論它是什麼樣的,反向傳播都是可以使用的,唯一的區別就是可能網絡拆解出來的門函數佈局更復雜一些。我們以之前的邏輯迴歸爲例:

f(w,x)=11+e(w0x0+w1x1+w2)

這個看似複雜的函數,其實可以看做一些基礎函數的組合,這些基礎函數及他們的偏導如下:
f(x)=1xdfdx=1/x2fc(x)=c+xdfdx=1f(x)=exdfdx=exfa(x)=axdfdx=a

上述每一個基礎函數都可以看做一個門,如此簡單的初等函數組合在一塊兒卻能夠完成邏輯迴歸中映射函數的複雜功能。下面我們畫出神經網絡,並給出具體輸入輸出和參數的數值:

例2

這個圖中,[x0, x1]是輸入,[w0, w1,w2]爲可調參數,所以它做的事情是對輸入做了一個線性計算(x和w的內積),同時把結果放入sigmoid函數中,從而映射到(0,1)之間的數。

上面的例子中,w與x之間的內積分解爲一長串的小函數連接完成,而後接的是sigmoid函數σ(x) ,有趣的是sigmoid函數看似複雜,求解倒是的時候卻是有技巧的,如下:

σ(x)=11+exdσ(x)dx=ex(1+ex)2=(1+ex11+ex)(11+ex)=(1σ(x))σ(x)

你看,它的導數可以用自己很簡單的重新表示出來。所以在計算導數的時候非常方便,比如sigmoid函數接收到的輸入是1.0,輸出結果是-0.73。那麼我們可以非常方便地計算得到它的偏導爲(1-0.73)*0.73~=0.2。我們看看在這個sigmoid函數部分反向傳播的計算代碼:

w = [2,-3,-3] # 我們隨機給定一組權重
x = [-1, -2]

# 前向傳播
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot)) # sigmoid函數

# 反向傳播經過該sigmoid神經元
ddot = (1 - f) * f # sigmoid函數偏導
dx = [w[0] * ddot, w[1] * ddot] # 在x這條路徑上的反向傳播
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # 在w這條路徑上的反向傳播
# yes!就醬紫算完了!是不是很簡單?

5.1 工程實現小提示

回過頭看看上頭的代碼,你會發現,實際寫代碼實現的時候,有一個技巧能幫助我們很容易地實現反向傳播,我們會把前向傳播的過程分解成反向傳播很容易追溯回來的部分。

6. 反向傳播實戰:複雜函數

我們看一個稍複雜一些的函數:

f(x,y)=x+σ(y)σ(x)+(x+y)2

額,插一句,這個函數沒有任何實際的意義。我們提到它,僅僅是想舉個例子來說明覆雜函數的反向傳播怎麼使用。如果直接對這個函數求x或者y的偏導的話,你會得到一個很複雜的形式。但是如果你用反向傳播去求解具體的梯度值的話,卻完全沒有這個煩惱。我們把這個函數分解成小部分,進行前向和反向傳播計算,即可得到結果,前向傳播計算的代碼如下:
x = 3 # 例子
y = -4

# 前向傳播
sigy = 1.0 / (1 + math.exp(-y)) # 單值上的sigmoid函數
num = x + sigy 
sigx = 1.0 / (1 + math.exp(-x)) 
xpy = x + y      
xpysqr = xpy**2                 
den = sigx + xpysqr
invden = 1.0 / den                                       
f = num * invden # 完成!                                

注意到我們並沒有一次性把前向傳播最後結果算出來,而是刻意留出了很多中間變量,它們都是我們可以直接求解局部梯度的簡單表達式。因此,計算反向傳播就變得簡單了:我們從最後結果往前看,前向運算中的每一箇中間變量sigy, num, sigx, xpy, xpysqr, den, invden我們都會用到,只不過後向傳回的偏導值乘以它們,得到反向傳播的偏導值。反向傳播計算的代碼如下:

# 局部函數表達式爲 f = num * invden
dnum = invden                              
dinvden = num    
# 局部函數表達式爲 invden = 1.0 / den 
dden = (-1.0 / (den**2)) * dinvden                            
# 局部函數表達式爲 den = sigx + xpysqr
dsigx = (1) * dden
dxpysqr = (1) * dden
# 局部函數表達式爲 xpysqr = xpy**2
dxpy = (2 * xpy) * dxpysqr                                        #(5)
# 局部函數表達式爲 xpy = x + y
dx = (1) * dxpy                                                   
dy = (1) * dxpy                                                   
# 局部函數表達式爲 sigx = 1.0 / (1 + math.exp(-x))
dx += ((1 - sigx) * sigx) * dsigx # 注意到這裏用的是 += !!
# 局部函數表達式爲 num = x + sigy
dx += (1) * dnum                                                  
dsigy = (1) * dnum                                                
# 局部函數表達式爲 sigy = 1.0 / (1 + math.exp(-y))
dy += ((1 - sigy) * sigy) * dsigy                                 
# 完事!

實際編程實現的時候,需要注意一下:

  • 前向傳播計算的時候注意保留部分中間變量:在反向傳播計算的時候,會再次用到前向傳播計算中的部分結果。這在反向傳播計算的回溯時可大大加速。

6.1 反向傳播計算中的常見模式

即使因爲搭建的神經網絡結構形式和使用的神經元都不同,但是大多數情況下,後向計算中的梯度計算可以歸到幾種常見的模式上。比如,最常見的三種簡單運算門(加、乘、最大),他們在反向傳播運算中的作用是非常簡單和直接的。我們一起看看下面這個簡單的神經網:


例3

上圖裏有我們提到的三種門add,max和multiply。

  • 加運算門在反向傳播運算中,不管輸入值是多少,取得它output傳回的梯度(gradient)然後均勻地分給兩條輸入路徑。因爲加法運算的偏導都是+1.0。
  • max(取最大)門不像加法門,在反向傳播計算中,它只會把傳回的梯度回傳給一條輸入路徑。因爲max(x,y)只對x和y中較大的那個數,偏導爲+1.0,而另一個數上的偏導是0。
  • 乘法門就更好理解了,因爲x*y對x的偏導爲y,而對y的偏導爲x,因此在上圖中x的梯度是-8.0,即-4.0*2.0

因爲梯度回傳的原因,神經網絡對輸入非常敏感。我們拿乘法門來舉例,如果輸入的xi 全都變成原來1000倍,而權重w不變,那麼在反向傳播計算的時候,x路徑上獲得的回傳梯度不變,而w上的梯度則會變大1000倍,這使得你不得不降低學習速率(learning rate)成原來的1/1000以維持平衡。因此在很多神經網絡的問題中,輸入數據的預處理也是非常重要的。

6.2 向量化的梯度運算

上面所有的部分都是在單變量的函數上做的處理和運算,實際我們在處理很多數據(比如圖像數據)的時候,維度都比較高,這時候我們就需要把單變量的函數反向傳播擴展到向量化的梯度運算上,需要特別注意的是矩陣運算的每個矩陣維度,以及轉置操作。

我們通過簡單的矩陣運算來拓展前向和反向傳播運算,示例代碼如下:

# 前向傳播運算
W = np.random.randn(5, 10)
X = np.random.randn(10, 3)
D = W.dot(X)

# 假如我們現在已經拿到了回傳到D上的梯度dD
dD = np.random.randn(*D.shape) # 和D同維度
dW = dD.dot(X.T) #.T 操作計算轉置, dW爲W路徑上的梯度
dX = W.T.dot(dD)  #dX爲X路徑上的梯度

7. 總結

直觀地理解,反向傳播可以看做圖解求導的鏈式法則。
最後我們用一組圖來說明實際優化過程中的正向傳播與反向殘差傳播:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

參考資料與原文

cs231n 反向傳播與直觀理解

發佈了49 篇原創文章 · 獲贊 343 · 訪問量 69萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章