第二章 反向传播算法如何工作的?
更好的公式展現(xiàn)請去gitbook 連接閱讀
在上一章,我們看到了神經(jīng)網(wǎng)絡如何使用梯度下降算法來學習他們自身的權重和偏差。但是,這里還留下了一個問題:我們并沒有討論如何計算代價函數(shù)的梯度。這是很大的缺失!在本章,我們會解釋計算這些梯度的快速算法,也就是反向傳播。
反向傳播算法最初在 1970 年代被發(fā)現(xiàn),但是這個算法的重要性直到 David Rumelhart、Geoffrey Hinton 和Ronald Williams 的 1986年的論文 中才被真正認可。這篇論文描述了對一些神經(jīng)網(wǎng)絡反向傳播要比傳統(tǒng)的方法更快,這使得使用神經(jīng)網(wǎng)絡來解決之前無法完成的問題變得可行。現(xiàn)在,反向傳播算法已經(jīng)是神經(jīng)網(wǎng)絡學習的重要組成部分了。
本章在全書的范圍內(nèi)要比其他章節(jié)包含更多的數(shù)學內(nèi)容。如果你不是對數(shù)學特別感興趣,那么可以跳過本章,將反向傳播當成一個黑盒,忽略其中的細節(jié)。那么為何要研究這些細節(jié)呢?
答案當然是理解。反向傳播的核心是對代價函數(shù) $$C$$ 關于 $$w$$ (或者 $$b$$)的偏導數(shù) $$\partial C/\partial w$$ 的計算表示。該表示告訴我們在權重和偏差發(fā)生改變時,代價函數(shù)變化的快慢。盡管表達式會有點復雜,不過里面也包含一種美感,就是每個元素其實是擁有一種自然的直覺上的解釋。所以反向傳播不僅僅是一種學習的快速算法。實際上它還告訴我們一些細節(jié)的關于權重和偏差的改變影響整個網(wǎng)絡行為方面的洞察。因此,這也是學習反向傳播細節(jié)的重要價值所在。
如上面所說,如果你想要粗覽本章,或者直接跳到下一章,都是可以的。剩下的內(nèi)容即使你是把反向傳播看做黑盒也是可以掌握的。當然,后面章節(jié)中也會有部分內(nèi)容涉及本章的結論,所以會常常給出本章的參考。不過對這些知識點,就算你對推導的細節(jié)不太清楚你還是應該要理解主要的結論的。
熱身:神經(jīng)網(wǎng)絡中使用矩陣快速計算輸出的觀點
在討論反向傳播前,我們先熟悉一下基于矩陣的算法來計算網(wǎng)絡的輸出。事實上,我們在上一章的最后已經(jīng)能夠看到這個算法了,但是我在那里很快地略過了,所以現(xiàn)在讓我們仔細討論一下。特別地,這樣能夠用相似的場景幫助我們熟悉在反向傳播中使用的矩陣表示。
我們首先給出網(wǎng)絡中權重的清晰定義。我們使用 $$w_{jk}^l$$ 表示從 $$(l-1)^{th}$$ 層的 $$k^{th}$$ 個神經(jīng)元到 $$(l)^{th}$$ 層的 $$l^{th}$$ 個神經(jīng)元的鏈接上的權重。例如,下圖給出了第二隱藏層的第四個神經(jīng)元到第三隱藏層的第二個神經(jīng)元的鏈接上的權重:
這樣的表示粗看比較奇怪,需要花一點時間消化。但是,后面你會發(fā)現(xiàn)這樣的表示會比較方便也很自然。奇怪的一點其實是下標 $$j$$ 和 $$k$$ 的順序。你可能覺得反過來更加合理。但我接下來會告訴你為什么要這樣做。
我們對網(wǎng)絡偏差和激活值也會使用類似的表示。顯式地,我們使用 $$b_J^l$$ 表示在 $$l^{th}$$ 層 $$j^{th}$$ 個神經(jīng)元的偏差,使用 $$a_j^l$$ 表示 $$l^{th}$$ 層 $$j^{th}$$ 個神經(jīng)元的激活值。下面的圖清楚地解釋了這樣表示的含義:
有了這些表示, $$l^{th}$$ 層的 $$j^{th}$$ 個神經(jīng)元的激活值 $$a_j^l$$ 就和 $$l^{th}$$ 層關聯(lián)起來了(對比公式(4) 和上一章的討論)
其中求和是在 $$(l-1)^{th}$$ 層的所有神經(jīng)元上進行的。為了用矩陣的形式重寫這個表達式,我們對每一層 $$l$$ 都定義一個權重矩陣 $$w^l$$,在 $$j^{th}$$ 行第$$k^{th}$$ 列的元素是 $$w_{jk}^l$$。類似的,對每一層 $$l$$,定義一個偏差向量,$$b^l$$。你已經(jīng)猜到這些如何工作了——偏差向量的每個元素其實就是前面給出的 $$b_j^l$$,每個元素對應于 $$l^{th}$$ 層的每個神經(jīng)元。最后,我們定義激活向量 $$a^l$$,其元素是那些激活值 $$a_j^l$$。
最后我們需要引入向量化函數(shù)(如 $$\sigma$$)來按照矩陣形式重寫公式(23) 。在上一章,我們其實已經(jīng)碰到向量化了,其含義就是作用函數(shù)(如 $$\sigma$$)到向量 $$v$$ 中的每個元素。我們使用 $$\sigma(v)$$ 表示這種按元素進行的函數(shù)作用。所以,$$\sigma(v)$$ 的每個元素其實滿足 $$\sigma(v)_j = \sigma(v_j)$$。給個例子,如果我們的作用函數(shù)是 $$f(x) = x^2$$,那么向量化的 $$f$$ 的函數(shù)作用就起到下面的效果:
也就是說,向量化的 $$f$$ 僅僅是對向量的每個元素進行了平方運算。
了解了這些表示,方程(23)就可以寫成下面的這種美妙而簡潔的向量形式了:
這個表達式給出了一種更加全局的思考每層的激活值和前一層的關聯(lián)方式:我們僅僅用權重矩陣作用在激活值上,然后加上一個偏差向量,最后作用 $$\sigma$$ 函數(shù)。
其實,這就是讓我們使用之前的矩陣下標 $$w_{jk}^l$$ 表示的初因。如果我們使用 $$j$$ 來索引輸入神經(jīng)元,$$k$$ 索引輸出神經(jīng)元,那么在方程(25)中我們需要將這里的矩陣換做其轉(zhuǎn)置。這只是一個小小的困惑的改變,這會使得我們無法自然地講出(思考)“應用權重矩陣到激活值上”這樣的簡單的表達。
這種全局的觀點相比神經(jīng)元層面的觀點常常更加簡明(沒有更多的索引下標了!)其實可以看做是在保留清晰認識的前提下逃離下標困境的方法。在實踐中,表達式同樣很有用,因為大多數(shù)矩陣庫提供了實現(xiàn)矩陣乘法、向量加法和向量化的快速方法。實際上,上一章的代碼其實已經(jīng)隱式使用了使用這種表達式來計算網(wǎng)絡行為。
在使用方程(25)計算 $$a^l$$ 時,我們計算了中間量 $$z^l \equiv w^la^{l-1} + b^l$$ 。這個量其實是非常有用的:我們稱 $$z^l$$ 為 $$l$$ 層的帶權輸入。在本章后面,我們會大量用到這個量。方程(25)有時候會寫作 $$a^l = \sigma(z^l)$$。同樣要指出的是 $$z_l$$ 的每個元素是 $$z_j^l = \sum_k w_{jk}^l a_k^{l-1} + b_j^l$$,其實 $$z_j^l$$ 就是第 $$l$$ 層第 $$j$$ 個神經(jīng)元的激活函數(shù)帶權輸入。
關于代價函數(shù)的兩個假設
反向傳播的目標是計算代價函數(shù) $$C$$ 分別關于 $$w$$ 和 $$b$$ 的偏導數(shù) $$\partial C/\partial w$$ 和 $$\partial C/\partial b$$。為了讓反向傳播可行,我們需要做出關于代價函數(shù)的兩個主要假設。在給出這兩個假設之前,我們先看看具體的一個代價函數(shù)。我們會使用上一章使用的二次代價函數(shù)。按照上一節(jié)給出的表示,二次代價函數(shù)有下列形式:
其中 $$n$$ 是訓練樣本的總數(shù);求和是在所有的訓練樣本 $$x$$ 上進行的;$$y = y(x)$$ 是對應的目標輸出;$$L$$ 表示網(wǎng)絡的層數(shù);$$a^L = a^L(x)$$ 是當輸入是 $$x$$ 時的網(wǎng)絡輸出的激活值向量。
好了,為了應用反向傳播,我們需要對代價函數(shù)做出什么樣的前提假設呢?第一個假設就是代價函數(shù)可以被寫成一個 在每個訓練樣本 $$x$$ 上的代價函數(shù) $$C_x$$ 的均值 $$C=\frac{1}{n} \sum_x C_x$$。這是關于二次代價函數(shù)的例子,其中對每個獨立的訓練樣本其代價是 $$C_x = \frac{1}{2} ||y-a^L||^2$$。這個假設對書中提到的其他任何一個代價函數(shù)也都是必須滿足的。
需要這個假設的原因是反向傳播實際上是對一個獨立的訓練樣本計算了 $$\partial C_x/\partial w$$ 和 $$\partial C_x/\partial b$$。然后我們通過在所有訓練樣本上進行平均化獲得 $$\partial C/\partial w$$ 和 $$\partial C/\partial b$$。實際上,有了這個假設,我們會認為訓練樣本 $$x$$ 已經(jīng)被固定住了,丟掉了其下標,將代價函數(shù) $$C_x$$ 看做 $$C$$。最終我們會把下標加上,現(xiàn)在為了簡化表示其實沒有這個必要。
第二個假設就是代價可以寫成神經(jīng)網(wǎng)絡輸出的函數(shù):
例如,二次代價函數(shù)滿足這個要求,因為對于一個單獨的訓練樣本 $$x$$ 其二次代價函數(shù)可以寫作:
這是輸出的激活值的函數(shù)。當然,這個代價函數(shù)同樣還依賴于目標輸出 $$y$$。記住,輸入的訓練樣本 $$x$$ 是固定的,所以輸出同樣是一個固定的參數(shù)。所以說,并不是可以隨意改變權重和偏差的,也就是說,這不是神經(jīng)網(wǎng)絡學習的對象。所以,將 $$C$$ 看成僅有輸出激活值 $$a^L$$ 的函數(shù)才是合理的,而 $$y$$ 僅僅是幫助定義函數(shù)的參數(shù)而已。
Hadamard 乘積
反向傳播算法基于常規(guī)的線性代數(shù)運算——諸如向量加法,向量矩陣乘法等。但是有一個運算不大常見。特別地,假設 $$s $$和 $$t$$ 是兩個同樣維度的向量。那么我們使用 $$s\odot t$$ 來表示按元素的乘積。所以 $$s\odot t$$ 的元素就是 $$(s\odot t)_j = s_j t_j$$。給個例子,
這種類型的按元素乘法有時候被稱為 Hadamard 乘積或者 Schur 乘積。我們這里取前者。好的矩陣庫通常會提供 Hadamard 乘積的快速實現(xiàn),在實現(xiàn)反向傳播的時候用起來很方便。
反向傳播的四個基本方程
反向傳播其實是對權重和偏差變化影響代價函數(shù)過程的理解。最終極的含義其實就是計算偏導數(shù) $$\partial C/\partial w_{jk}^l$$ 和 $$\partial C/\partial b_j^l$$。但是為了計算這些值,我們首先引入一個中間量,$$\delta_j^l$$,這個我們稱為在 $$l^{th}$$ 層第 $$j^{th}$$ 個神經(jīng)元上的誤差(error)。
反向傳播將給出計算誤差 $$\delta_j^l$$ 的流程,然后將其關聯(lián)到計算 $$\partial C/\partial w_{jk}^l$$ 和 $$\partial C/\partial b_j^l$$ 上。
為了理解誤差是如何定義的,假設在神經(jīng)網(wǎng)絡上有一個惡魔:
這個小精靈在 $$l$$ 層的第 $$j^{th}$$ 個神經(jīng)元上。當輸入進來時,精靈對神經(jīng)元的操作進行攪局。他會增加很小的變化 $$\Delta z_j^l$$ 在神經(jīng)元的帶權輸入上,使得神經(jīng)元輸出由 $$\sigma(z_j^l)$$ 變成 $$\sigma(z_j^l + \Delta z_j^l)$$。這個變化會向網(wǎng)絡后面的層進行傳播,最終導致整個代價函數(shù)產(chǎn)生 $$\frac{\partial C}{\partial z_j^l} \Delta z_j^l$$ 的改變。
現(xiàn)在,這個精靈變好了,試著幫助你來優(yōu)化代價函數(shù),他們試著找到可以讓代價更小的 $$\Delta z_j^l$$。假設 $$\frac{\partial C}{\partial z_j^l}$$ 有一個很大的值(或正或負)。那么這個精靈可以降低代價通過選擇與 $$\frac{\partial C}{\partial z_j^l}$$ 相反符號的 $$\Delta z_j^l$$ 。相反,如果$$\frac{\partial C}{\partial z_j^l}$$ 接近 $$0$$,那么精靈并不能通過擾動帶權輸入 $$z_j^l$$ 來改變太多代價函數(shù)。在小精靈看來,這時候神經(jīng)元已經(jīng)很接近最優(yōu)了。
這里需要注意的是,只有在 $$\Delta z_j^l$$ 很小的時候才能夠滿足。我們需要假設小精靈只能進行微小的調(diào)整。
所以這里有一種啟發(fā)式的認識,$$\frac{\partial C}{\partial z_j^l}$$ 是神經(jīng)元的誤差的度量。
按照上面的描述,我們定義 $$l$$ 層的第 $$j^{th}$$ 個神經(jīng)元上的誤差 $$\delta_j^l$$ 為:
按照我們通常的慣例,我們使用 $$\delta^l$$ 表示關聯(lián)于 $$l$$ 層的誤差向量。反向傳播會提供給我們一種計算每層的 $$\delta^l$$ 的方法,然后將這些誤差和最終我們需要的量 $$\partial C/\partial w_{jk}^l$$ 和 $$\partial C/\partial b_j^l$$聯(lián)系起來。
你可能會想知道為何精靈在改變帶權輸入 $$z_j^l$$。肯定想象精靈改變輸出激活 $$a_j^l$$ 更加自然,然后就使用 $$\frac{\partial C}{\partial a_j^l}$$ 作為度量誤差的方法了。 實際上,如果你這樣做的話,其實和下面要討論的差不同。但是看起來,前面的方法會讓反向傳播在代數(shù)運算上變得比較復雜。所以我們堅持使用 $$\delta_j^l = \partial C / \partial z_j^l$$ 作為誤差的度量。
在分類問題中,誤差有時候會用作分類的錯誤率。如果神經(jīng)網(wǎng)絡正確分類了 96.0% 的數(shù)字,那么其誤差是 4.0%。很明顯,這和我們上面提及的誤差的差別非常大了。在實際應用中,區(qū)分這兩種含義是非常容易的。
解決方案:反向傳播基于四個基本方程。這些方程給我們一種計算誤差和代價函數(shù)梯度的方法。我列出這四個方程。但是需要注意:你不需要一下子能夠同時理解這些公式。因為過于龐大的期望可能會導致失望。實際上,反向傳播方程內(nèi)容很多,完全理解這些需要花費充分的時間和耐心,需要一步一步地深入理解。而好的消息是,這樣的付出回報巨大。所以本節(jié)對這些內(nèi)容的討論僅僅是一個幫助你正確掌握這些公式的起步。
下面簡要介紹我們的探討這些公式的計劃:首先給出這些公式的簡短證明以解釋他們的正確性;然后以偽代碼的方式給出這些公式的算法形式,并展示這些偽代碼如何轉(zhuǎn)化成真實的可執(zhí)行的 python 代碼;在本章的最后,我們會發(fā)展處一個關于反向傳播公式含義的直覺圖景,以及人們?nèi)绾文軌驈牧汩_始發(fā)現(xiàn)這個規(guī)律。按照此法,我們會不斷地提及這四個基本方程,隨著你對這些方程理解的加深,他們會看起來更加舒服,甚至是美妙和自然的。
輸出層誤差的方程,$$\delta^L$$:每個元素定義如下:
這是一個非常自然的表達式。右式第一個項 $$\partial C/\partial a_j^L$$ 表示代價隨著 $$j^{th}$$ 輸出激活值的變化而變化的速度。假如 $$C$$ 不太依賴一個特定的輸出神經(jīng)元 $$j$$,那么$$\delta_j^L$$ 就會很小,這也是我們想要的效果。右式第二項 $$\sigma'(z_j^L)$$ 刻畫了在 $$z_j^L$$ 處激活函數(shù) $$\sigma$$ 變化的速度。
注意到在 BP1 中的每個部分都是很好計算的。特別地,我們在前向傳播計算網(wǎng)絡行為時已經(jīng)計算過 $$z_j^L$$,這僅僅需要一點點額外工作就可以計算 $$\sigma'(z_j^L)$$。當然 $$\partial C/\partial a_j^L$$ 依賴于代價函數(shù)的形式。然而,給定了代價函數(shù),計算$$\partial C/\partial a_j^L$$就沒有什么大問題了。例如,如果我們使用二次函數(shù),那么 $$C = \frac{1}{2} \sum_j(y_j-a_j)^2$$,所以 $$\partial C/\partial a_j^L = (a_j - y_j)$$,這其實很容易計算。
方程(BP1)對 $$\delta^L$$ 來說是個按部分構成的表達式。這是一個非常好的表達式,但不是我們期望的用矩陣表示的形式。但是,重寫方程其實很簡單,
這里 $$\nabla_a C$$ 被定義成一個向量,其元素師偏導數(shù) $$\partial C/\partial a_j^L$$。你可以將其看成 $$C$$ 關于輸出激活值的改變速度。方程(BP1)和方程(BP1a)的等價也是顯而易見的,所以現(xiàn)在開始,我們會交替地使用這兩個方程。舉個例子,在二次代價函數(shù)時,我們有 $$\nabla_a C = (a^L - y)$$,所以(BP1)的整個矩陣形式就變成
如你所見,這個方程中的每個項都有一個很好的向量形式,所以也可以很方便地使用像 Numpy 這樣的矩陣庫進行計算了。
使用下一層的誤差 $$\delta^{l+1}$$ 來表示當前層的誤差 $$\delta_l$$:特別地,
其中 $$(w^{l+1})^T$$ 是 $$(l+1)^{th}$$ 權重矩陣 $$w^{l+1}$$ 的轉(zhuǎn)置。這其實可以很直覺地看做是后在 $$l^{th}$$ 層的輸出的誤差的反向傳播,給出了某種關于誤差的度量方式。然后,我們進行 Hadamard 乘積運算 $$\odot \sigma'(z^l)$$。這會讓誤差通過 $$l$$ 層的激活函數(shù)反向傳遞回來并給出在第 $$l$$ 層的帶權輸入的誤差 $$\delta$$。
通過組合(BP1)和(BP2),我們可以計算任何層的誤差了。首先使用(BP1)計算$$\delta^l$$,然后應用方程(BP2)來計算$$\delta^{L-1}$$,然后不斷作用(BP2),一步一步地反向傳播完整個網(wǎng)絡。
代價函數(shù)關于網(wǎng)絡中任意偏差的改變率:就是
這其實是,誤差 $$\delta_j^l$$ 和偏導數(shù)值 $$\partial C/\partial b_j^l$$完全一致。這是很好的性質(zhì),因為(BP1)和(BP2)已經(jīng)告訴我們?nèi)绾斡嬎?$$\delta_j^l$$。所以就可以將(BP3)簡記為
其中 $$\delta$$ 和偏差 $$b$$ 都是針對同一個神經(jīng)元。
代價函數(shù)關于任何一個權重的改變率:特別地,
這告訴我們?nèi)绾斡嬎闫珜?shù) $$\partial C/\partial w_{jk}^l$$,其中 $$\delta^l$$ $$a^{l-1}$$ 這些量我們都已經(jīng)知道如何計算了。方程也可以寫成下面少下標的表示:
其中 $$a_{in}$$ 是輸出給 $$w$$ 產(chǎn)生的神經(jīng)元的輸入和 $$\delta_{out}$$是來自 $$w$$ 的神經(jīng)元輸出的誤差。放大看看權重 w,還有兩個由這個鏈接相連的神經(jīng)元,我們給出一幅圖如下:
方程(32)的一個結論就是當激活值很小,梯度 $$\partial C/\partial w$$ 也會變得很小。這樣,我們就說權重學習緩慢,表示在梯度下降的時候,這個權重不會改變太多。換言之,(BP4)的后果就是來自很低的激活值神經(jīng)元的權重學習會非常緩慢。
這四個公式同樣還有很多觀察。讓我們看看(BP1)中的項 $$\sigma'(z_k^l)$$。回憶一下上一章的 sigmoid 函數(shù)圖像,當函數(shù)值接近 $$0$$ 或者 $$1$$ 的時候圖像非常平。這就使得在這些位置的導數(shù)接近于 $$0$$.所以如果輸出神經(jīng)元處于或者低激活值或者高激活值時,最終層的權重學習緩慢。這樣的情形,我們常常稱輸出神經(jīng)元已經(jīng)飽和了,并且,權重學習也會終止(或者學習非常緩慢)。類似的結果對于輸出神經(jīng)元的偏差也是成立的。
針對前面的層,我們也有類似的觀點。特別地,注意在(BP2)中的項 $$\sigma'(z^l)$$。這表示 $$\delta_j^l$$ 很可能變小如果神經(jīng)元已經(jīng)接近飽和。這就導致任何輸入進一個飽和的神經(jīng)元的權重學習緩慢。
如果 $$(w^{l+1})^T \delta^{l+1}$$ 擁有足夠大的量能夠補償 $$\sigma'(z_k^l)$$ 的話,這里的推導就不能成立了。但是我們上面是常見的情形。
總結一下,我們已經(jīng)學習到權重學習緩慢如果輸入神經(jīng)元激活值很低,或者輸出神經(jīng)元已經(jīng)飽和了(過高或者過低的激活值)。
這些觀測其實也是不非常令人驚奇的。不過,他們幫助我們完善了關于神經(jīng)網(wǎng)絡學習的背后的思維模型。而且,我們可以將這種推斷方式進行推廣。四個基本方程也其實對任何的激活函數(shù)都是成立的(證明中也可以看到,其實推斷本身不依賴于任何具體的代價函數(shù))所以,我們可以使用這些方程來設計有特定屬性的激活函數(shù)。我們這里給個例子,假設我們準備選擇一個(non-sigmoid)的激活函數(shù) $$\sigma$$ 使得 $$\sigma'$$ 總是正數(shù)。這會防止在原始的 sigmoid 神經(jīng)元飽和時學習速度的下降的情況出現(xiàn)。在本書的后面,我們會見到這種類型的對激活函數(shù)的改變。時時回顧這四個方程可以幫助解釋為何需要有這些嘗試,以及嘗試帶來的影響。
問題
另一種反向傳播方程的表示方式:我已經(jīng)給出了使用了 Hadamard 乘積的反向傳播的公式。如果你對這種特殊的乘積不熟悉,可能會有一些困惑。下面還有一種表示方式,那就是基于傳統(tǒng)的矩陣乘法,某些讀者可能會覺得很有啟發(fā)。(1) 證明 (BP1) 可以寫成
其中 $$\Sigma'(z^L)$$ 是一個方陣,其對角線的元素是 $$\sigma'(z_j^L)$$,其他的元素均是 $$0$$。注意,這個矩陣通過一般的矩陣乘法作用在 $$\nabla_a C$$ 上。(2) 證明(BP2) 可以寫成
(3) 結合(1)和(2) 證明
對那些習慣于這種形式的矩陣乘法的讀者,(BP1) (BP2) 應該更加容易理解。而我們堅持使用 Hadamard 乘積的原因在于其更快的數(shù)值實現(xiàn)。
四個基本方程的證明(可選)
我們現(xiàn)在證明這四個方程。所有這些都是多元微積分的鏈式法則的推論。如果你熟悉鏈式法則,那么我鼓勵你在讀之前自己證明一番。
練習
- 證明方程 (BP3) 和 (BP4)
這樣我們就完成了反向傳播四個基本公式的證明。證明本身看起來復雜。但是實際上就是細心地應用鏈式法則。我們可以將反向傳播看成是一種系統(tǒng)性地應用多元微積分中的鏈式法則來計算代價函數(shù)的梯度的方式。這些就是反向傳播理論上的內(nèi)容——剩下的是實現(xiàn)細節(jié)。
反向傳播算法
反向傳播方程給出了一種計算代價函數(shù)梯度的方法。讓我們顯式地用算法描述出來:
看看這個算法,你可以看到為何它被稱作反向傳播。我們從最后一層開始向后計算誤差向量 $$\delta^l$$。這看起來有點奇怪,為何要從后面開始。但是如果你認真思考反向傳播的證明,這種反向移動其實是代價函數(shù)是網(wǎng)絡輸出的函數(shù)的后果。為了理解代價隨前面層的權重和偏差變化的規(guī)律,我們需要重復作用鏈式法則,反向地獲得需要的表達式。
練習
- 使用單個修正的神經(jīng)元的反向傳播
假設我們改變一個前向傳播網(wǎng)絡中的單個神經(jīng)元,使得那個神經(jīng)元的輸出是 $$f(\sum_j w_jx_j + b)$$,其中 $$f$$ 是和 sigmoid 函數(shù)不同的某一函數(shù)。我們?nèi)绾握{(diào)整反向傳播算法? - 線性神經(jīng)元上的反向傳播
假設我們將非線性神經(jīng)元替換為 $$\sigma(z) = z$$。重寫反向傳播算法。
正如我們上面所講的,反向傳播算法對一個訓練樣本計算代價函數(shù)的梯度,$$C=C_x$$。在實踐中,通常將反向傳播算法和諸如隨機梯度下降這樣的學習算法進行組合使用,我們會對許多訓練樣本計算對應的梯度。特別地,給定一個大小為 $$m$$ 的 minibatch,下面的算法應用一步梯度下降學習在這個 minibatch 上:
- 前向傳播:對每個 $$l=2,3,...,L$$ 計算 $$z^{x,l} = w^la^{x,l-1} + b^l$$ 和 $$a^{x,l} = \sigma(z^{x,l})$$
- 輸出誤差 $$\delta^{x,L}$$:計算向量 $$\delta^{x,L} = \nabla_a C_x \odot \sigma'(z^{x,L})$$
- 反向傳播誤差:對每個 $$l=L-1, L-2, ..., 2$$ 計算 $$\delta^{x,l} = ((w^{l+1})^T\delta^{x,l+1})\odot \sigma'(z^{x,l})$$
當然,在實踐中實現(xiàn)隨機梯度下降,我們還需要一個產(chǎn)生訓練樣本 minibatch 的循環(huán),還有就是訓練次數(shù)的循環(huán)。這里我們先省略了。
代碼
理解了抽象的反向傳播的理論知識,我們現(xiàn)在就可以學習上一章中使用的實現(xiàn)反向傳播的代碼了。回想上一章的代碼,需要研究的是在 Network 類中的update_mini_batch 和 backprop 方法。這些方法的代碼其實是我們上面的算法描述的直接翻版。特別地,update_mini_batch 方法通過計算當前mini_batch 中的訓練樣本對 Network 的權重和偏差進行了更新:
class Network(object): ...def update_mini_batch(self, mini_batch, eta):"""Update the network's weights and biases by applyinggradient descent using backpropagation to a single mini batch.The "mini_batch" is a list of tuples "(x, y)", and "eta"is the learning rate."""nabla_b = [np.zeros(b.shape) for b in self.biases]nabla_w = [np.zeros(w.shape) for w in self.weights]for x, y in mini_batch:delta_nabla_b, delta_nabla_w = self.backprop(x, y)nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]self.weights = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.weights, nabla_w)]self.biases = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.biases, nabla_b)]主要工作其實是在 delta_nabla_b, delta_nabla_w = self.backprop(x, y) 這里完成的,調(diào)用了backprop 方法計算出了偏導數(shù),$$\partial C_x/\partial b_j^l$$ 和 $$\partial C_x/\partial w_{jk}^l$$。反向傳播方法跟上一節(jié)的算法基本一致。這里只有個小小的差異——我們使用一個略微不同的方式來索引神經(jīng)網(wǎng)絡的層。這個改變其實是為了 Python 的特性——負值索引的使用能夠讓我們從列表的最后往前遍歷,如l[-3] 其實是列表中的倒數(shù)第三個元素。下面 backprop 的代碼,使用了一些用來計算 $$\sigma$$、導數(shù) $$\sigma'$$ 及代價函數(shù)的導數(shù)幫助函數(shù)。所以理解了這些,我們就完全可以掌握所有的代碼了。如果某些東西讓你困惑,你可能需要參考代碼的原始描述
class Network(object): ...def backprop(self, x, y):"""Return a tuple "(nabla_b, nabla_w)" representing thegradient for the cost function C_x. "nabla_b" and"nabla_w" are layer-by-layer lists of numpy arrays, similarto "self.biases" and "self.weights"."""nabla_b = [np.zeros(b.shape) for b in self.biases]nabla_w = [np.zeros(w.shape) for w in self.weights]# feedforwardactivation = xactivations = [x] # list to store all the activations, layer by layerzs = [] # list to store all the z vectors, layer by layerfor b, w in zip(self.biases, self.weights):z = np.dot(w, activation)+bzs.append(z)activation = sigmoid(z)activations.append(activation)# backward passdelta = self.cost_derivative(activations[-1], y) * \sigmoid_prime(zs[-1])nabla_b[-1] = deltanabla_w[-1] = np.dot(delta, activations[-2].transpose())# Note that the variable l in the loop below is used a little# differently to the notation in Chapter 2 of the book. Here,# l = 1 means the last layer of neurons, l = 2 is the# second-last layer, and so on. It's a renumbering of the# scheme in the book, used here to take advantage of the fact# that Python can use negative indices in lists.for l in xrange(2, self.num_layers):z = zs[-l]sp = sigmoid_prime(z)delta = np.dot(self.weights[-l+1].transpose(), delta) * spnabla_b[-l] = deltanabla_w[-l] = np.dot(delta, activations[-l-1].transpose())return (nabla_b, nabla_w)...def cost_derivative(self, output_activations, y):"""Return the vector of partial derivatives \partial C_x /\partial a for the output activations."""return (output_activations-y) def sigmoid(z):"""The sigmoid function."""return 1.0/(1.0+np.exp(-z))def sigmoid_prime(z):"""Derivative of the sigmoid function."""return sigmoid(z)*(1-sigmoid(z))問題
- 在 minibatch 上的反向傳播的全矩陣方法
我們對于隨機梯度下降的實現(xiàn)是對一個 minibatch 中的訓練樣本進行遍歷。所以也可以更改反向傳播算法使得它同時對一個 minibatch 中的所有樣本進行梯度計算。這個想法其實就是我們可以用一個矩陣 $$X=[x_1, x_2, ..., x_m]$$,其中每列就是在minibatch 中的向量,而不是單個的輸入向量,$$x$$。我們通過乘權重矩陣,加上對應的偏差進行前向傳播,在所有地方應用 sigmoid 函數(shù)。然后按照類似的過程進行反向傳播。請顯式寫出這種方法下的偽代碼。更改network.py 來實現(xiàn)這個方案。這樣做的好處其實利用到了現(xiàn)代的線性代數(shù)庫。所以,這會比在 minibatch 上進行遍歷要運行得更快(在我的筆記本電腦上,在 MNIST 分類問題上,我相較于上一章的實現(xiàn)獲得了 2 倍的速度提升)。在實際應用中,所有靠譜的反向傳播的庫都是用了類似的基于矩陣或者變體的方式來實現(xiàn)的。
在哪種層面上,反向傳播是快速的算法?
為了回答這個問題,首先考慮另一個計算梯度的方法。就當我們回到上世界50、60年代的神經(jīng)網(wǎng)絡研究。假設你是世界上首個考慮使用梯度下降方法學習的那位!為了讓自己的想法可行,就必須找出計算代價函數(shù)梯度的方法。想想自己學到的微積分,決定試試看鏈式法則來計算梯度。但玩了一會后,就發(fā)現(xiàn)代數(shù)式看起來非常復雜,然后就退縮了。所以就試著找另外的方式。你決定僅僅把代價看做權重 $$C$$ 的函數(shù)。你給這些權重 $$w_1, w_2, ...$$ 進行編號,期望計算關于某個權值 $$w_j$$ 關于 $$C$$ 的導數(shù)。而一種近似的方法就是下面這種:
其中 $$\epsilon>0$$ 是一個很小的正數(shù),而 $$e_j$$ 是在第j個方向上的單位向量。換句話說,我們可以通過計算 $$w_j$$ 的兩個接近相同的點的值來估計 $$\partial C/\partial w_j$$,然后應用公式(46)。同樣方法也可以用來計算 $$\partial C/\partial b$$。
這個觀點看起來非常有希望。概念上易懂,容易實現(xiàn),使用幾行代碼就可以搞定。看起來,這樣的方法要比使用鏈式法則還要有效。
然后,遺憾的是,當你實現(xiàn)了之后,運行起來這樣的方法非常緩慢。為了理解原因,假設我們有 $$1,000,000$$ 權重。對每個不同的權重 $$w_j$$ 我們需要計算 $$C(w+\epsilon * e_j)$$ 來計算 $$\partial C/\partial w_j$$。這意味著為了計算梯度,我們需要計算代價函數(shù) $$1, 000, 000 $$次,需要 $$1, 000, 000$$ 前向傳播(對每個樣本)。我們同樣需要計算 $$C(w)$$,總共是 $$1,000,001$$ 次。
反向傳播聰明的地方就是它確保我們可以同時計算所有的偏導數(shù) $$\partial C/\partial w_j$$ 使用一次前向傳播,加上一次后向傳播。粗略地說,后向傳播的計算代價和前向的一樣。*
這個說法是合理的,但需要額外的說明來澄清這一事實。在前向傳播過程中主要的計算代價消耗在權重矩陣的乘法上,而反向傳播則是計算權重矩陣的轉(zhuǎn)置矩陣。這些操作顯然有著類似的計算代價。
所以最終的計算代價大概是兩倍的前向傳播計算大家。比起直接計算導數(shù),顯然 反向傳播 有著更大的優(yōu)勢。所以即使 反向傳播 看起來要比 (46) 更加復雜,但實際上要更快。
這個加速在1986年首次被眾人接受,并直接導致神經(jīng)網(wǎng)絡可以處理的問題的擴展。這也導致了大量的研究者涌向了神經(jīng)網(wǎng)絡方向。當然,反向傳播 并不是萬能鑰匙。在 1980 年代后期,人們嘗試挑戰(zhàn)極限,尤其是嘗試使用反向傳播來訓練深度神經(jīng)網(wǎng)絡。本書后面,我們將看到現(xiàn)代計算機和一些聰明的新想法已經(jīng)讓 反向傳播 成功地訓練這樣的深度神經(jīng)網(wǎng)絡。
反向傳播:大視野
正如我所講解的,反向傳播 提出了兩個神秘的問題。首先,這個算法真正在干什么?我們已經(jīng)感受到從輸出處的錯誤被反向傳回的圖景。但是我們能夠更深入一些,構造出一種更加深刻的直覺來解釋所有這些矩陣和向量乘法么?第二神秘點就是,某人為什么能發(fā)現(xiàn)這個 反向傳播?跟著一個算法跑一遍甚至能夠理解證明算法可以運行這是一回事。這并不真的意味著你理解了這個問題到一定程度,能夠發(fā)現(xiàn)這個算法。是否有一個推理的思路可以指引我們發(fā)現(xiàn) 反向傳播 算法?本節(jié),我們來探討一下這兩個謎題。
為了提升我們關于算法究竟做了什么的直覺,假設我們已經(jīng)對 $$w_{jk}^l$$ 做一點小小的變動 $$\Delta w_{jk}^l$$:
這個改變會導致在輸出激活值上的相應改變:
然后,會產(chǎn)生對下一層激活值的改變:
接著,這些改變都將影響到一個個下一層,到達輸出層,最終影響代價函數(shù):
所以代價函數(shù) $$\Delta C$$ 改變和 $$\Delta w_{jk}^l$$ 就按照下面的公式關聯(lián)起來了:
這給出了一種可能的計算 $$\frac{\partial C}{\partial w_{jk}^l}$$ 的方法其實是細致地追蹤一個 $$w_{jk}^l$$ 的微小變化如何導致 $$C$$ 中的變化值。如果我們可以做到這點,能夠精確地使用易于計算的量來表達每種關系,那么我們就能夠計算 $$\frac{\partial C}{\partial w_{jk}^l}$$ 了。
我們嘗試一下這個方法。$$\Delta w_{jk}^l$$ 導致了在 $$l^{th}$$ 層 $$j^{th}$$ 神經(jīng)元的激活值的變化 $$\Delta a_j^l$$。這個變化由下面的公式給出:
$$\Delta a_j^l$$ 的變化將會導致下一層的所有激活值的變化。我們聚焦到其中一個激活值上看看影響的情況,不妨設 $$a_q^{l+1}$$,
實際上,這會導致下面的變化:
將其代入方程(48),我們得到:
當然,這個變化又會去下一層的激活值。實際上,我們可以想象出一條從 $$w_{jk}^l$$ 到 $$C$$ 的路徑,然后每個激活值的變化會導致下一層的激活值的變化,最終是輸出層的代價的變化。假設激活值的序列如下 $$a_j^l, a_q^{l+1}, ..., a_n^{L-1},a_m^{L}$$,那么結果的表達式就是
我們已經(jīng)對每個經(jīng)過的神經(jīng)元設置了一個 $$\partial a/\partial a$$ 這種形式的項,還有輸出層的 $$\partial C/\partial a_m^L$$。這表示除了 $$C$$ 的改變由于網(wǎng)絡中這條路徑上激活值的變化。當然,整個網(wǎng)絡中存在很多 $$w_{jk}^l$$ 可以傳播而影響代價函數(shù)的路徑,這里我們就看其中一條。為了計算 $$C$$ 的全部改變,我們就需要對所有可能的路徑進行求和,即,
這里我們對路徑中所有可能的中間神經(jīng)元選擇進行求和。對比 (47) 我們有
現(xiàn)在公式(53)看起來相當復雜。但是,這里其實有一個相當好的直覺上的解釋。我們用這個公式計算 $$C$$ 關于網(wǎng)絡中一個權重的變化率。而這個公式告訴我們的是:兩個神經(jīng)元之間的連接其實是關聯(lián)與一個變化率因子,這僅僅是一個神經(jīng)元的激活值相對于其他神經(jīng)元的激活值的偏導數(shù)。從第一個權重到第一個神經(jīng)元的變化率因子是 $$\partial a_j^l/\partial w_{jk}^l$$。路徑的變化率因子其實就是這條路徑上的眾多因子的乘積。而整個的變化率 $$\partial C/\partial w_{jk}^l$$ 就是對于所有可能的從初始權重到最終輸出的代價函數(shù)的路徑的變化率因子的和。針對某一個路徑,這個過程解釋如下,
我們到現(xiàn)在所給出的東西其實是一種啟發(fā)式的觀點,一種思考權重變化對網(wǎng)絡行為影響的方式。讓我們給出關于這個觀點應用的一些流程建議。首先,你可以推導出公式(53)中所有單獨的的偏導數(shù)顯式表達式。只是一些微積分的運算。完成這些后,你可以弄明白如何用矩陣運算寫出對所有可能的情況的求和。這項工作會比較乏味,需要一些耐心,但不用太多的洞察。完成這些后,就可以盡可能地簡化了,最后你發(fā)現(xiàn),自己其實就是在做反向傳播!所以你可以將反向傳播想象成一種計算所有可能的路徑變化率的求和的方式。或者,換句話說,反向傳播就是一種巧妙地追蹤權重(和偏差)微小變化的傳播,抵達輸出層影響代價函數(shù)的技術。
現(xiàn)在我不會繼續(xù)深入下去。因為這項工作比較無聊。如果你想挑戰(zhàn)一下,可以嘗試與喜愛。即使你不去嘗試,我也希望這種思維方式可以讓你能夠更好地理解反向傳播。
那其他的一些神秘的特性呢——反向傳播如何在一開始被發(fā)現(xiàn)的?實際上,如果你跟隨我剛剛給出的觀點,你其實是可以發(fā)現(xiàn)反向傳播的一種證明的。不幸的是,證明會比本章前面介紹的證明更長和更加的復雜。那么,前面那個簡短(卻更加神秘)的證明如何被發(fā)現(xiàn)的?當你寫出來所有關于長證明的細節(jié)后,你會發(fā)現(xiàn)其實里面包含了一些明顯的可以進行改進的地方。然后你進行一些簡化,得到稍微簡短的證明,寫下來。然后又能發(fā)現(xiàn)一些更加明顯的簡化。進過幾次迭代證明改進后,你會發(fā)現(xiàn)最終的簡單卻看起來奇特的證明,因為你移除了很多構造的細節(jié)了!老實告訴你,其實最早的證明的出現(xiàn)也不是太過神秘的事情。因為那只是很多對簡化證明的艱辛工作的積累。
文/Not_GOD(簡書作者)
原文鏈接:http://www.jianshu.com/p/4fca9fe2af74
總結
以上是生活随笔為你收集整理的第二章 反向传播算法如何工作的?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第三章 改进神经网络的学习方式(中下)
- 下一篇: ReLu(Rectified Linea