1. 程式人生 > >深度學習基礎系列(九)| Dropout VS Batch Normalization? 是時候放棄Dropout了 深度學習基礎系列(七)| Batch Normalization

深度學習基礎系列(九)| Dropout VS Batch Normalization? 是時候放棄Dropout了 深度學習基礎系列(七)| Batch Normalization

  Dropout是過去幾年非常流行的正則化技術,可有效防止過擬合的發生。但從深度學習的發展趨勢看,Batch Normalizaton(簡稱BN)正在逐步取代Dropout技術,特別是在卷積層。本文將首先引入Dropout的原理和實現,然後觀察現代深度模型Dropout的使用情況,並與BN進行實驗比對,從原理和實測上來說明Dropout已是過去式,大家應儘可能使用BN技術。

 

 一、Dropout原理

  根據wikipedia定義,dropout是指在神經網路中丟棄掉一些隱藏或可見單元。通常來說,是在神經網路的訓練階段,每一次迭代時,都會隨機選擇一批單元,讓其被暫時忽略掉,所謂的忽略是不讓這些單元參與前向推理和後向傳播。

   

  上圖是標準的神經網路,經過dropout後,則變成如下圖:

  

  一般來說,我們在可能發生過擬合的情況下才會使用dropout等正則化技術。那什麼時候可能會發生呢?比如神經網路過深,或訓練時間過長,或沒有足夠多的資料時。那為什麼dropout能有效防止過擬合呢?可以理解為,我們每次訓練迭代時,隨機選擇一批單元不參與訓練,這使得每個單元不會依賴於特定的字首單元,因此具有一定的獨立性;同樣可以看成我們拿同樣的資料在訓練不同的網路,每個網路都有可能過擬合,但迭代多次後,這種過擬合會被抵消掉。

  要注意的是,dropout是體現在訓練環節,訓練完成後,我們認為所有的單元都被訓練好了,在驗證或測試階段,我們是拿完整的神經網路去驗證或測試。

 

二、Dropout具體實現

  以keras為例,其程式碼為:keras.backend.dropout(x, level, noise_shape=None, seed=None),其中x指的是輸入引數,level則是keep-prob,也就是這個單元有多少概率會被設定為0。

import tensorflow.keras.backend as K

input = K.random_uniform_variable(shape=(3, 3), low=0, high=1)

print("dropout with keep-prob 0.5:", K.eval(K.dropout(input, 0.5)))
print("dropout with keep-prob 0.2:", K.eval(K.dropout(input, 0.2))) print("dropout with keep-prob 0.8:", K.eval(K.dropout(input, 0.8)))

  看看輸出結果:

dropout with keep-prob 0.5: 
[[1.190095  0.        1.2999489]
 [0.        0.3164637 0.       ]
 [0.        0.        0.       ]]
dropout with keep-prob 0.2: 
[0.74380934 0.67237484 0.81246805]
 [0.8819132  0.19778982 1.2349881 ]
 [1.0369372  0.5945368  0.        ]]
dropout with keep-prob 0.8: 
[[0.        0.        0.       ]
 [0.        0.        4.9399524]
 [4.147749  2.3781471 0.       ]]

  可以看出,level值越大,每個單元成為0的概率也就越大。

  在具體的keras應用中,dropout通常放在啟用函式後,比如:

model=keras.models.Sequential()
model.add(keras.layers.Dense(150, activation="relu"))
model.add(keras.layers.Dropout(0.5))

 

三、Dropout正在被拋棄

  隨著深度學習的發展,Dropout在現代卷積架構中,已經逐步被BN(想要了解BN,大家可以參見我之前寫的 深度學習基礎系列(七)| Batch Normalization 一文,這裡不再贅述)取代,BN也同樣擁有不亞於Dropout的正則化效果。

  “We presented an algorithm for constructing, training, and performing inference with batch-normalized networks. The resulting networks can be trained with saturating nonlinearities, are more tolerant to increased training rates, and often do not require Dropout for regularization.” -Ioffe and Svegedy 2015

  至於為何Dropout不再受青睞,原因如下:

  • Dropout在卷積層的正則效果有限。相比較於全連線層,卷積層的訓練引數較少,啟用函式也能很好地完成特徵的空間轉換,因此正則化效果在卷積層不明顯;
  • Dropout也過時了,能發揮其作用的地方在全連線層,可當代的深度網路中,全連線層也在慢慢被全域性平均池化曾所取代,不但能減低模型尺寸,還可以提升效能。

  事實上,我們可以看看keras實現的現代經典模型,就可以窺之dropout目前的處境。開啟keras的地址:https://github.com/keras-team/keras-applications 

  縱觀無論是VGG、ResNet、Inception、MobileNetV2等模型,都不見了Dropout蹤影。唯獨在MobileNetV1模型裡,還可以找到Dropout,但不是在卷積層;而且在MobileNetV2後,已經不再有全連線層,而是被全域性平均池化層所取代。如下圖所示:

  

  其他模型也類似,紛紛拋棄了Dropout和全連線層。

 

四、Dropout VS BatchNormalization

  我們需要做一個簡單實驗來驗證上述理論的成立,實驗分五種測試模型:

  • 沒有使用Dropout,也沒有使用BN;
  • 使用了Dropout,不使用BN,使訓練單元為0的概率為0.2;
  • 使用了Dropout,不使用BN,使訓練單元為0的概率為0.5;
  • 使用了Dropout,不使用BN,使訓練單元為0的概率為0.8;
  • 使用了BN,不使用Dropout

  程式碼如下:

import keras
from keras.datasets import cifar10
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten, BatchNormalization
from keras.layers import Conv2D, MaxPooling2D
from matplotlib import pyplot as plt
import numpy as np

# 為保證公平起見,使用相同的隨機種子
np.random.seed(7)
batch_size = 32
num_classes = 10
epochs = 40
data_augmentation = True

# The data, split between train and test sets:
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

# Convert class vectors to binary class matrices.
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

def model(bn=False, dropout=False, level=0.5):
    model = Sequential()
    model.add(Conv2D(32, (3, 3), padding='same', input_shape=x_train.shape[1:]))
    if bn:
        model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Conv2D(32, (3, 3)))
    if bn:
        model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    if dropout:
        model.add(Dropout(level))

    model.add(Conv2D(64, (3, 3), padding='same'))
    if bn:
        model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Conv2D(64, (3, 3)))
    if bn:
        model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    if dropout:
        model.add(Dropout(level))

    model.add(Flatten())
    model.add(Dense(512))
    if bn:
        model.add(BatchNormalization())
    model.add(Activation('relu'))
    if dropout:
        model.add(Dropout(level))
    model.add(Dense(num_classes))
    model.add(Activation('softmax'))
    if bn:
        opt = keras.optimizers.rmsprop(lr=0.001, decay=1e-6)
    else:
        opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)

    model.compile(loss='categorical_crossentropy',
                             optimizer=opt,
                             metrics=['accuracy'])

    # 使用資料增強獲取更多的訓練資料
    datagen = ImageDataGenerator(width_shift_range=0.1, height_shift_range=0.1, horizontal_flip=True)
    datagen.fit(x_train)
    history = model.fit_generator(datagen.flow(x_train, y_train, batch_size=batch_size), epochs=epochs,
                                  validation_data=(x_test, y_test), workers=4)
    return history


no_dropout_bn_history = model(False, False)
dropout_low_history = model(False, True, 0.2)
dropout_medium_history = model(False, True, 0.5)
dropout_high_history = model(False, True, 0.8)
bn_history = model(True, False)

# 比較多種模型的精確度
plt.plot(no_dropout_bn_history.history['val_acc'])
plt.plot(dropout_low_history.history['val_acc'])
plt.plot(dropout_medium_history.history['val_acc'])
plt.plot(dropout_high_history.history['val_acc'])
plt.plot(bn_history.history['val_acc'])
plt.title('Model accuracy')
plt.ylabel('Validation Accuracy')
plt.xlabel('Epoch')
plt.legend(['No bn and dropout', 'Dropout with 0.2', 'Dropout with 0.5', 'Dropout with 0.8', 'BN'], loc='lower right')
plt.grid(True)
plt.show()

# 比較多種模型的損失率
plt.plot(no_dropout_bn_history.history['val_loss'])
plt.plot(dropout_low_history.history['val_loss'])
plt.plot(dropout_medium_history.history['val_loss'])
plt.plot(dropout_high_history.history['val_loss'])
plt.plot(bn_history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['No bn and dropout', 'Dropout with 0.2', 'Dropout with 0.5', 'Dropout with 0.8', 'BN'], loc='upper right')
plt.grid(True)
plt.show()

  各模型的驗證準確率如下圖:

  各模型的驗證損失率如下:

  

  由上圖可知,Dropout在不同概率下,其表現差異較大,相對來說,Dropout with 0.2的表現接近於 No bn and dropout(可以理解為Dropout的keep-prob為1的版本)。總體來說,BN在準確率和損失率上表現要優於Dropout,比如準確率上BN能達到85%,而Dropout接近為79%。

 

五、結論

  無論是理論上的分析,還是現代深度模型的演變,或者是實驗的結果,BN技術已顯示出其優於Dropout的正則化效果,我們也是時候放棄Dropout,投入BN的懷抱了。