CS231n課程筆記翻譯:反向傳播筆記
譯者註:本文智能單元首發,譯自斯坦福CS231n課程筆記Backprop Note,課程教師Andrej Karpathy授權翻譯。本篇教程由杜客翻譯完成,堃堃和鞏子嘉進行校對修改。譯文含公式和代碼,建議PC端閱讀。
原文如下:
內容列表:
- 簡介
- 簡單表達式和理解梯度
- 複合表達式,鏈式法則,反向傳播
- 直觀理解反向傳播
- 模塊:Sigmoid例子
- 反向傳播實踐:分段計算
- 回傳流中的模式
- 用戶向量化操作的梯度
- 小結
簡介
目標:本節將幫助讀者對反向傳播形成直觀而專業的理解。反向傳播是利用鏈式法則遞歸計算表達式的梯度的方法。理解反向傳播過程及其精妙之處,對於理解、實現、設計和調試神經網路非常關鍵。
問題陳述:這節的核心問題是:給定函數 ,其中是輸入數據的向量,需要計算函數關於的梯度,也就是。
目標:之所以關註上述問題,是因為在神經網路中對應的是損失函數(),輸入裡面包含訓練數據和神經網路的權重。舉個例子,損失函數可以是SVM的損失函數,輸入則包含了訓練數據、權重和偏差。注意訓練集是給定的(在機器學習中通常都是這樣),而權重是可以控制的變數。因此,即使能用反向傳播計算輸入數據 上的梯度,但在實踐為了進行參數更新,通常也只計算參數(比如)的梯度。然而 的梯度有時仍然是有用的:比如將神經網路所做的事情可視化便於直觀理解的時候,就能用上。
如果讀者之前對於利用鏈式法則計算偏微分已經很熟練,仍然建議瀏覽本篇筆記。因為它呈現了一個相對成熟的反向傳播視角,在該視角中能看見基於實數值迴路的反向傳播過程,而對其細節的理解和收穫將幫助讀者更好地通過本課程。
簡單表達式和理解梯度
從簡單表達式入手可以為複雜表達式打好符號和規則基礎。先考慮一個簡單的二元乘法函數。對兩個輸入變數分別求偏導數還是很簡單的:
解釋:牢記這些導數的意義:函數變數在某個點周圍的極小區域內變化,而導數就是變數變化導致的函數在該方向上的變化率。
注意等號左邊的分號和等號右邊的分號不同,不是代表分數。相反,這個符號表示操作符被應用於函數,並返回一個不同的函數(導數)。對於上述公式,可以認為值非常小,函數可以被一條直線近似,而導數就是這條直線的斜率。換句話說,每個變數的導數指明了整個表達式對於該變數的值的敏感程度。比如,若,則,的導數。這就說明如果將變數的值變大一點,整個表達式的值就會變小(原因在於負號),而且變小的量是變大的量的三倍。通過重新排列公式可以看到這一點()。同樣,因為,可以知道如果將的值增加,那麼函數的輸出也將增加(原因在於正號),且增加量是。
函數關於每個變數的導數指明了整個表達式對於該變數的敏感程度。
如上所述,梯度是偏導數的向量,所以有。即使是梯度實際上是一個向量,仍然通常使用類似「x上的梯度」的術語,而不是使用如「x的偏導數」的正確說法,原因是因為前者說起來簡單。
我們也可以對加法操作求導:
這就是說,無論其值如何,的導數均為1。這是有道理的,因為無論增加中任一個的值,函數的值都會增加,並且增加的變化率獨立於的具體值(情況和乘法操作不同)。取最大值操作也是常常使用的:
上式是說,如果該變數比另一個變數大,那麼梯度是1,反之為0。例如,若,那麼max是4,所以函數對於就不敏感。也就是說,在上增加,函數還是輸出為4,所以梯度是0:因為對於函數輸出是沒有效果的。當然,如果給增加一個很大的量,比如大於2,那麼函數的值就變化了,但是導數並沒有指明輸入量有巨大變化情況對於函數的效果,他們只適用於輸入量變化極小時的情況,因為定義已經指明:。
使用鏈式法則計算複合表達式
現在考慮更複雜的包含多個函數的複合函數,比如。雖然這個表達足夠簡單,可以直接微分,但是在此使用一種有助於讀者直觀理解反向傳播的方法。將公式分成兩部分:和。在前面已經介紹過如何對這分開的兩個公式進行計算,因為是和相乘,所以,又因為是加,所以。然而,並不需要關心中間量的梯度,因為沒有用。相反,函數關於的梯度才是需要關注的。鏈式法則指出將這些梯度表達式鏈接起來的正確方式是相乘,比如。在實際操作中,這只是簡單地將兩個梯度數值相乘,示例代碼如下:
# 設置輸入值x = -2; y = 5; z = -4# 進行前向傳播q = x + y # q becomes 3f = q * z # f becomes -12# 進行反向傳播:# 首先回傳到 f = q * zdfdz = q # df/dz = q, 所以關於z的梯度是3dfdq = z # df/dq = z, 所以關於q的梯度是-4# 現在回傳到q = x + ydfdx = 1.0 * dfdq # dq/dx = 1. 這裡的乘法是因為鏈式法則dfdy = 1.0 * dfdq # dq/dy = 1
最後得到變數的梯度[dfdx, dfdy, dfdz],它們告訴我們函數f對於變數[x, y, z]的敏感程度。這是一個最簡單的反向傳播。一般會使用一個更簡潔的表達符號,這樣就不用寫df了。這就是說,用dq來代替dfdq,且總是假設梯度是關於最終輸出的。
這次計算可以被可視化為如下計算線路圖像:
————————————————————————————————————————
上圖的真實值計算線路展示了計算的視覺化過程。前向傳播從輸入計算到輸出(綠色),反向傳播從尾部開始,根據鏈式法則遞歸地向前計算梯度(顯示為紅色),一直到網路的輸入端。可以認為,梯度是從計算鏈路中迴流。————————————————————————————————————————
反向傳播的直觀理解
反向傳播是一個優美的局部過程。在整個計算線路圖中,每個門單元都會得到一些輸入並立即計算兩個東西:1. 這個門的輸出值,和2.其輸出值關於輸入值的局部梯度。門單元完成這兩件事是完全獨立的,它不需要知道計算線路中的其他細節。然而,一旦前向傳播完畢,在反向傳播的過程中,門單元門將最終獲得整個網路的最終輸出值在自己的輸出值上的梯度。鏈式法則指出,門單元應該將回傳的梯度乘以它對其的輸入的局部梯度,從而得到整個網路的輸出對該門單元的每個輸入值的梯度。
這裡對於每個輸入的乘法操作是基於鏈式法則的。該操作讓一個相對獨立的門單元變成複雜計算線路中不可或缺的一部分,這個複雜計算線路可以是神經網路等。
下面通過例子來對這一過程進行理解。加法門收到了輸入[-2, 5],計算輸出是3。既然這個門是加法操作,那麼對於兩個輸入的局部梯度都是+1。網路的其餘部分計算出最終值為-12。在反向傳播時將遞歸地使用鏈式法則,算到加法門(是乘法門的輸入)的時候,知道加法門的輸出的梯度是-4。如果網路如果想要輸出值更高,那麼可以認為它會想要加法門的輸出更小一點(因為負號),而且還有一個4的倍數。繼續遞歸併對梯度使用鏈式法則,加法門拿到梯度,然後把這個梯度分別乘到每個輸入值的局部梯度(就是讓-4乘以x和y的局部梯度,x和y的局部梯度都是1,所以最終都是-4)。可以看到得到了想要的效果:如果x,y減小(它們的梯度為負),那麼加法門的輸出值減小,這會讓乘法門的輸出值增大。
因此,反向傳播可以看做是門單元之間在通過梯度信號相互通信,只要讓它們的輸入沿著梯度方向變化,無論它們自己的輸出值在何種程度上升或降低,都是為了讓整個網路的輸出值更高。
模塊化:Sigmoid例子
上面介紹的門是相對隨意的。任何可微分的函數都可以看做門。可以將多個門組合成一個門,也可以根據需要將一個函數分拆成多個門。現在看看一個表達式:
在後面的課程中可以看到,這個表達式描述了一個含輸入x和權重w的2維的神經元,該神經元使用了sigmoid激活函數。但是現在只是看做是一個簡單的輸入為x和w,輸出為一個數字的函數。這個函數是由多個門組成的。除了上文介紹的加法門,乘法門,取最大值門,還有下面這4種:
其中,函數使用對輸入值進行了常量的平移,將輸入值擴大了常量倍。它們是加法和乘法的特例,但是這裡將其看做一元門單元,因為確實需要計算常量的梯度。整個計算線路如下:
———————————————————————————————————————
使用sigmoid激活函數的2維神經元的例子。輸入是[x0, x1],可學習的權重是[w0, w1, w2]。一會兒會看見,這個神經元對輸入數據做點積運算,然後其激活數據被sigmoid函數擠壓到0到1之間。
————————————————————————————————————————
在上面的例子中可以看見一個函數操作的長鏈條,鏈條上的門都對w和x的點積結果進行操作。該函數被稱為sigmoid函數。sigmoid函數關於其輸入的求導是可以簡化的(使用了在分子上先加後減1的技巧):
可以看到梯度計算簡單了很多。舉個例子,sigmoid表達式輸入為1.0,則在前向傳播中計算出輸出為0.73。根據上面的公式,局部梯度為(1-0.73)*0.73~=0.2,和之前的計算流程比起來,現在的計算使用一個單獨的簡單表達式即可。因此,在實際的應用中將這些操作裝進一個單獨的門單元中將會非常有用。該神經元反向傳播的代碼實現如下:
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函數# 對神經元反向傳播ddot = (1 - f) * f # 點積變數的梯度, 使用sigmoid函數求導dx = [w[0] * ddot, w[1] * ddot] # 回傳到xdw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # 回傳到w# 完成!得到輸入的梯度
實現提示:分段反向傳播。上面的代碼展示了在實際操作中,為了使反向傳播過程更加簡潔,把向前傳播分成不同的階段將是很有幫助的。比如我們創建了一個中間變數dot,它裝著w和x的點乘結果。在反向傳播的時,就可以(反向地)計算出裝著w和x等的梯度的對應的變數(比如ddot,dx和dw)。
本節的要點就是展示反向傳播的細節過程,以及前向傳播過程中,哪些函數可以被組合成門,從而可以進行簡化。知道表達式中哪部分的局部梯度計算比較簡潔非常有用,這樣他們可以「鏈」在一起,讓代碼量更少,效率更高。
反向傳播實踐:分段計算
看另一個例子。假設有如下函數:
首先要說的是,這個函數完全沒用,讀者是不會用到它來進行梯度計算的,這裡只是用來作為實踐反向傳播的一個例子,需要強調的是,如果對或進行微分運算,運算結束後會得到一個巨大而複雜的表達式。然而做如此複雜的運算實際上並無必要,因為我們不需要一個明確的函數來計算梯度,只需知道如何使用反向傳播計算梯度即可。下面是構建前向傳播的代碼模式:
x = 3 # 例子數值y = -4# 前向傳播sigy = 1.0 / (1 + math.exp(-y)) # 分子中的sigmoi #(1)num = x + sigy # 分子 #(2)sigx = 1.0 / (1 + math.exp(-x)) # 分母中的sigmoid #(3)xpy = x + y #(4)xpysqr = xpy**2 #(5)den = sigx + xpysqr # 分母 #(6)invden = 1.0 / den #(7)f = num * invden # 搞定! #(8)
┗|`O′|┛ 嗷~~,到了表達式的最後,就完成了前向傳播。注意在構建代碼s時創建了多個中間變數,每個都是比較簡單的表達式,它們計算局部梯度的方法是已知的。這樣計算反向傳播就簡單了:我們對前向傳播時產生每個變數(sigy, num, sigx, xpy, xpysqr, den, invden)進行回傳。我們會有同樣數量的變數,但是都以d開頭,用來存儲對應變數的梯度。注意在反向傳播的每一小塊中都將包含了表達式的局部梯度,然後根據使用鏈式法則乘以上游梯度。對於每行代碼,我們將指明其對應的是前向傳播的哪部分。
# 回傳 f = num * invdendnum = invden # 分子的梯度 #(8)dinvden = num #(8)# 回傳 invden = 1.0 / den dden = (-1.0 / (den**2)) * dinvden #(7)# 回傳 den = sigx + xpysqrdsigx = (1) * dden #(6)dxpysqr = (1) * dden #(6)# 回傳 xpysqr = xpy**2dxpy = (2 * xpy) * dxpysqr #(5)# 回傳 xpy = x + ydx = (1) * dxpy #(4)dy = (1) * dxpy #(4)# 回傳 sigx = 1.0 / (1 + math.exp(-x))dx += ((1 - sigx) * sigx) * dsigx # Notice += !! See notes below #(3)# 回傳 num = x + sigydx += (1) * dnum #(2)dsigy = (1) * dnum #(2)# 回傳 sigy = 1.0 / (1 + math.exp(-y))dy += ((1 - sigy) * sigy) * dsigy #(1)# 完成! 嗷~~
需要注意的一些東西:
對前向傳播變數進行緩存:在計算反向傳播時,前向傳播過程中得到的一些中間變數非常有用。在實際操作中,最好代碼實現對於這些中間變數的緩存,這樣在反向傳播的時候也能用上它們。如果這樣做過於困難,也可以(但是浪費計算資源)重新計算它們。
在不同分支的梯度要相加:如果變數x,y在前向傳播的表達式中出現多次,那麼進行反向傳播的時候就要非常小心,使用+=而不是=來累計這些變數的梯度(不然就會造成覆寫)。這是遵循了在微積分中的多元鏈式法則,該法則指出如果變數在線路中分支走向不同的部分,那麼梯度在回傳的時候,就應該進行累加。
回傳流中的模式
一個有趣的現象是在多數情況下,反向傳播中的梯度可以被很直觀地解釋。例如神經網路中最常用的加法、乘法和取最大值這三個門單元,它們在反向傳播過程中的行為都有非常簡單的解釋。先看下面這個例子:
——————————————————————————————————————————
一個展示反向傳播的例子。加法操作將梯度相等地分發給它的輸入。取最大操作將梯度路由給更大的輸入。乘法門拿取輸入激活數據,對它們進行交換,然後乘以梯度。——————————————————————————————————————————
從上例可知:
加法門單元把輸出的梯度相等地分發給它所有的輸入,這一行為與輸入值在前向傳播時的值無關。這是因為加法操作的局部梯度都是簡單的+1,所以所有輸入的梯度實際上就等於輸出的梯度,因為乘以1.0保持不變。上例中,加法門把梯度2.00不變且相等地路由給了兩個輸入。
取最大值門單元對梯度做路由。和加法門不同,取最大值門將梯度轉給其中一個輸入,這個輸入是在前向傳播中值最大的那個輸入。這是因為在取最大值門中,最高值的局部梯度是1.0,其餘的是0。上例中,取最大值門將梯度2.00轉給了z變數,因為z的值比w高,於是w的梯度保持為0。
乘法門單元相對不容易解釋。它的局部梯度就是輸入值,但是是相互交換之後的,然後根據鏈式法則乘以輸出值的梯度。上例中,x的梯度是-4.00x2.00=-8.00。
非直觀影響及其結果。注意一種比較特殊的情況,如果乘法門單元的其中一個輸入非常小,而另一個輸入非常大,那麼乘法門的操作將會不是那麼直觀:它將會把大的梯度分配給小的輸入,把小的梯度分配給大的輸入。在線性分類器中,權重和輸入是進行點積,這說明輸入數據的大小對於權重梯度的大小有影響。例如,在計算過程中對所有輸入數據樣本乘以1000,那麼權重的梯度將會增大1000倍,這樣就必須降低學習率來彌補。這就是為什麼數據預處理關係重大,它即使只是有微小變化,也會產生巨大影響。對於梯度在計算線路中是如何流動的有一個直觀的理解,可以幫助讀者調試網路。
用向量化操作計算梯度
上述內容考慮的都是單個變數情況,但是所有概念都適用於矩陣和向量操作。然而,在操作的時候要注意關注維度和轉置操作。
矩陣相乘的梯度:可能最有技巧的操作是矩陣相乘(也適用於矩陣和向量,向量和向量相乘)的乘法操作:
# 前向傳播W = np.random.randn(5, 10)X = np.random.randn(10, 3)D = W.dot(X)# 假設我們得到了D的梯度dD = np.random.randn(*D.shape) # 和D一樣的尺寸dW = dD.dot(X.T) #.T就是對矩陣進行轉置dX = W.T.dot(dD)
提示:要分析維度!注意不需要去記憶dW和dX的表達,因為它們很容易通過維度推導出來。例如,權重的梯度dW的尺寸肯定和權重矩陣W的尺寸是一樣的,而這又是由X和dD的矩陣乘法決定的(在上面的例子中X和W都是數字不是矩陣)。總有一個方式是能夠讓維度之間能夠對的上的。例如,X的尺寸是[10x3],dD的尺寸是[5x3],如果你想要dW和W的尺寸是[5x10],那就要dD.dot(X.T)。
使用小而具體的例子:有些讀者可能覺得向量化操作的梯度計算比較困難,建議是寫出一個很小很明確的向量化例子,在紙上演算梯度,然後對其一般化,得到一個高效的向量化操作形式。
小結
對梯度的含義有了直觀理解,知道了梯度是如何在網路中反向傳播的,知道了它們是如何與網路的不同部分通信並控制其升高或者降低,並使得最終輸出值更高的。
討論了分段計算在反向傳播的實現中的重要性。應該將函數分成不同的模塊,這樣計算局部梯度相對容易,然後基於鏈式法則將其「鏈」起來。重要的是,不需要把這些表達式寫在紙上然後演算它的完整求導公式,因為實際上並不需要關於輸入變數的梯度的數學公式。只需要將表達式分成不同的可以求導的模塊(模塊可以是矩陣向量的乘法操作,或者取最大值操作,或者加法操作等),然後在反向傳播中一步一步地計算梯度。
在下節課中,將會開始定義神經網路,而反向傳播使我們能高效計算神經網路各個節點關於損失函數的梯度。換句話說,我們現在已經準備好訓練神經網路了,本課程最困難的部分已經過去了!ConvNets相比只是向前走了一小步。
參考文獻
- Automatic differentiation in machine learning: a survey
反向傳播筆記全文翻譯完。
譯者反饋
- 轉載須全文轉載註明原文鏈接,否則保留維權權利;
- 知乎上關於反向傳播的問題很多,希望知友們能點贊幫助本文廣泛傳播為更多人解惑;
- 請知友們通過評論和私信等方式批評指正,貢獻者均會補充提及;
- 感謝知友roach sinai、鄭申海和陳一的細節修改建議。
推薦閱讀:
※墨爾本的master of IT和澳國立的AI如何選擇?
※想了解一些人工智慧與機器學習的資訊信息和行業動態,有什麼比較好的專業期刊和專著書籍可推薦的?
※「冷撲大師」 (Libratus) 是如何利用博弈論打德撲的?
※到底什麼才是人類的靈魂,智慧,驕傲和尊嚴
※Galactic Dependencies依存關係數據集+細粒度語言類型學預測 | 直播預告·PhD Talk