詳解 BackPropagation 反向傳播演算法!

紅色石頭發表於2021-09-22

首先介紹一下鏈式法則

假如我們要求z對x1的偏導數,那麼勢必得先求z對t1的偏導數,這就是鏈式法則,一環扣一環

BackPropagation(BP)正是基於鏈式法則的,接下來用簡單的前向傳播網路為例來解釋。裡面有線的神經元代表的sigmoid函式,y_1代表的是經過模型預測出來的,y_1 = w1 * x1 + w2 * x2,而y^1代表的是實際值,最後是預測值與實際值之間的誤差,l_1 = 1/2 * (y_1 – y^1)^2,l_2同理。總的錯誤是E = l_1 + l_2。

在神經網路中我們採用梯度下降(Gradient Descent)來進行引數更新,最終找到最優引數。可是離E最近的不是w1,首先我們需要求出E對l_1的偏導,接著求l_1對於最近神經元sigmoid中變數的導數,最後再求y_0對於w1的偏導,進行梯度更新。

這便是神經網路中的BP演算法,與以往的正向傳播不同,它應該是從反向的角度不斷優化

這裡只是用了一層隱含層,可以看的出來一個引數的梯度往往與幾個量產生關係:

  • 最終y被預測的值。這往往取決於你的啟用函式,如這裡採用sigmoid
  • 中間對啟用函式進行求導的值

  • 輸入的向量,即為x

推廣到N層隱含層,只是乘的東西變多了,但是每個式子所真正代表的含義是一樣的。

換個角度說,在深度學習梯度下降的時候會出現比較常見的兩類問題,梯度消失以及梯度爆炸很可能就是這些量之間出了問題,對模型造成了影響。

1、梯度消失(Gradient Vanishing)。意思是梯度越來越小,一個很小的數再乘上幾個較小的數,那麼整體的結果就會變得非常的小。那麼導致的可能原因有哪些呢?我們由靠近E的方向向後分析。

  • 啟用函式。y_1是最後經過啟用函式的結果,如果啟用函式不能很好地反映一開始輸入時的情況,那麼就很有可能出問題。sigmoid函式的性質是正數輸出為大於0.5,負數輸出為小於0.5,因為函式的值域為(0,1),所以也常常被用作二分類的啟用函式,用以表示概率。但是,當x比較靠近原點的時候,x變化時,函式的輸出也會發生明顯的變化,可是,當x相當大的時候,sigmoid幾乎已經是無動於衷了,x相當小的時候同理。這裡不妨具體舉個二分類的例子,比如說用0,1代表標籤,疊了一層神經網路,sigmoid函式作為啟用函式。E對l_1的偏導極大程度將取決於y_1,因為標籤就是0,1嘛。就算輸入端的x,w都比較大,那麼經過sigmoid壓縮之後就會變得很小,只有一層的時候其實還好,但是當層數變多之後呢,sigmoid函式假如說每一層都當做是啟用函式,那麼最後E對l_1的偏導將是十分地小,儘管x,w代表著一些資訊,可經過sigmoid壓縮之後資訊發生了丟失,梯度無法完成真正意義上的傳播,乘上一個很小的數,那麼整個梯度會越來越小,梯度越小,說明幾乎快收斂了。換句話說,幾乎沒多久就已經收斂了。

另一種思路是從公式(4)出發,無論y_0取何值,公式(4)的輸出值總是介於(0,1/4](當然具體邊界處是否能取到取決於具體變數的取值),證明:

因為不斷乘上一個比較小的數字,所以層數一多,那麼整個梯度的值就會變得十分小,而且這個是由sigmoid本身導致,應該是梯度消失的主因。

解決方法可以是換個啟用函式,比如RELU就不錯,或者RELU的變種。

2、梯度爆炸(Gradient Exploding)。意思是梯度越來越大,更新的時候完全起不到優化的作用。其實梯度爆炸發生的頻率遠小於梯度消失的頻率。如果發生了,可以用梯度削減(Gradient Clipping)。

  • 梯度削減。首先設定一個clip_gradient作為梯度閾值,然後按照往常一樣求出各個梯度,不一樣的是,我們沒有立馬進行更新,而是求出這些梯度的L2範數,注意這裡的L2範數與嶺迴歸中的L2懲罰項不一樣,前者求平方和之後開根號而後者不需要開根號。如果L2範數大於設定好的clip_gradient,則求clip_gradient除以L2範數,然後把除好的結果乘上原來的梯度完成更新。當梯度很大的時候,作為分母的結果就會很小,那麼乘上原來的梯度,整個值就會變小,從而可以有效地控制梯度的範圍。

另外,觀察公式可知,其實到底對誰求偏導是看最近的一次是誰作為自變數,就會對誰求,不一定都是對權重引數求,也有對y求的時候。

接著我們用PyTorch來實操一下反向傳播演算法,PyTorch可以實現自動微分,requires_grad 表示這個引數是可學習的,這樣我們進行BP的時候,直接用就好。不過預設情況下是False,需要我們手動設定。

import torch

x = torch.ones(3,3,requires_grad = True)
t = x * x + 2 
z = 2 * t + 1
y = z.mean()

接下來我們想求y對x的微分,需要注意這種用法只能適用於y是標量而非向量

y.backward()
print(x.grad)

所以當y是向量形式的時候我們可以自己來算,如

x = torch.ones(3,3,requires_grad = True)
t = x * x + 2
y = t - 9

如果計算y.backward()會報錯,因為是向量,所以我們需要手動算v,這裡就是y對t嘛,全為1,注意v的維度就行。

v = torch.ones(3,3)
y.backward(v)
print(x.grad)

相關文章