說(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ò)很嚴(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)的橫線不像右邊橫線表達(dá)的那樣割以,它不是表示除法金度。這個(gè)符號(hào)表示操作d/dx被運(yùn)用到f上,并返回一個(gè)不同的函數(shù)(導(dǎo)函數(shù))严沥。你可以把上面的表達(dá)式看成當(dāng)h很小時(shí)猜极,那么這個(gè)函數(shù)可以近似看成一條直線,那么導(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)昆禽,還有四種新類型的門(mén):
其中 fc 將輸入值增加一個(gè)常量c, fa將輸入值乘以一個(gè)常量壮韭。他們可以看作是加法和乘法的特殊情況喷屋,但是我們把它們看作新的一元門(mén),因?yàn)槲覀冃枰@些常量恶耽。整個(gè)電路如下所示:
從上面的例子可以看出溉苛,對(duì)w和x的點(diǎn)乘結(jié)果進(jìn)行了一系列的操作,這些操作的函數(shù)稱之為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)比較難以解釋,它的局部梯度就是輸入的數(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ù)字的情況一樣)怨酝∧窍龋總有一種方法可以確定維度。比如慷垮,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)尤揣。