【DL-CV】反向傳播,(隨機)梯度降低

【DL-CV】損失函數,SVM損失與交叉熵損失<前篇---後篇>【DL-CV】激活函數及其選擇python

有了損失函數L,咱們能定量的評價模型的好壞。咱們但願損失能最小化,或具體點,咱們但願能找到使損失最小化的權重W。固然這個過程不是一步完成的,咱們會使用梯度降低算法來一步步修改權重W,好讓損失逐漸逼近最小值,這是一個收斂的過程。下面介紹梯度降低算法以並用反向傳播來求梯度算法

梯度降低 Gradient descent

看名字就和梯度脫不了關係了。其原理很簡單,學太高數都知道,梯度是一個向量,方向指向函數增大最快的方向;那反過來梯度的負值指向函數衰減最快的方向。損失函數展開後是關於權重W的函數L(W),那其梯度負值 -∇L 指向損失降低最快的方向,咱們讓權重W往該方向走一小步獲得新的權重Wnew(更新權重),它對應更低的損失。不停地重複計算梯度,更新權重這兩步,權重/模型就會趨於完美(所謂迭代訓練過程)segmentfault

負梯度: $-∇L =- {∂L \over ∂W}$, 權重更新: $W_{nwe}={W-\alpha {∂L \over ∂W}}$,從公式中也知道梯度$∂L \over ∂W$$是和W形狀同樣的矩陣(標量對矩陣求導的特性)。
固然這裏的權重W是一個矩陣而不是一個值,會涉及矩陣乘法求梯度。網絡

這裏的α叫學習率(一般是個很小的正數),用於控制權重變化的幅度,能夠理解爲步長。學習率的選擇是神經網絡訓練中最重要的超參數設定之一,學習率太大,容易越過損失函數的最小值而後在最小值周圍浮動沒法收斂;學習率過小,收斂速度太慢,訓練效率下降。關於學習率的設定咱們延後詳細講解。框架

總的來講,這就是原味不加特效的梯度降低法,也就是徹底跟着梯度走。隨着知識的深刻,後面會介紹更高級的更新法則(再也不死跟梯度),以得到更高的性能。dom

反向傳播 Back propagation

原理就是高數中求複合函數偏導數/偏微分的鏈式法則,我還記得剛學的時候會畫樹狀圖,用着老師教的口訣「分叉相加,分層相乘」進行推導。不過那都是很簡單的題目了,趕上這種層數多參數多涉及矩陣的神經網絡,仍是很容易犯錯的,最好仍是老老實實的看下反向傳播吧。函數

在應用於神級網絡模型以前,咱們拿個簡單的函數直觀地展現反向傳播原理。
設有函數$f(w,x)={1\over 1+e^{w*x}}$,w是行向量,x是列向量。咱們能夠原函數的計算細分紅不少小步,交給不一樣的子函數完成,這些子函數嵌套(複合函數)起來便能計算出原函數的值。把每一個子函數看作一個節點,相連得下圖
圖片描述性能

正向傳播是計算函數值,輸出結果給下一個函數計算(從左往右)。反向傳播求全局梯度的原理就是先求局部梯度的公式,而後接受正向傳播傳來的結果帶入局部梯度公式獲得局部梯度,因而每一個節點都有本身的局部梯度,最後咱們在反向的過程當中(從右往左)運用口訣「分叉相加,分層相乘」把它們拼起來獲得全局梯度了。從圖中能夠看出對w的梯度大小是和w同樣的。學習

有人可能會一口氣算出複合函數的導數公式而後一個勁地帶w和x進去獲得結果,像這樣簡單函數固然是能夠的。但這樣對計算機來講運算量就加倍了:正向傳播時計算了一遍,而用你的公式代w和x又會重複正向傳播的計算。面對多層神經網絡這是超耗時的,因此正向傳播時要分層保存結果供每層局部梯度計算使用(實際上深度學習框架就是這樣乾的)lua


咱們的多層神經網絡的模型就至關於一個巨大的函數,它由多個線性分類器$f(x,W,b) = {W*x+b}$,激活函數,最後加個損失函數(這些函數都是連續可微的)複合而成。運用反向傳播,咱們能夠計算損失函數關於每一層權重的梯度,而後實現每一層權重的訓練。

關於矩陣乘法的梯度

上面反向傳播的例子是對權重某個具體值進行求導的,實際使用上咱們是對整個權重這個矩陣進行求導的,但概念是通用的,建議初學時寫出一個很小很明確的向量化例子,在紙上演算梯度,而後對其通常化,獲得一個高效的向量化操做形式。
但更多時候咱們會用一個小技巧,這個技巧的關鍵是分析維度,咱們能夠經過維度的拼湊使得拼出來的${∂L \over ∂W}與{W}形狀相同$,總有一個方式是可以讓維度之間可以對的上的

# 前向傳播
W = np.random.randn(5, 10) # 假設W是 5x10 矩陣
X = np.random.randn(10, 3) # x是 10x3 矩陣
D = W.dot(X) # D=W*x 是 5x3 矩陣


# 假設咱們獲得了D的梯度∂L/∂D
dD = np.random.randn(*D.shape) # 和D同樣的尺寸 5x3

#X.T指X的轉置
dW = dD.dot(X.T) #∂L/∂W應該是 5x10 矩陣,由 dD*X.T拼出([5x3]*[3x10]=[5x10])
dX = W.T.dot(dD) #∂L/∂X應該是 10x3 矩陣,由 W.T*dD拼出([10x5]*[5x3]=[10x3])

隨機梯度降低 Stochastisc Gradient Descent

有隨機固然就有不隨機,這種不隨機的算法叫批量學習(batch learning)算法,爲了引入主角「隨機」咱們先來聊聊其它。批量學習算法在進行迭代訓練(計算損失,計算梯度,權重更新三循環)的時候要遍歷所有訓練樣本,也所以,這種算法可以有效地抑制訓練集內帶噪聲的樣本所致使的劇烈變更,而且得到全局最優解;但同時也不免顧此失彼,因爲每次更新權重全部樣本都要參與訓練,訓練集一大起來很是耗時。


爲了彌補面對大量數據時用時上的缺陷,就有了隨機梯度降低法,這裏介紹其中一種叫小批量梯度降低(mini-batch gradient descent)的算法:
每次迭代訓練時從訓練集中隨機部分樣本(也就是batch size,數量一般爲2n,經常使用32,64,128,256,根據狀況定)進行迭代更新權重。因爲每次迭代只使用部分樣本,因此和批量學習相比,能減小單次訓練時間。它保持收斂性的同時還能減小了迭代結果陷入局部最優解的狀況。應用小批量梯度降低法的隨機梯度降低法已經成爲當前深度學習的主流算法

# 大概思路
while True:
    data_batch = sample_training_data(data, 256) # 從訓練集中隨機取256個樣本用於訓練
    weights_grad = evaluate_gradient(loss_fun, data_batch, weights) #獲取梯度
    weights += - step_size * weights_grad # 更新權重

雙層網絡&softmax損失函數反向傳播

下面咱們經過一個小網絡例子並運用求導技巧來展現神經網絡反向傳播的實現。如今咱們有一個使用softmax損失的雙層網絡,咱們的目標是求$\partial L\over\partial W_1$,$\partial L\over\partial W_2$,$\partial L\over\partial b_1$,$\partial L\over\partial b_2$

clipboard.png

輸入(X)→→線性分類器+relu激活函數(h1 =max(0,X*W1 + b1) )→→線性分類器(h2 = h1*W2 + b2)→→輸出(S=h2)→→softmax損失函數並使用L2正則化

X是(N*D)的矩陣,包含N個樣本數據,每行X i是第i個樣本的數據

以上的h1,h2/s,都是矩陣,每行是第i個樣本的層激活值

如下的 j 表明類別的數字,假如這個網絡輸出的是10個類別的分數,則j=0,1,2,...9

從右至左,首先來推導softmax損失函數的梯度。咱們有損失公式:
$$L = { \frac{1}{N} \sum_i^N L_i }+ { \lambda R(W) }$$
共有N個樣本,其中第i個樣本帶來的損失是:
$$L_i=\log (\sum_je^{s_{ij}})-s_{{iy_i}}$$
當 j!=yi 時:
$${\partial L_i\over \partial s_{ij}}={e^{s_{ij}}\over \sum_je^{s_{ij}}}$$
當 j==yi 時:
$${\partial L_i\over \partial s_{ij}}={e^{s_{ij}}\over \sum_je^{s_{ij}}}-1$$

據此容易求得${\partial L_i\over \partial s_{i}}$,使用維度技巧開始拼湊,${\partial L_i\over \partial W_2}(20*10)應該由{\partial L_i\over \partial s_i}(1*10)和h^1_i.T(20*1)組合而成$,因而:
$${\partial L_i\over \partial W_2}=h^1_i.T*{\partial L_i\over \partial s_i}\quad→(20*10)=(20*1)(1*10)$$

繼續往上走會遇到ReLU激活函數逐元素運行max(0,x),輸入小於等於0,回傳梯度爲0;輸入大於0,原封不動回傳梯度。咱們有$h^1_i =max(0,X_i*W_1+b_1),另設f_i=X_i*W_1+b_1$,運用維度技巧,${\partial L_i\over \partial h_i}(1*20)應該由{\partial L_i\over \partial s_i}(1*10)和W_2.T(10*20)組合而成$,因而:
$${\partial L_i\over \partial h^1_i}={\partial L_i\over \partial s_i}*W_2.T\quad→(1*20)=(1*10)(10*20)$$
$${\partial L_i\over \partial f_{ik}}={\partial L_i\over \partial h^1_{ik}}\quad (h^1_{ik}>0)$$
$${\partial L_i\over \partial f_{ik}}=0\quad (h^1_{ik}<=0)$$

繼續使用技巧,剩下的${\partial L_i\over \partial W_1}(3072*20)$ 應該有由${\partial L_i\over \partial f_{i}}(1*20)$和$X_i.T(3072*1)$組合而成,因而:
$${\partial L_i\over \partial W_1}=X_i.T*{\partial L_i\over \partial f_{i}}\quad→(3072*20)=(3072*1)(1*20)$$

至於偏轉值b的梯度就好求多了,因爲在公式中局部梯度爲1,直接接受傳來的梯度便可
$${\partial L_i\over\partial b_2}={\partial L_i\over\partial s_i}$$
$${\partial L_i\over\partial b_1}={\partial L_i\over\partial f_i}$$
以上的推導的都是${L_i}$對誰誰的梯度,那麼${L}$對誰誰的梯度怎麼求?難道求N次梯度求和取平均嗎?——不用,矩陣的運算給了咱們很好的性質,把上面提到的${X_i}$,${h^1_i}$,${f_i}$,${s_i}$這些向量全換成完整的矩陣${X}$,${h^1}$,${f}$,${s}$進行運算便可,矩陣乘法會執行求和操做,你只需最後求平均便可加上正則化損失的梯度便可。${L}$對$b$的梯度些許不一樣,須要對傳來的梯度在列上求和取平均
$${\partial L\over \partial W_2}={1\over N}h^1.T*{\partial L\over \partial s}+2\lambda W_2\quad→(20*10)=(20*N)(N*10)$$
$${\partial L\over \partial h^1}={\partial L\over \partial s}*W_2.T\quad→(N*20)=(N*10)(10*20)$$
$${\partial L\over \partial f_{ik}}={\partial L\over \partial h^1_{ik}}\quad (h^1_{ik}>0)$$
$${\partial L\over \partial f_{ik}}=0\quad (h^1_{ik}<=0)$$
$${\partial L\over \partial W_1}={1\over N}X.T*{\partial L\over \partial f_{i}}+2\lambda W_1\quad→(3072*20)=(3072*N)(N*20)$$
${L}$對$b$的梯度些許不一樣,須要對傳來的梯度在列上求和取平均。

numpy代碼實現

import numpy as np
def loss_and_grads(X, y, reg):
    N, D = X.shape  #記錄X尺寸,N個樣本
    # 正向傳播
    h1 = np.maximum(0, np.dot(X, W1) + b1)  #
    h2 = np.dot(h1, W2) + b2
    scores = h2

    scores -= np.repeat(np.max(scores, axis=1), self.num_classes).reshape(scores.shape)  # 預處理防溢出
    exp_class_scores = np.exp(scores)
    exp_corrext_class_scores = exp_class_scores[np.arange(N), y]
    loss = np.log(np.sum(exp_class_scores, axis=1)) - exp_corrext_class_scores
    loss = np.sum(loss)/N
    loss += reg*(np.sum(W2**2)+np.sum(W1**2))

    grads = {}
    # layer2
    dh2 = exp_class_scores / np.sum(exp_class_scores, axis=1, keepdims=True)  # 按行求和保持二維特性以廣播
    dh2[np.arange(N), y] -= 1  #j==y_i 時要減一
    dh2 /= N

    dW2 = np.dot(h1.T, dh2)
    dW2 += 2*reg*W2  # 加上正則化損失的梯度

    db2 = np.sum(dh2, axis=0) / N

    # layer1
    dh1 = np.dot(dh2, W2.T)

    df = dh1
    df[h1 <= 0] = 0  # 布爾標記使激活值非正數的梯度爲0

    dW1 = np.dot(X.T, df)
    dW1 += 2*reg*W1

    db1 = np.sum(df, axis=0) / N

    grads['W2'] = dW2
    grads['b2'] = db2
    grads['W1'] = dW1
    grads['b1'] = db1

    return loss, grads
相關文章
相關標籤/搜索