摘要 緩存
本節將對反向傳播進行直觀的理解。反向傳播是利用鏈式法則遞歸計算表達式的梯度的方法。理解反向傳播過程及其精妙之處,對於理解、實現、設計和調試神經網絡很是關鍵。反向求導的核心問題是:給定函數 $f(x)$ ,其中 $x$ 是輸入數據的向量,須要計算函數 $f$ 關於 $x$ 的梯度,也就是 $\nabla f(x)$ 。 網絡
之因此關注上述問題,是由於在神經網絡中 $f$ 對應的是損失函數 $L$,輸入裏面包含訓練數據和神經網絡的權重。舉個例子,損失函數能夠是 Hinge Loss ,其輸入則包含了訓練數據 $(x_i,y_i)_{i=1}^N$,權重 W 和誤差 b 。注意訓練集是給定的(在機器學習中一般都是這樣),而權重是要調節的變量。所以,即便能用反向傳播計算輸入數據 $x_i$ 上的梯度,但在實踐爲了進行參數更新,一般也只計算參數的梯度。 $x_i$ 的梯度有時也是有用的,好比將神經網絡所作的事情可視化便於直觀理解的時候,就能用上。 dom
從簡單表達式入手能夠爲複雜表達式打好符號和規則基礎。先考慮一個簡單的二元乘法函數 $f(x,y) = xy$。對兩個輸入變量分別求偏導數仍是很簡單的: 機器學習
\[f(x,y) = xy \Rightarrow \frac{d f}{d x} = y , \ \ \ \frac{ d f}{d y} = x \]函數
牢記這些導數的意義:函數變量在某個點 $x$ 鄰域內的變化,而導數就是變量變化致使的函數在該方向上的變化率。 學習
\[\frac{df(x)}{dx} = \lim_{h \rightarrow 0} \frac{f(x + h) – f(x)}{h}\]設計
注意等號左邊的分號和等號右邊的分號不一樣,不是表明分數。相反,這個符號表示操做符 $\frac{d}{d x}$ 被應用於函數 $f$ ,並返回一個不一樣的函數(導數)。對於上述公式,能夠認爲 $h$ 值很是小,函數能夠被一條直線近似,而導數就是這條直線的斜率。換句話說,每一個變量的導數指明瞭整個表達式對於該變量的值的敏感程度。好比,若 $x = 4, y = -3$ ,則 $f(x,y) = –12$, $x$ 的導數 $ \frac{d f}{d x} = -3$。這就說明若是將變量 $x$ 的值變大一點,整個表達式的值就會變小(緣由在於負號),並且變小的量是 $x$ 變大的量的三倍。經過從新排列公式能夠看到這一點: 調試
\[f(x + h) = f(x) + h \frac{d f}{d x}\] code
一樣,由於 $\frac{d f}{d y} = 4$ ,能夠知道若是將 $y$ 的值增長 $h$ ,那麼函數的輸出也將增長(緣由在於正號),且增長量是 $4h$。函數關於每一個變量的導數指明瞭整個表達式對於該變量的敏感程度。 blog
如上所述,梯度 $\nabla f $ 是偏導數的向量,因此有
\[\nabla f = \left [\frac{\partial f}{\partial x} ,\frac{\partial f}{\partial y}\right] =[y,x]\]
即便是梯度其實是一個向量,仍然一般使用相似「x上的梯度」的術語,而不是使用如「x的偏導數」的正確說法,緣由是由於前者提及來簡單。也能夠對加法操做求導:
\[ f(x,y) = x+y \Rightarrow \frac{df}{dx} = 1 ,\ \ \frac{df}{dy} = 1 \]
這就是說,不管其值如何, $x,y$ 的導數均爲1。這是有道理的,由於不管增長 $x,y$ 中任一個的值,函數 $f$ 的值都會增長,而且增長的變化率獨立於 $x,y$ 的具體值(狀況和乘法操做不一樣)。取最大值操做也是經常使用的:
\[f(x,y) = \max(x,y) \rightarrow \frac{df}{dx} = \mathbb{I}(x \ge y), \ \ \ \frac{df}{dy} =\mathbb{I}(y \ge x) \]
上式是說,若是該變量比另外一個變量大,那麼梯度是 1 ,反之爲 0 。例如,若 $x=4,y=2$ ,那麼 $\max$ 是 4 ,因此函數對於 $y$ 就不敏感。也就是說,在 $y$ 上增長 $h$, 函數仍是輸出爲 4 ,因此梯度是 0 :由於對於函數輸出是沒有效果的。固然,若是給 $y$ 增長一個很大的量,好比大於 2 ,那麼函數 $f$ 的值就變化了,可是導數並無指明輸入量有巨大變化狀況對於函數的效果,他們只適用於輸入量變化極小時的狀況,由於定義已經指明:$h \rightarrow 0$。
如今考慮更復雜的包含多個函數的複合函數,好比 $f(x,y,z) = (x + y ) \cdot z$ 。雖然這個表達足夠簡單,能夠直接微分,可是在此使用一種有助於讀者直觀理解反向傳播的方法。將公式分紅兩部分:$q = x + y$ 和 $f = q \cdot z$ 。在前面已經介紹過如何對這分開的兩個公式進行計算,由於 $f$ 是 $q$ 和 $z$ 相乘,又由於 $q$ 是 $x$ 加 $y$ 因此:
\[ \frac{\partial f}{\partial q} = z, \ \frac{\partial f}{\partial z} = q ; \ \ \frac{\partial q}{\partial x} = 1, \ \frac{\partial q}{\partial y} = 1. \]
然而,並不須要關心中間量 $q$ 的梯度,由於 $\frac{\partial f}{\partial q }$ 沒有用。相反,函數 $f$ 關於 $x,y,z$ 的梯度纔是須要關注的。鏈式法則指出將這些梯度表達式連接起來的正確方式是相乘,好比:
\[\frac{\partial f}{\partial x} = \frac{\partial f}{\partial q} \cdot \frac{\partial q}{\partial x} \]
在實際操做中,這只是簡單地將兩個梯度數值相乘,示例代碼以下:
# 設置輸入值 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, 因此關於z的梯度是3 dfdq = z # df/dq = z, 因此關於q的梯度是-4 # 如今回傳到q = x + y dfdx = 1.0 * dfdq # dq/dx = 1. 這裏的乘法是由於鏈式法則 dfdy = 1.0 * dfdq # dq/dy = 1
最後獲得變量的梯度 [dfdx, dfdy, dfdz] ,它們告訴咱們函數 $f$ 對於變量 [x, y, z] 的敏感程度。這是一個最簡單的反向傳播。通常會使用一個更簡潔的表達符號,這樣就不用寫 df 了。這就是說,用 dq 來代替 dfdq ,且老是假設梯度是關於最終輸出的。
此次計算能夠被可視化爲以下計算線路圖像:
上圖的真實值計算線路展現了計算的視覺化過程。前向傳播從輸入計算到輸出(綠色),反向傳播從尾部開始,根據鏈式法則遞歸地向前計算梯度(顯示爲紅色),一直到網絡的輸入端。能夠認爲,梯度是從計算鏈路中迴流。
反向傳播是一個優美的局部過程。在整個計算線路圖中,每一個門單元都會獲得一些輸入並當即計算兩個值:
門單元完成這兩件事是徹底獨立的,它不須要知道計算線路中的其餘細節。然而,一旦前向傳播完畢,在反向傳播的過程當中,門單元將最終得到整個網絡的最終輸出值在本身的輸出值上的梯度。鏈式法則指出,門單元應該將回傳的梯度乘以它對其的輸入的局部梯度,從而獲得整個網絡的輸出對該門單元的每一個輸入值的梯度。這裏對於每一個輸入的乘法操做是基於鏈式法則的,該操做讓一個相對獨立的門單元變成複雜計算線路中不可或缺的一部分,這個複雜計算線路能夠是神經網絡等。
下面經過例子來對這一過程進行理解。加法門收到了輸入 [-2, 5] ,計算輸出是 3 。既然這個門是加法操做,那麼對於兩個輸入的局部梯度都是 +1 。網絡的其他部分計算出最終值爲 –12 。在反向傳播時將遞歸地使用鏈式法則,算到加法門(是乘法門的輸入)的時候,知道加法門的輸出的梯度是 –4 。若是網絡若是想要輸出值更高,那麼能夠認爲它會想要加法門的輸出更小一點(由於負號),並且還有一個 4 的倍數。繼續遞歸併對梯度使用鏈式法則,加法門拿到梯度,而後把這個梯度分別乘到每一個輸入值的局部梯度(就是讓 –4 乘以 x 和 y 的局部梯度, x 和 y 的局部梯度都是 1 ,因此最終都是 –4 )。能夠看到獲得了想要的效果:若是 x, y 減少(它們的梯度爲負),那麼加法門的輸出值減少,這會讓乘法門的輸出值增大。
所以,反向傳播能夠看作是門單元之間在經過梯度信號相互通訊,只要讓它們的輸入沿着梯度方向變化,不管它們本身的輸出值在何種程度上升或下降,都是爲了讓整個網絡的輸出值更高。
上面介紹的門是相對隨意的。任何可微分的函數均可以看作門。能夠將多個門組合成一個門,也能夠根據須要將一個函數分拆成多個門。如今看看一個表達式:
\[f(w,x) = \frac{1}{1 + e^{-(w_0x_0+ w_1 x_1 + b)}}\]
在後面的課程中能夠看到,這個表達式描述了一個含輸入 x 和權重 w 的 2 維的神經元,該神經元使用了 sigmoid 激活函數。可是如今只是看作是一個簡單的輸入爲 x 和 w ,輸出爲一個數字的函數。這個函數是由多個門組成的。除了上文介紹的加法門,乘法門,取最大值門,還有下面這4種:
\begin{aligned}
f(x) &= \frac{1}{x} \rightarrow \frac{df}{dx} = -\frac{1}{x^2}\\
f_c(x) &= c + x \rightarrow \frac{df}{dx} = 1\\
f(x) &= e^x \rightarrow \frac{df}{dx} = e^x\\
f_a(x) &= ax \rightarrow \frac{df}{dx} = a
\end{aligned}
其中,函數 $f_c$ 使用對輸入值進行了常量 $c$ 的平移,$f_a$ 將輸入值擴大了常量 $a$ 倍。它們是加法和乘法的特例,可是這裏將其看作一元門單元,由於確實須要計算常量 $c,a$ 的梯度。整個計算線路以下:
使用sigmoid激活函數的2維神經元的例子。輸入是 [x0, x1] ,可學習的權重是 [w0, w1, w2] 。一下子會看見,這個神經元對輸入數據作點積運算,而後其激活數據被sigmoid函數擠壓到 0 到 1 之間。
在上面的例子中能夠看見一個函數操做的長鏈條,鏈條上的門都對 $w$ 和 $x$ 的點積結果進行操做。該函數被稱爲 sigmoid函數 $\sigma(x)$ 。sigmoid 函數及其導數以下:
\begin{aligned}
\sigma(x) &= \frac{1}{1+e^{-x}} \\
\frac{d \sigma(x)}{dx} &= \sigma(x)(1-\sigma(x))
\end{aligned}
能夠看到梯度計算簡單了不少。舉個例子,sigmoid 表達式輸入爲 1.0 ,則在前向傳播中計算出輸出爲 0.73 。根據上面的公式,局部梯度爲 (1-0.73) $\times$ 0.73 $\sim$ 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] # 回傳到x dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # 回傳到w # 完成!獲得輸入的梯度
實現提示:分段反向傳播。上面的代碼展現了在實際操做中,爲了使反向傳播過程更加簡潔,把向前傳播分紅不一樣的階段將是頗有幫助的。好比咱們建立了一箇中間變量 dot ,它裝着 w 和 x 的點乘結果。在反向傳播的時,就能夠(反向地)計算出裝着 w 和 x 等的梯度的對應的變量(好比 ddot,dx 和 dw )。
本節的要點就是展現反向傳播的細節過程,以及前向傳播過程當中,哪些函數能夠被組合成門,從而能夠進行簡化。知道表達式中哪部分的局部梯度計算比較簡潔很是有用,這樣他們能夠「鏈」在一塊兒,讓代碼量更少,效率更高。
看另外一個例子。假設有以下函數:
\[f(x,y) =\frac{x + \sigma(y)}{\sigma(x) + (x+y)^2}\]
首先要說的是,這個函數徹底沒用,讀者是不會用到它來進行梯度計算的,這裏只是用來做爲實踐反向傳播的一個例子,須要強調的是,若是對 x 或 y 進行微分運算,運算結束後會獲得一個巨大而複雜的表達式。然而作如此複雜的運算實際上並沒有必要,由於咱們不須要一個明確的函數來計算梯度,只需知道如何使用反向傳播計算梯度便可。下面是構建前向傳播的代碼模式:
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 * invden dnum = invden # 分子的梯度 #(8) dinvden = num #(8) # 回傳 invden = 1.0 / den dden = (-1.0 / (den**2)) * dinvden #(7) # 回傳 den = sigx + xpysqr dsigx = (1) * dden #(6) dxpysqr = (1) * dden #(6) # 回傳 xpysqr = xpy**2 dxpy = (2 * xpy) * dxpysqr #(5) # 回傳 xpy = x + y dx = (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 + sigy dx += (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.00 $\times$ 2.00= -8.00 。
非直觀影響及其結果。注意一種比較特殊的狀況,若是乘法門單元的其中一個輸入很是小,而另外一個輸入很是大,那麼乘法門的操做將會不是那麼直觀:它將會把大的梯度分配給小的輸入,把小的梯度分配給大的輸入。在線性分類器中,權重和輸入是進行點積 $w^Tx_i$ ,這說明輸入數據的大小對於權重梯度的大小有影響。例如,在計算過程當中對全部輸入數據樣本
乘以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 的尺寸是 [10 $\times$3] ,dD 的尺寸是 [5 $times$ 3] ,若是你想要 dW 和 W 的尺寸是 [5 $times$ 10],那就要 dD.dot(X.T)。
使用小而具體的例子:有些讀者可能以爲向量化操做的梯度計算比較困難,建議是寫出一個很小很明確的向量化例子,在紙上演算梯度,而後對其通常化,獲得一個高效的向量化操做形式。
對梯度的含義有了直觀理解,知道了梯度是如何在網絡中反向傳播的,知道了它們是如何與網絡的不一樣部分通訊並控制其升高或者下降,並使得最終輸出值更高的。
討論了分段計算在反向傳播的實現中的重要性。應該將函數分紅不一樣的模塊,這樣計算局部梯度相對容易,而後基於鏈式法則將其「鏈」起來。重要的是,不須要把這些表達式寫在紙上而後演算它的完整求導公式,由於實際上並不須要關於輸入變量的梯度的數學公式。只須要將表達式分紅不一樣的能夠求導的模塊(模塊能夠是矩陣向量的乘法操做,或者取最大值操做,或者加法操做等),而後在反向傳播中一步一步地計算梯度。
在下節課中,將會開始定義神經網絡,而反向傳播使咱們能高效計算神經網絡各個節點關於損失函數的梯度。換句話說,咱們如今已經準備好訓練神經網絡了,本課程最困難的部分已通過去了!ConvNets 相比只是向前走了一小步。