說(shuō)明:
本系列文章翻譯斯坦福大學(xué)的課程:Convolutional Neural Networks for Visual Recognition的課程講義 原文地址:http://cs231n.github.io/。 最好有Python基礎(chǔ)(但不是必要的),Python的介紹見(jiàn)該課程的module0。
本節(jié)的code見(jiàn)地址:
https://github.com/anthony123/cs231n/tree/master/module1-4如果在code中發(fā)現(xiàn)bug或者有什么不清楚的地方,可以及時(shí)給我留言,因?yàn)閏ode沒(méi)有經(jīng)過(guò)很?chē)?yán)格的測(cè)試。
課程目錄:
簡(jiǎn)介簡(jiǎn)單表達(dá)式,梯度解釋復(fù)合表達(dá)式, 鏈?zhǔn)椒▌t, 反向傳播反向傳播的直覺(jué)理解
模塊化: sigmoid 例子
反向傳播實(shí)踐:分層計(jì)算(Staged computation)
反向流的模式
向量化操作的梯度
總結(jié)
簡(jiǎn)介
動(dòng)機(jī)
在這一節(jié)中,我們將介紹反向傳播,它通過(guò)遞歸地應(yīng)用鏈?zhǔn)椒▌t來(lái)計(jì)算表達(dá)式的梯度。理解這個(gè)過(guò)程及其細(xì)節(jié)是理解,有效開(kāi)發(fā),設(shè)計(jì)及調(diào)試神經(jīng)網(wǎng)絡(luò)的關(guān)鍵。
問(wèn)題描述
這節(jié)課研究的主要問(wèn)題如下:我們已知一個(gè)函數(shù) f(x),其中x是輸入向量,我們的興趣在于計(jì)算f在x處的梯度(比如:▽f(x))。
動(dòng)機(jī)
回憶一下, 我們對(duì)這個(gè)問(wèn)題感興趣的主要原因是,在神經(jīng)網(wǎng)絡(luò)這種特殊情況下,f對(duì)應(yīng)于損失函數(shù)(L),輸入x由訓(xùn)練數(shù)據(jù)和神經(jīng)網(wǎng)絡(luò)的權(quán)重組成。比如,損失函數(shù)可能是SVM損失函數(shù),輸入由訓(xùn)練數(shù)據(jù)(xi, yi), i= 1 … N 和權(quán)重W與偏置量b組成。注意(在機(jī)器學(xué)習(xí)中通常是這種情況)我們通常認(rèn)為訓(xùn)練數(shù)據(jù)已經(jīng)給出并且是固定的,而權(quán)重是我們需要控制的變量。所以,盡管我們可以非常容易對(duì)輸入數(shù)據(jù)xi使用反向傳播計(jì)算梯度,但在實(shí)踐中, 我們只計(jì)算參數(shù)的梯度(W和b),以便我們可以進(jìn)行參數(shù)更新。然而,在后面的課程我們可以看到對(duì)xi求梯度有時(shí)候也是有用的,比如對(duì)神經(jīng)網(wǎng)絡(luò)的可視化及解釋。
如果你之前已經(jīng)非常了解如何使用鏈?zhǔn)椒▌t求梯度,那么我們還是建議你瀏覽一下這一節(jié),因?yàn)槲覀儗⒁詫?shí)數(shù)電路反向流的角度解釋反向傳播,你在這種視角下學(xué)到的東西對(duì)你后續(xù)的課程會(huì)有幫助。
簡(jiǎn)單表達(dá)式和梯度解釋
讓我們從簡(jiǎn)單的表達(dá)式開(kāi)始,這樣有利于我們理解更復(fù)雜表達(dá)式的概念及我們的一些約定的符號(hào)表達(dá)。考慮一個(gè)簡(jiǎn)單的兩個(gè)數(shù)字乘法函數(shù)
f(x,y)= xy。
對(duì)變量求偏導(dǎo)是一個(gè)簡(jiǎn)單的微積分問(wèn)題:

解釋
記住導(dǎo)數(shù)告訴你的事情:它告訴我們?cè)谝粋€(gè)變量無(wú)限小的周邊區(qū)域內(nèi)函數(shù)變化的速率。

嚴(yán)格來(lái)講,左邊符號(hào)的橫線(xiàn)不像右邊橫線(xiàn)表達(dá)的那樣,它不是表示除法。這個(gè)符號(hào)表示操作d/dx被運(yùn)用到f上,并返回一個(gè)不同的函數(shù)(導(dǎo)函數(shù))。你可以把上面的表達(dá)式看成當(dāng)h很小時(shí),那么這個(gè)函數(shù)可以近似看成一條直線(xiàn),那么導(dǎo)數(shù)就是斜率。也就是說(shuō),對(duì)每個(gè)變量的導(dǎo)數(shù),告訴我們整個(gè)表達(dá)式在這個(gè)變量上的敏感度。比如,如果x=4,y=-3,f(x,y)= -12。對(duì)x的偏導(dǎo)數(shù)?f/?x = -3,這個(gè)表達(dá)式告訴我們,如果我們稍微提高這個(gè)變量,那么整個(gè)表達(dá)式的值就會(huì)減少(因?yàn)槭秦?fù)號(hào)),并且減少的量為改變量的三倍。上面的表達(dá)式也可以寫(xiě)成如下形式:f(x+h) = f(x) + h*(df(x)/dx)。同理,?f/?y = 4,我們期望稍微增加y的值h,那么整個(gè)表達(dá)式就會(huì)增加4h。
變量的導(dǎo)數(shù)告訴我們 整個(gè)表達(dá)式在這個(gè)變量上的敏感性。
之前提到, 梯度▽f 是偏導(dǎo)數(shù)的向量,所以▽f = [?f/?x, ?f/?y] = [y,x]。盡管梯度實(shí)際上是一個(gè)向量,但是為了簡(jiǎn)潔,我們經(jīng)常說(shuō)“x的梯度”,而不是x上的偏導(dǎo)數(shù)。
我們也可以求出加法操作的導(dǎo)數(shù)

從上式可以看出,不管x和y是什么值,他們的導(dǎo)數(shù)都為1。這也合乎情理,因?yàn)楫?dāng)我們?cè)黾觴或者y時(shí),表達(dá)式也會(huì)增加x或y。所以增加的速率與x或y的值都沒(méi)有關(guān)系(和乘法運(yùn)算不一樣)。最后一個(gè)例子是我們?cè)诤罄m(xù)的課程見(jiàn)到比較多的max函數(shù):

從上式可以看出,輸入值較大值者的(子)梯度為1,而另外那個(gè)輸入值的梯度為0。如果輸入為x=4, y=2, 那么最大值為4,所以函數(shù)對(duì)y值的變化不敏感。也就是說(shuō),當(dāng)我們對(duì)y提高一個(gè)微小量h,那么這個(gè)函數(shù)的輸入仍然為4,所以它的梯度為0,也就是沒(méi)有效果。當(dāng)然,如果我們對(duì)y變化較大(比如 大于2),那么f的值就會(huì)改變,但是導(dǎo)數(shù)并不會(huì)告訴我們自變量發(fā)生較大變化時(shí)對(duì)函數(shù)f的影響。它們只能預(yù)測(cè)輸入量微小的,無(wú)限小的變化時(shí)對(duì)f的影響,即當(dāng)h趨近于0的時(shí)候。
使用鏈?zhǔn)椒▌t的復(fù)合表達(dá)式
現(xiàn)在我們來(lái)看一下一個(gè)稍微復(fù)雜的復(fù)合函數(shù)的例子。比如:f(x,y,z) = (x+y)*z 。這個(gè)表達(dá)式依然非常簡(jiǎn)單,可以直接求導(dǎo),但是,我們將使用一種特殊的方法,這種方法可以幫助我們理解反向傳播。特別的,這個(gè)表達(dá)式可以分成兩個(gè)更簡(jiǎn)單的表達(dá)式:q=x+y 和 f=qz 。而且,我們知道如何計(jì)算這兩個(gè)簡(jiǎn)單的導(dǎo)數(shù)。對(duì)于f=qz,?f/?q = z; ?f/?z = q。 對(duì)于q=x+y, ?q/?x=1, ?q/?y=1。但是我們并不關(guān)心中間變量q的梯度,即?f/?q并沒(méi)有意義。我們只關(guān)心f在輸入變量x,y,z上的梯度。鏈?zhǔn)椒▌t告訴我們,正確鏈接這些梯度表達(dá)式的方式是通過(guò)乘法實(shí)現(xiàn)。比如:?f/?x=?f/?q?q/?x。在實(shí)踐中,這是兩個(gè)梯度值的乘法。讓我們來(lái)看一下這個(gè)例子的代碼:
#設(shè)置輸入值
x = -2; y = 5; z = -4
#計(jì)算前向過(guò)程
q=x+y
f=q*z
#逆序求出反向過(guò)程,也就是先求f=q*z
dfdz=q
dfdq=z
#現(xiàn)在通過(guò)q=x+y反向傳播
dfdx=1.0*dfdq #dq/dx=1,這里的乘法便是鏈?zhǔn)椒▌t的運(yùn)用
dfdy=1.0*dfdq
所以我們最終得到變量的梯度[dfdx,dfdy,dfdz],它告訴我們f在變量x,y,z上的敏感度。這是反向傳播最簡(jiǎn)單的例子。在后續(xù)的講解中,我們dq代替dfdq,并且總是假設(shè)梯度是關(guān)于最終表達(dá)式的。
這個(gè)計(jì)算可以使用以下電路圖直觀的顯示:

反向傳播的直觀理解
我們可以發(fā)現(xiàn)反向傳播是一個(gè)非常優(yōu)美的局部過(guò)程。電路圖的每個(gè)門(mén)可以獲得輸入,并且能夠立刻計(jì)算兩個(gè)值:1)它的輸出值;2)根據(jù)輸出值計(jì)算局部梯度值。我們注意到這些門(mén)不需要知道整個(gè)電路的細(xì)節(jié),便可以計(jì)算梯度。一旦前向傳導(dǎo)結(jié)束,在反向傳播過(guò)程中,門(mén)最終會(huì)通過(guò)整個(gè)電路的最終值知道它的輸出值的梯度。鏈?zhǔn)椒▌t告訴我們,門(mén)應(yīng)該將它的梯度乘以每個(gè)輸入值的梯度值。
運(yùn)用鏈?zhǔn)椒▌t產(chǎn)生的乘法可以將一個(gè)單獨(dú)的,相對(duì)沒(méi)有用的門(mén),變成一個(gè)復(fù)雜電路的齒輪,比如整個(gè)神經(jīng)網(wǎng)絡(luò)。
為了獲得更加直覺(jué)的理解,我們?cè)僖陨厦娴谋磉_(dá)式為例。加門(mén)獲得輸入[-2, 5], 計(jì)算的輸出值為3。因?yàn)檫@個(gè)門(mén)是用來(lái)計(jì)算加法運(yùn)算,所以這兩個(gè)輸入值的局部梯度值都是+1。剩下的電路計(jì)算最終的輸出值:-12 。在反向傳播過(guò)程中,鏈?zhǔn)椒▌t遞歸地反向運(yùn)用到整個(gè)網(wǎng)絡(luò)。加門(mén)(乘門(mén)的輸入)學(xué)習(xí)到它的輸出值梯度為-4。如果我們把整個(gè)電路比作一個(gè)人,他希望輸出一個(gè)較高的值(這個(gè)比喻可以幫助我們理解),那么電路會(huì)希望加門(mén)的輸出會(huì)降低(因?yàn)槭秦?fù)號(hào)),并且以4倍的力氣。繼續(xù)遞歸并鏈接梯度,加門(mén)使用這個(gè)梯度,并且將它乘以它所有輸入值的局部梯度(x和y上的梯度都為 1*-4 = -4)。這也符合我們的預(yù)期,如果x和y的值減少(對(duì)應(yīng)他們的負(fù)梯度),那么加門(mén)的輸出也會(huì)下降,從而會(huì)使得乘門(mén)的輸出上升。
反向傳播因此可以被認(rèn)為是門(mén)(通過(guò)梯度信號(hào))之間交流他們想要他們的輸出結(jié)果(增加或者減少,以多大的強(qiáng)度),從而使得最后的結(jié)果增加。
模塊化: sigmoid
例子
上面介紹的門(mén)相對(duì)比較隨機(jī),任何可微分的函數(shù)都可以作為一個(gè)門(mén),我們可以將多個(gè)門(mén)合成一個(gè)門(mén),或者將一個(gè)函數(shù)分解為多個(gè)門(mén)。我們來(lái)看下面這個(gè)例子:

在后面的課程我們知道,這個(gè)表達(dá)式描述了一個(gè)使用sigmoid激活函數(shù)的二維神經(jīng)元(輸入為x, 權(quán)重為W)。但是現(xiàn)在我們把它簡(jiǎn)單的認(rèn)為是一個(gè)將w,x轉(zhuǎn)變成一個(gè)數(shù)字的函數(shù)。這個(gè)函數(shù)由許多門(mén)組成,除了上面描述的那些門(mén),還有四種新類(lèi)型的門(mén):

其中 fc 將輸入值增加一個(gè)常量c, fa將輸入值乘以一個(gè)常量。他們可以看作是加法和乘法的特殊情況,但是我們把它們看作新的一元門(mén),因?yàn)槲覀冃枰@些常量。整個(gè)電路如下所示:

從上面的例子可以看出,對(duì)w和x的點(diǎn)乘結(jié)果進(jìn)行了一系列的操作,這些操作的函數(shù)稱(chēng)之為Sigmoid函數(shù)。Sigmoid函數(shù)的導(dǎo)數(shù)非常簡(jiǎn)單:

我們可以看到,如果sigmoid的輸入為1.0,輸出為0.73,那么它的梯度為(1-0.73)*0.73 = 0.2 。和上面的結(jié)果一致。所以,在以后的應(yīng)用中,我們可以將它們合并成一個(gè)門(mén),讓我們?cè)诖a中看神經(jīng)元的反向傳播:
w=[2,-3,-3] #隨機(jī)的梯度和數(shù)據(jù)
x = [-1, -2]
#前向傳播
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0/(1+math.exp(-dot))
#神經(jīng)元的反向傳播
ddot = (1-f)*f #直接使用公式計(jì)算梯度
dx = [w[0]*ddot, w[1]*ddot] #反向傳播到x
dw = [x[0]*ddot, x[1]*ddot, 1.0*ddot] #反向傳播到w
實(shí)現(xiàn)技巧:階段化反向傳播
像上面的代碼顯示的那樣,在實(shí)踐中,我們經(jīng)常將前向傳播分解成容易反向傳播的幾個(gè)子階段。比如,在上面的例子中,我們創(chuàng)建了一個(gè)新的變量dot 計(jì)算w和x之間的點(diǎn)積。在反向傳播過(guò)程中,我們連續(xù)(逆序)計(jì)算對(duì)應(yīng)變量的梯度。
這節(jié)課的重點(diǎn)在于反向傳播的細(xì)節(jié),把哪部分看成是門(mén)完全是出于方便的考慮。它可以幫助了解哪一部分的表達(dá)式比較容易計(jì)算局部梯度,以便我們可以使用最少的代碼和努力計(jì)算出所有變量的梯度。
反向傳播實(shí)踐: 階段化計(jì)算
我們來(lái)看另外一個(gè)例子,假設(shè)我們有以下這個(gè)函數(shù):

首先說(shuō)明一下,這個(gè)函數(shù)沒(méi)有任何意義,它完全是用于練習(xí)的一個(gè)例子。如果你要計(jì)算x或者y的偏導(dǎo)數(shù),那將會(huì)很復(fù)雜。但是通過(guò)將上式分解,我們可以非常容易的求出我們想要的結(jié)果,代碼如下:
x = 3 # example values
y = -4
# forward pass
sigy = 1.0 / (1 + math.exp(-y)) # sigmoid in numerator #(1)
num = x + sigy # numerator #(2)
sigx = 1.0 / (1 + math.exp(-x)) # sigmoid in denominator #(3)
xpy = x + y #(4)
xpysqr = xpy**2 #(5)
den = sigx + xpysqr # denominator #(6)
invden = 1.0 / den #(7)
f = num * invden # done! #(8)
在代碼的末端,我們完成了前向傳導(dǎo)。注意到我們創(chuàng)建了很多中間變量,每一個(gè)中間變量都是非常容易求出其梯度的。因此,計(jì)算反向傳播非常容易,我們只需要逆序計(jì)算出所有變量的梯度就行(sigy , num, sigx, xpy, xpysqr, den, inden)。我們將會(huì)創(chuàng)建相同的變量,但是將以d開(kāi)頭,表示關(guān)于那個(gè)變量的梯度。同時(shí),我們計(jì)算梯度的之后,除了計(jì)算局部梯度,還要將表達(dá)式的結(jié)果與梯度想乘,從而計(jì)算最終的梯度。代碼如下:
# backprop f = num * invden
dnum = invden # gradient on numerator #(8)
dinvden = num #(8)
# backprop invden = 1.0 / den
dden = (-1.0 / (den**2)) * dinvden #(7)
# backprop den = sigx + xpysqr
dsigx = (1) * dden #(6)
dxpysqr = (1) * dden #(6)
# backprop xpysqr = xpy**2
dxpy = (2 * xpy) * dxpysqr #(5)
# backprop xpy = x + y
dx = (1) * dxpy #(4)
dy = (1) * dxpy #(4)
# backprop sigx = 1.0 / (1 + math.exp(-x))
dx += ((1 - sigx) * sigx) * dsigx # Notice += !! See notes below #(3)
# backprop num = x + sigy
dx += (1) * dnum #(2)
dsigy = (1) * dnum #(2)
# backprop sigy = 1.0 / (1 + math.exp(-y))
dy += ((1 - sigy) * sigy) * dsigy #(1)
# done! phew
有一些事情需要注意:
緩存前向傳導(dǎo)的變量:在反向傳導(dǎo)的過(guò)程中,前向傳導(dǎo)過(guò)程中的一些變量將非常有用。在實(shí)踐中,你寫(xiě)代碼的時(shí)候,最好能將這些變量緩存,以便它們?cè)诜聪騻鞑サ倪^(guò)程中能夠用到。
在分叉處的梯度:在前向表達(dá)式中,包含x和y變量多次,所以在反向傳播過(guò)程中,我們應(yīng)該使用+= 而不是 =。因?yàn)槲覀冃枰堰@個(gè)變量上所有的梯度都要累計(jì)起來(lái)。這也符合多變量鏈?zhǔn)椒▌t:如果一個(gè)變量分支到電路的不同部分,那么反向傳播的梯度就會(huì)累加。
反向傳播流的模式
在很多情況下,反向傳播流的梯度可以以一種直覺(jué)的方式來(lái)解釋。比如:在神經(jīng)網(wǎng)絡(luò)中經(jīng)常使用的三個(gè)門(mén)(加,乘,最大值),都有一個(gè)比較簡(jiǎn)單的解釋方式。以這個(gè)電路為例:

從上面的圖我們可以看出:
加門(mén)永遠(yuǎn)接收輸出的梯度,并把它平等的分散到所有的輸入中,而不管在前向傳導(dǎo)過(guò)程中輸入值是多少。這也與加法的梯度值為+1相吻合。所以所有輸入的梯度都與輸出的梯度值相同。像上圖顯示的那樣。
最大值門(mén)可以理解為路由梯度。不像加門(mén),平等地將輸出梯度路由到所有的輸入梯度,最大值門(mén)將輸出梯度路由到其中的一個(gè)分支(在前向傳導(dǎo)過(guò)程中,
數(shù)值較高的那個(gè)變量)。這是因?yàn)樽畲笾甸T(mén)中較大的輸入值的局部梯度為1,較小的輸入值局部梯度為0。像上圖顯示的那樣。
乘法門(mén)比較難以解釋?zhuān)木植刻荻染褪禽斎氲臄?shù)字(位置交換),然后再與輸出梯度相乘。像上圖顯示的那樣。
向量化操作的梯度
上面講的都是基于單個(gè)變量的,但是它們可以非常容易地?cái)U(kuò)展到矩陣和向量的操作。然而,我們必須要注意維度和轉(zhuǎn)置操作。
矩陣相乘的梯度 可能最容易出錯(cuò)的操作是矩陣與矩陣之間的乘法(最終分解成矩陣與向量,向量與向量之間的操作)。
#前向傳導(dǎo)
W=np.random.randn(5,10)
X=np.random.randn(10,3)
D=W.dot(X)
#假設(shè)我們已經(jīng)知道D的梯度
dD = np.random.randn(*D.shape)
dW = dD.dot(X.T)
dX = W.T.dot(dD)
技巧:使用維度分析。我們沒(méi)有必要記住dW 和dX的表達(dá)式,因?yàn)槲覀兒苋菀淄ㄟ^(guò)維度推斷出來(lái)。比如:我們知道dw的大小和W的大小是一致的,并且它是由X和dD的乘積得到。(以x,W都是數(shù)字的情況一樣)??傆幸环N方法可以確定維度。比如,x的大小為[10x3],dD的大小為[5x3], 所以如果我們知道dW的大小為[5x10](由W的大小確定),那么唯一得到這個(gè)結(jié)果的方法是dD.dot(X.T),如上所示。
在小的,顯式的例子上實(shí)驗(yàn)
有些人會(huì)覺(jué)得在向量化的例子上推導(dǎo)梯度更新有有點(diǎn)困難。我們的建議是寫(xiě)出一個(gè)顯式的,小型的向量化的例子,在紙上推導(dǎo)梯度,并將模式擴(kuò)展到向量化的形式中。
總結(jié):
我們講解了梯度的意義,它在電路中是如何反向傳播以及他們?nèi)绾谓涣?,電路的哪一部分?yīng)該增加或減少,以及需要改變多少,使的最終的結(jié)果變得更高。我們討論了實(shí)現(xiàn)反向傳播過(guò)程中**階段化計(jì)算**的重要性。你總是需要將你的函數(shù)分解成不同的模塊,而每個(gè)模塊可以非常容易地計(jì)算子梯度,最后使用鏈?zhǔn)椒▌t將梯度鏈接起來(lái)。而且,你可能永遠(yuǎn)不需要在紙上寫(xiě)出整個(gè)表達(dá)式的導(dǎo)數(shù)表達(dá)式。所以,把你的表達(dá)式分解成不同的階段,并分別計(jì)算這些中間變量的梯度(這些階段有矩陣向量乘法,或者最大值操作,或者總和操作等),最后通過(guò)反向傳播計(jì)算各個(gè)變量的梯度。
在下一節(jié)課中,我們開(kāi)始定義神經(jīng)網(wǎng)絡(luò),反向傳播使得我們可以有效的計(jì)算神經(jīng)網(wǎng)絡(luò)連接處的梯度(關(guān)于損失函數(shù))。也就是說(shuō),我們已經(jīng)準(zhǔn)備好訓(xùn)練Neural Nets, 這個(gè)課程最難的理論部分已經(jīng)結(jié)束了, ConNets現(xiàn)在只離我一步之遠(yuǎn)。