1. 程式人生 > >李理:卷積神經網路之Dropout

李理:卷積神經網路之Dropout

上文介紹了Batch Normalization技術。Batch Normalization是加速訓練收斂速度的非常簡單但又好用的一種實用技術,下面我們介紹可以提高模型的泛化能力的DropOut技術。

4. Dropout

4.1 Dropout簡介

dropout是一種防止模型過擬合的技術,這項技術也很簡單,但是很實用。它的基本思想是在訓練的時候隨機的dropout(丟棄)一些神經元的啟用,這樣可以讓模型更魯棒,因為它不會太依賴某些區域性的特徵(因為區域性特徵有可能被丟棄)。

圖片描述

上圖a是標準的一個全連線的神經網路,b是對a應用了dropout的結果,它會以一定的概率(dropout probability)隨機的丟棄掉一些神經元。

4.2 Dropout的實現

實現Dropout最直觀的思路就是按照dropout的定義來計算,比如上面的3層(2個隱藏層)的全連線網路,我們可以這樣實現:

""" 最原始的dropout實現,不推薦使用 """

p = 0.5 # 保留一個神經元的概率,這個值越大,丟棄的概率就越小。

def train_step(X): 

  H1 = np.maximum(0, np.dot(W1, X) + b1)
  U1 = np.random.rand(*H1.shape) < p # first dropout mask
  H1 *= U1 # drop!
  H2 = np.maximum(0
, np.dot(W2, H1) + b2) U2 = np.random.rand(*H2.shape) < p # second dropout mask H2 *= U2 # drop! out = np.dot(W3, H2) + b3 # 反向梯度計算,程式碼從略 def predict(X): H1 = np.maximum(0, np.dot(W1, X) + b1) * p # NOTE: scale the activations H2 = np.maximum(0, np.dot(W2, H1) + b2) * p # NOTE: scale the activations
out = np.dot(W3, H2) + b3

我們看函式 train_step,正常計算第一層的啟用H1之後,我們隨機的生成dropout mask陣列U1。它生成一個0-1之間均勻分佈的隨機陣列,然後把小於p的變成1,大於p的變成0。極端的情況,p = 0,則所有數都不小於p,因此U1全是0;p=1,所有數都小於1,因此U1全是1。因此越大,U1中1越多,也就keep的越多,反之則dropout的越多。
然後我們用U1乘以H1,這樣U1中等於0的神經元的啟用就是0,其餘的仍然是H1。
第二層也是一樣的道理。

predict函式我們需要注意一下。因為我們訓練的時候會隨機的丟棄一些神經元,但是預測的時候就沒辦法隨機丟棄了【我個人覺得也不是不能丟棄,但是這會帶來結果會不穩定的問題,也就是給定一個測試資料,有時候輸出a有時候輸出b,結果不穩定,這是實際系統不能接受的,使用者可能認為你的模型有”bug“】。那麼一種”補償“的方案就是每個神經元的輸出都乘以一個p,這樣在”總體上“使得測試資料和訓練資料是大致一樣的。比如一個神經元的輸出是x,那麼在訓練的時候它有p的概率keep,(1-0)的概率丟棄,那麼它輸出的期望是p x+(1-p) 0=px。因此測試的時候把這個神經元乘以p可以得到同樣的期望。

但是這樣測試的時候就需要多一次乘法,我們對於訓練的實時性要求沒有測試那麼高。所以更為常見的做法是如下面的程式碼:

p = 0.5 # 保留一個神經元的概率,這個值越大,丟棄的概率就越小。

def train_step(X):
  H1 = np.maximum(0, np.dot(W1, X) + b1)
  U1 = (np.random.rand(*H1.shape) < p) / p # first dropout mask. Notice /p!
  H1 *= U1 # drop!
  H2 = np.maximum(0, np.dot(W2, H1) + b2)
  U2 = (np.random.rand(*H2.shape) < p) / p # second dropout mask. Notice /p!
  H2 *= U2 # drop!
  out = np.dot(W3, H2) + b3

def predict(X):
  # ensembled forward pass
  H1 = np.maximum(0, np.dot(W1, X) + b1) # no scaling necessary
  H2 = np.maximum(0, np.dot(W2, H1) + b2)
  out = np.dot(W3, H2) + b3

上面的程式碼在訓練的時候給U1的每個元素除以p,相當於給H1放大1/p倍,那麼預測的時候,那麼後面層的引數學到的就相當於沒有dropout的情況,因此預測的時候就不需要再乘以p了。比如第1層的輸出是100個神經元,假設每一個神經元的輸出都是0.5。如果沒有dropout,這100個神經元都會連線到第二層的第一個神經元,假設第二層的第一個神經元的引數都是1,那麼它的累加和是50。如果使用了0.5的概率保留,則第二層第一個神經元的累加和變成了25。但是上面的演算法,我們對每個神經元的輸出都先除以0.5。也就是第一層每個輸出都是1,一個100個輸入給第二層的第一個神經元,然後又以0.5的概率丟棄,那麼最終累加的結果還是50。這樣就”相當於“補償”了第二層的“損失”。

4.3 實現

我們開啟作業2的Dropout.ipynb。

4.3.1 cell1-2

和之前的一樣的程式碼

4.3.2 cell3

開啟layers.py,把dropout_forward裡缺失的程式碼實現如下:

  if mode == 'train':
    ###########################################################################
    # TODO: Implement the training phase forward pass for inverted dropout.   #
    # Store the dropout mask in the mask variable.                            #
    ###########################################################################
    [N,D] = x.shape
    mask = (np.random.rand(N,D) < (1-p))/(1-p)
    out = x * mask
    ###########################################################################
    #                            END OF YOUR CODE                             #
    ###########################################################################
  elif mode == 'test':
    ###########################################################################
    # TODO: Implement the test phase forward pass for inverted dropout.       #
    ###########################################################################
    out = x
    ###########################################################################
    #                            END OF YOUR CODE                             #
    ###########################################################################

程式碼非常簡單,不過注意的是這裡的p是丟棄的概率,所以保留的概率是(1-p)。

執行這個cell的結果如下圖:

圖片描述

注意使用不同的dropout的結果是差不多的,因為我們實現的方法是“補償”,如果您使用第一種實現,那麼均值應該隨著p而變化

4.3.3 cell4

接下來是實現dropout_backward,這也非常簡單,只有一行程式碼:

if mode == ‘train’:

    ###########################################################################
    # TODO: Implement the training phase backward pass for inverted dropout.  #
    ###########################################################################
    dx = mask * dout
    ###########################################################################
    #                            END OF YOUR CODE                             #
    ###########################################################################

然後我們執行cell4進行gradient check

圖片描述

4.3.4 cell5

然後我們需要修改fc_net.py來增加dropout的支援,具體來說,如果dropout引數不是空,那麼每一個relu的輸出都需要增加一個dropout。

4.3.4.1 loss函式

init裡程式碼已經處理好了,不需要任何修改,我們只需要修改loss的forward和backward部分。

首先是loss函式的forward部分:

    dropout_cache = {}
    for i in range(1, self.num_layers):
      keyW = 'W' + str(i)
      keyb = 'b' + str(i)

      if not self.use_batchnorm:
        current_input, affine_relu_cache[i] = affine_relu_forward(current_input, self.params[keyW], self.params[keyb])

      else:
        key_gamma = 'gamma' + str(i)
        key_beta = 'beta' + str(i)
        current_input, affine_bn_relu_cache[i] = affine_bn_relu_forward(current_input, self.params[keyW],
                                                                        self.params[keyb],
                                                                        self.params[key_gamma], self.params[key_beta],
                                                                        self.bn_params[i - 1])

      if self.use_dropout:
        current_input, dropout_cache[i] = dropout_forward(current_input,self.dropout_param)

和之前的程式碼比其實就增加了兩行,如果self.use_dropout。
當然還要記得在外面定義dropout_cache這個dict

然後是backward部分:

    for i in range(self.num_layers - 1, 0, -1):
      if self.use_dropout:
        affine_dx = dropout_backward(affine_dx, dropout_cache[i])

      if not self.use_batchnorm:
        affine_dx, affine_dw, affine_db = affine_relu_backward(affine_dx, affine_relu_cache[i])

      else:
        affine_dx, affine_dw, affine_db, dgamma, dbeta = affine_bn_relu_backward(affine_dx, affine_bn_relu_cache[i])
        grads['beta' + str(i)] = dbeta
        grads['gamma' + str(i)] = dgamma

在for迴圈的最上面增加 if self.use_dropout這兩行。
注意順序,我們是把dropout放到啟用函式之後,因此反向求梯度是要最先計算affine_dx

完成程式碼後我們執行這個cell堅持梯度是否正確計算:

圖片描述

4.3.5 cell6-7

As an experiment, we will train a pair of two-layer networks on 500 training examples: one will use no dropout, and one will use a dropout probability of 0.75. We will then visualize the training and validation accuracies of the two networks over time.

接下來我們做一個實驗,我們會用500個數據訓練一對2層的網路:其中一個使用dropout(0.75的丟棄概率),一個不用。

訓練資料上的準確率

圖片描述

驗證資料上的準確率

圖片描述

從上面兩個圖可以看出:不使用dropout,訓練資料很少時會過擬合,在訓練資料上準確率100%,但是驗證資料上只有28%。而使用了dropout之後,訓練資料90%,但是驗證資料上能提高到30%以上。

這說明dropout確實能緩解過擬合的問題。

本篇文章介紹了Dropout技術,在下一篇文章中,我們將深入講解一個CNN網路的具體實現。

圖片描述

圖片描述