Python部落(python.freelycode.com)組織翻譯,禁止轉載,歡迎轉發。
我來這裡的目的是為了測試我對於Karpathy的博客《駭客的神經網絡指導》以及Python的理解,也是為了掌握最近精讀的Derek Banas的文章《令人驚奇的注釋代碼博覽》。作為一個沉浸在R語言和結構化數據的經典統計學習方法的人,我對於Python和神經網絡都很陌生,所以最好不要對個人能力產生錯覺,以為通過閱讀就可以掌握事物。因此,開始寫代碼吧。
神經門
理解神經網絡中任何節點的一種方法是把它當作門,它接收一個或多個輸入,並產生一個輸出,就像一個函數。
例如,考慮一個接受x和y作為輸入的門,並計算:f(x,y) = x * y,讓我們一起在Python中實現它:
類似地,我們可以實現一個門來計算它的兩個輸入的和。
和一個計算兩個輸入最大值的門。
最優化問題
考慮到電路的目標是最大化輸出。這其實是神經網絡的優化問題(實際上是最小化損失函數,但差別是微不足道的,這與最大化負損失函數相同)。現在,關鍵在於如何確定修改一點點輸入,就可以使輸出變大一點。如果我們能夠設計出一種識別的方法,可以一次又一次地嘗試這種方法來獲得更大的輸出值,直到我們碰到南牆,輸出停止增加。在那時,我們可以說我們已經將產出最大化,或者說至少在局部上達到了。
用一個只有一個門的簡單的電路來說明,就是前向乘法門
假設上文定義的前向乘法門是我們在神經網絡裡的唯一門。如何來調整每個輸入,完全取決於輸出對輸入的更改的敏感性(或響應)。因為就像之前所展示的那樣,輸出本質上是輸入的函數,輸出對每個輸入的敏感性就是那個輸入的偏導數,讓我們來計算一下:
當x=3和y=4時,乘法門的梯度,等於4,因為del(xy)/del(x)=y,也就是4。
由於梯度肯定是正的,所以增大一點x理應會增加輸出。讓我們來試試:
輸出果然增加了,和我們設想的一樣
這只是一個簡單門。如果我們讓門來實現函數f(x): f(x) = 100 - (x-y)^2會怎樣呢
如果我們給這個門提供的輸入是3和4,按道理它應該儘量讓x和y更接近,這樣輸出才會最大化。這個函數在x=y上的最大化是不重要的
請參見下面的圖片:
現在讓我們定義這個門,看看我們的梯度計算和輸入修改的方法是否有助於增加這個門的輸出。
正如預期的那樣,在梯度暗示的方向上對輸入進行一些修改,結果會產生更高的輸出。現在讓我們來多做幾次,幾十次,直到產量的提高或增加都不再明顯了
也和預期一樣,以這種方式來進行優化,讓x從3提升到3.49同時讓y從4降到3.51,從而使輸出達到了99.999。
使用解析梯度
到目前為止,我們已經證明了梯度下降法優化的一個應用,getNumericalForwardGradient函數來計算梯度數值。在實踐中,神經網絡包含大量複雜的門,這些門的數值的每次計算都變得非常昂貴。因此,我們經常使用解析梯度,它更準確,計算量也更少。
這就提出了新的問題:如果表達式複雜到,即使數學方法也很難求解梯度的解析解。我們將看到,為了處理這種情況,我們可以計算出一些簡單表達式的解析梯度,然後應用鏈式法則。
多神經門電路
讓我們考慮一個電路門,以x,y和z分別作為輸入和輸出。
實際上,這是兩個基本門的合體:一個是加門,輸入為x和y,另一個是乘積門,輸入為r和加門的輸出q。我們可以定義這個門如下:
上圖來源於 http://karpathy.github.io/neuralnets/
反向傳播
優化這個電路本來是需要我們計算整個電路的梯度。現在相反,我們將計算每個組件門的梯度,然後應用鏈式法則來獲得整個電路的梯度。
在這裡,q只是一個輸入為x和y的前向加法門, f是一個輸入為z和q的前向乘法門。上述最後兩個方程是整個的關鍵: 當使用x(或y)計算整個電路的梯度時,我們僅僅計算了關於x(或y)的門q的梯度,並用一個因子將其放大,就等於與門q的輸出有關的電路的梯度。
對於這個電路的輸入,x=-2,y=5,z=-4,這不難計算
讓我們看看這裡發生了什麼。照此∂x/∂q就等於1,即,增加x從而增加了門的輸出q。然而,在較大的電路中(f)輸出是由於輸出q的減少而增加的,因為∂f/∂q= z = -4是一個負數。因此,我們的目標是通過減少q來實現最大的電路f的輸出,同樣x的值也需要減少。
在這個電路中展示的很明顯,為了計算任何輸入的梯度,我們需要根據每個輸入,計算出那些直接接受輸入的門的梯度,然後將電路的每個門的梯度結果相乘(鏈式法則)。
但是在一個更複雜的電路中,在輸出階段之前,這個門可能會通向多個其他門,所以最好先從輸出階段開始進行逆向鏈式計算。(反向傳播)
上圖來源於http://karpathy.github.io/neuralnets/
看過我們如何使用鏈式法則後,我們現在可以把重點放在一些簡單門的局部梯度上:
我剛意識到我們好久都沒有寫代碼了。哦。現在,我們將對我們所討論的一切進行代碼化,來看看反向傳播使用鏈式法則到底是如何幫助我們計算相同的梯度。
在定義了門和單元之後,讓我們運行正向傳遞來生成輸出值:
現在,讓我們運行反向傳遞來破譯梯度df/dx:
現在我們已經從零開始在一個簡單的電路上實現了反向傳播,並且看到了如何利用鏈式法則來獲得一個更大的電路的梯度。實在是太有趣啦!
英文原文:https://sushant-choudhary.github.io/blog/2017/11/25/a-friendly-introduction-to-backrop-in-python.html