相信每個剛剛入門神經網絡(如今叫深度學習)的同窗都必定在反向傳播的梯度推導那裏被折磨了半天。在各類機器學習的課上明明聽得很是明白,神經網絡無非就是正向算一遍Loss,反向算一下每一個參數的梯度,而後你們按照梯度更新就行了。問題是梯度到底怎麼求呢?課上每每舉的是標量的例子,但是一到你作做業的時候就發現全部的東西都是vectorized的,一個一個都是矩陣。矩陣的微分操做大部分人都是不熟悉的,結果使得不少人在梯度的推導這裏直接選擇死亡。我曾經就是其中的一員,作CS231n的Assignment 1裏面那幾個簡單的小導數都搞得讓我懷疑人生。python
我相信不少人都看了很多資料,好比CS231n的講師Karpathy推薦的這一篇矩陣求導指南http://cs231n.stanford.edu/vecDerivs.pdf,可是通過了幾天的折磨之後,我發現事實上根本就不須要去學習這些東西。在神經網絡中正確計算梯度其實很是簡單,只須要把握好下面的兩條原則便可。這兩條原則很是適合對矩陣微分不熟悉的同窗,雖然看起來並不嚴謹,可是有效。數組
1. 用好維度分析,不要直接求導網絡
神經網絡中求梯度,第一原則是:若是你對矩陣微分不熟悉,那麼永遠不要直接計算一個矩陣對另外一個矩陣的導數。咱們很快就能夠看到,在神經網絡中,全部的矩陣對矩陣的導數都是能夠經過間接的方法,利用求標量導數的那些知識輕鬆求出來的。而這種間接求導數的方法就是維度分析。我認爲維度分析是神經網絡中求取梯度最好用的技巧,沒有之一。用好維度分析,你就不用一個一個地去分析矩陣當中每一個元素到底是對誰怎麼求導的,各類求和完了之後是左乘仍是右乘,到底該不應轉置等等破事,簡直好用的不能再好用了。這一技巧在Karpathy的Course Note上也提到了一點。機器學習
什麼叫維度分析?舉一個最簡單的例子。設某一層的Forward Pass爲,X是NxD的矩陣,W是DxC的矩陣,b是1xC的矩陣,那麼score就是一個NxC的矩陣。如今上層已經告訴你L對score的導數是多少了,咱們求L對W和b的導數。函數
咱們已經知道必定是一個NxC的矩陣(由於Loss是一個標量,score的每個元素變化,Loss也會隨之變化),那麼就有學習
如今問題來了,score是一個矩陣,W也是個矩陣,矩陣對矩陣求導,怎麼求啊?若是你對矩陣微分不熟悉的話,到這裏就直接懵逼了。因而不少同窗都出門右轉去學習矩陣微分到底怎麼搞,看到那滿篇的推導過程就感到一陣噁心,以後就提早走完了從入門到放棄,從深度學習到深度厭學的整個過程。spa
其實咱們沒有必要直接求score對W的導數,咱們能夠利用另外兩個導數間接地把算出來。首先看看它是多大的。咱們知道
必定是DxC的(和W同樣大),而
是NxC的,哦那你瞬間就發現了
必定是DxN的,由於(DxN)x(NxC)=>(DxC),而且你還發現你隨手寫的這個式子右邊兩項寫反了,應該是
。code
那好,咱們已經知道了是DxN的,那就好辦了。既然score=XW+b,若是都是標量的話,score對W求導,自己就是X;X是NxD的,咱們要DxN的,那就轉置一下唄,因而咱們就得出了:orm
完事了。element
你看,咱們並無直接去用諸如這種細枝末節的一個一個元素求導的方式推導
,而是利用
再加上熟悉的標量求導的知識,就把這個矩陣求導給算出來了。這就是神經網絡中求取導數的正確姿式。
爲何這一招老是有效呢?這裏的關鍵點在於Loss是一個標量,而標量對一個矩陣求導,其大小和這個矩陣的大小永遠是同樣的。那麼,在神經網絡裏,你永遠均可以執行這個「知二求一」的過程,其中的「二」就是兩個Loss對參數的導數,另外一個是你不會求的矩陣對矩陣的導數。首先把你無法直接求的矩陣導數的大小給計算出來,而後利用你熟悉的標量求導的方法大概看看導數長什麼樣子,最後湊出那個目標大小的矩陣來就行了。
那呢?咱們來看看,
是NxC的,
是1xC的,
看起來像1,那聰明的你確定想到
其實就是1xN個1了,由於(1xN)x(NxC)=>(1xC)。其實這也就等價於直接對d_score的第一維求個和,把N下降成1而已。
多說一句,這個求和是怎麼來的?緣由實際上在於所謂的「廣播」機制。你會發現,XW是一個NxC的矩陣,可是b只是一個1xC的矩陣,按理說,這倆矩陣形狀不同,是不能相加的。可是咱們都知道,實際上咱們想作的事情是讓XW的每一行都加上b。也就是說,咱們把b的第一維複製了N份,強行變成了一個NxC的矩陣,而後加在了XW上(固然這件事其實是numpy幫你作的)。那麼,當你要回來求梯度的時候,既然每個b都參與了N行的運算,那就要把每一份的梯度全都加起來求個和的。由於求導法則告訴咱們,若是一個變量參與了多個運算,那就要把它們的導數加起來。這裏借用一下
的圖,相信你們能夠看得更明白。
總之,不要試圖在神經網絡裏面直接求矩陣對矩陣的導數,而要用維度分析間接求,這樣能夠爲你省下不少沒必要要的麻煩。
2. 用好鏈式法則,不要一步到位
我曾經以爲鏈式法則簡直就是把簡單的問題搞複雜,複合函數求導這種東西高考的時候咱們就都會了,還用得着一步一步地往下拆?好比,我一眼就能看出來
,還用得着先把
當成一箇中間函數麼?
不幸的是,在神經網絡裏面,你會發現事情沒那麼容易。上面的這些推導只在標量下成立,若是w,x和b都是矩陣的話,咱們很容易就感到無從下筆。還舉上面這個例子,設,咱們要求
,那麼咱們直接就能夠寫出
L對H的導數,是反向傳播當中上一層會告訴你的,但問題是H對W的導數怎麼求呢?
若是你學會了剛纔的維度分析法,那麼你可能會以爲是一個DxN的矩陣。而後就會發現沒有任何招能夠用了。事實上,卡殼的緣由在於,
根本不是一個矩陣,而是一個4維的tensor。對這個鬼玩意的運算初學者是搞不定的。準確的講,它也能夠表示成一個矩陣,可是它的大小並非DxN,並且它和
的運算也不是簡單的矩陣乘法,會有向量化等等的過程。有興趣的同窗能夠參考這篇文章,裏面有一個例子講解了如何直接求這個導數:矩陣求導術(下)。
這是一個剛學完反向傳播的初學者很容易踩到的陷阱:試圖不設中間變量,直接就把目標參數的梯度給求出來。若是這麼去作的話,很容易在中間碰到這種非矩陣的結構,由於理論上矩陣對矩陣求導求出來是一個4維tensor,不是咱們熟悉的二維矩陣。除非你徹底掌握了上面那篇reference當中的數學技巧,否則你就只能乾瞪眼了。
可是,若是你不直接求取對W的導數,而把當作一箇中間變量的話,事情就簡單的多了。由於若是每一步求導都只是一個簡單二元運算的話,那麼即便是矩陣對矩陣求導,求出來也仍然是一個矩陣,這樣咱們就能夠用維度分析法往下作了。
設,則有
利用維度分析:dS是NxC的,dH是NxC的,考慮到,那麼容易想到
也是NxC的,也就是
,這是一個element-wise的相乘;因此
;
再求,用上一部分的方法,很容易求得
,因此就求完了。
有了這些結果,咱們不妨回頭看看一開始的那個式子:,若是你錯誤地認爲
是一個DxN的矩陣的話,再往下運算:
咱們已經知道,這兩個矩陣一個是NxC的,一個是DxN的,不管怎麼相乘,也得不出DxN的矩陣。矛盾就是出在H對W的導數其實並非一個矩陣。可是若是使用鏈式法則運算的話,咱們就能夠避開這個複雜的tensor,只使用矩陣運算和標量求導就搞定神經網絡中的梯度推導。
藉助這兩個技巧,已經足以計算任何複雜的層的梯度。下面咱們來實戰一個:求Softmax層的梯度。
Softmax層每每是輸出層,其Forward Pass公式爲:
,
,
假設輸入X是NxD的,總共有C類,那麼W顯然應該是DxC的,b是1xC的。其中就是第i個樣本預測的其正確class的機率。關於softmax的知識在這裏就很少說了。咱們來求Loss關於W, X和b的導數。爲了簡便起見,下面全部的d_xxx指的都是Loss對xxx的導數。
咱們首先把Loss從新寫一下,把P代入進去:
不要一步到位,咱們把前面一部分和後面一部分分開看。設, rowsum就是每一行的score指數和,所以是Nx1的,那麼就有
先看d_score,其大小與score同樣,是NxC的。你會發現若是扔掉前面的1/N不看,d_score其實就是一堆0,而後在每一行那個正確的class那裏爲-1;寫成python代碼就是
d_score = np.zeros_like(score)
d_score[range(N),y] -= 1
而後看d_rowsum,其實就是,很是簡單。
如今咱們關注,須要注意的是咱們不要直接求
是什麼,兩個都是矩陣,很差求;相反,咱們求
是多少。咱們會發現上面咱們求了一個d_score,這裏又求了一個d_score,這說明score這個矩陣參與了兩個運算,這是符合這裏Loss的定義的。求導法則告訴咱們,當一個變量參與了兩部分運算的時候,把這兩部分的導數加起來就能夠了。
這一部分的d_score就很好求了:
,左邊是NxC的,右邊已知的是Nx1的,那麼剩下的有多是1xC的,也有多是NxC的。這個時候就要分析一下了。咱們會發現右邊應該是NxC的,由於每個score都隻影響一個rowsum的元素,所以咱們不該該求和。NxC的矩陣就是
本身,因此咱們就很容易得出:
# 實際上,d_rowsum每每是一個長度爲N的一位數組,所以咱們先用np.newaxis把它的shape由N升維到Nx1, # 這樣就可使用廣播機制(Nx1 * NxC) # 而後用乘號作element wise相乘。 d_score += d_rowsum[:, np.newaxis] * (np.exp(score)) d_score /= N #再把那個1/N給補上
這樣咱們就完成了對score的求導,以後score對W, X和b的求導,相信你也就會了。
固然,若是你注意一下的話,你會發現其實第二部分的那個式子就是P矩陣。不過若是你沒有注意到這一點也無所謂,用這套方法也能夠求出d_score是多少。
利用一樣的方法,如今看看那個卡住無數人的Batch Normalization層的梯度推導,是否是也感到不那麼困難了?
但願本文能夠爲剛剛入門神經網絡的同窗提供一些幫助,若有錯漏歡迎指出。
謝謝!