深度學習基礎系列(七)| Batch Normalization

  Batch Normalization(批量標準化,簡稱BN)是近些年來深度學習優化中一個重要的手段。BN能帶來以下優勢:網絡

  • 加速訓練過程;
  • 可使用較大的學習率;
  • 容許在深層網絡中使用sigmoid這種易致使梯度消失的激活函數;
  • 具備輕微地正則化效果,以此能夠下降dropout的使用。

  但爲何BN可以如此有效?讓咱們來一探究竟。dom

1、Covariate Shift

  Convariate shift是BN論文做者提出來的概念,其意是指具備不一樣分佈的輸入值對深度網絡學習的影響。舉個例子,假設咱們有一個玫瑰花的深度學習網絡,這是一個二分類的網絡,1表示識別爲玫瑰,0則表示非玫瑰花。咱們先看看訓練數據集的一部分:函數

  直觀來講,玫瑰花的特徵表現很明顯,都是紅色玫瑰花。 再看看訓練數據集的另外一部分:學習

  很明顯,這部分數據的玫瑰花各類顏色都有,其特徵分佈與上述數據集是不同的。經過下圖咱們能夠再比較下:測試

  圖中右側部分綠色圓圈指的是玫瑰,紅色打叉指的是非玫瑰,藍色線爲深度學習最後訓練出來的邊界。這張圖能夠更加直觀地比較出兩個數據集的特徵分佈是不同的,這種不同也就是所謂的covariate shift,而這種分佈不一致將減緩訓練速度。優化

  爲何這麼說呢?輸入值的分佈不一樣,也能夠理解爲輸入特徵值的scale差別較大,與權重進行矩陣相乘後,會產生一些偏離較大地差別值;而深度學習網絡須要經過訓練不斷更新完善,那麼差別值產生的些許變化都會深深影響後層,偏離越大表現越爲明顯;所以,對於反向傳播來講,這些現象都會致使梯度發散,從而須要更多的訓練步驟來抵消scale不一樣帶來的影響,也須要更多地步驟才能最終收斂。spa

  而BN的做用就是將這些輸入值進行標準化,下降scale的差別至同一個範圍內。這樣作的好處在於一方面提升梯度的收斂程度,加快訓練速度;另外一方面使得每一層能夠儘可能面對同一特徵分佈的輸入值,減小了變化帶來的不肯定性,也下降了對後層網路的影響,各層網路變得相對獨立。3d

  也許上述的解釋可能有些晦澀,讓咱們經過代碼和圖像來直觀理解。code

 2、Batch Normalization

  BN的計算公式以下圖所示:orm

  簡單地說,經過計算均值和方差後,mini-batch的數據進行標準化,再加上β和γ可使數據進行移動和縮放。

  咱們以某個CIFAR-10的數據爲例,經過BN的轉換來查看數據分佈的先後變化,如下爲示例代碼:

import numpy as np
import matplotlib.pyplot as plt
import tensorflow.keras.backend as K
from tensorflow import keras

(train_images, train_labels), (test_images, test_labels) = keras.datasets.cifar10.load_data()

# 輸入圖片尺寸爲(32, 32, 3),經flatten後大小爲(3072, 1)
x = train_images[0].reshape(-1) / 255
print("x:", x.shape)
# 假設咱們的隱藏層第一層的輸出爲(1024, 1),則反推權重大小爲(1024, 3072)
w = K.eval(K.random_normal_variable(shape=(1024, 3072), mean=0, scale=1))
print("w:", w.shape)
# 進行矩陣乘法獲得大小爲(1024, 1)的集合z
z = np.dot(w, x)
print("z:", z.shape)

a = K.constant(z)
# 求均值
mean = K.mean(a)
print("mean:", K.eval(mean))
var = K.var(a)
# 求方差
print("var:", K.eval(var))
# 對z進行batch normalization,gamma爲0表示不進行移動,beta爲0.25表示將normal後的值壓縮至1/4大小
a = K.eval(K.batch_normalization(a, mean, var, 0, 0.25))
# flatten normal值
a = a.reshape(-1)
print("batch_normal_a:", a.shape)

#以圖的方式直觀展現without_BN和with_BN的區別
p1 = plt.subplot(211)
p1.hist(z, 50, density=1, facecolor='g', alpha=0.75)
p1.set_title("data distribution without BN")
p1.set_xlabel('data range')
p1.set_ylabel('probability')
p1.grid(True)
#p1.axis([-4, 4, 0, 1])

p2 = plt.subplot(212)
p2.hist(a, 50, density=1, facecolor='g', alpha=0.75)
p2.set_title("data distribution with BN")
p2.set_xlabel('data range')
p2.set_ylabel('probability')
p2.grid(True)
#p2.axis([-4, 4, 0, 1])

plt.subplots_adjust(hspace=1)
plt.show()

  其圖像爲:

 

  從圖像分析可知,數據通過標準化後,其形狀保持大體不變,但尺寸被咱們壓縮至(-1, 1)之間,而原尺寸在(-80,80)之間。

  經過平移和縮放,BN可使數據被限定在咱們想要的範圍內,因此每層的輸出數據都進行BN的話,可使後層網絡面對穩定的輸入值,下降梯度發散的可能,從而加快訓練速度;同時也意味着容許使用大點的學習率,加快收斂過程。

  被縮放的數據讓使用sigmoid或tanh激活函數在深層網絡變成可能,而且在實際應用中β和γ是能夠學習的,下圖是一個直觀的解釋圖。

  

3、Batch Normalizaiotn的實際應用

  理論結合實踐才能肯定是否有用,讓咱們以keras舉例,看看BN是否能提升效率。

  

  上圖簡要地繪製了BN在神經網絡中的位置,在每層網絡的激活函數前。與前述例子不一樣之處在於,數據不是單個進行標準化,而是以mini batch集合的方式進行標準化。

  咱們經過下述代碼來比較和觀察without_BN模型和with_BN模型的差別:  

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 = 100
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

# without_BN模型的訓練
model_without_bn = Sequential()
model_without_bn.add(Conv2D(32, (3, 3), padding='same',
                            input_shape=x_train.shape[1:]))
model_without_bn.add(Activation('relu'))
model_without_bn.add(Conv2D(32, (3, 3)))
model_without_bn.add(Activation('relu'))
model_without_bn.add(MaxPooling2D(pool_size=(2, 2)))
model_without_bn.add(Dropout(0.25))

model_without_bn.add(Conv2D(64, (3, 3), padding='same'))
model_without_bn.add(Activation('relu'))
model_without_bn.add(Conv2D(64, (3, 3)))
model_without_bn.add(Activation('relu'))
model_without_bn.add(MaxPooling2D(pool_size=(2, 2)))
model_without_bn.add(Dropout(0.25))

model_without_bn.add(Flatten())
model_without_bn.add(Dense(512))
model_without_bn.add(Activation('relu'))
model_without_bn.add(Dropout(0.5))
model_without_bn.add(Dense(num_classes))
model_without_bn.add(Activation('softmax'))

opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)

model_without_bn.compile(loss='categorical_crossentropy',
                         optimizer=opt,
                         metrics=['accuracy'])
if not data_augmentation:
    history_without_bn = model_without_bn.fit(x_train, y_train,
                                          batch_size=batch_size,
                                          epochs=epochs,
                                          validation_data=(x_test, y_test),
                                          shuffle=True)
else:
    # 使用數據加強獲取更多的訓練數據
    datagen = ImageDataGenerator(width_shift_range=0.1, height_shift_range=0.1, horizontal_flip=True)
    datagen.fit(x_train)
    history_without_bn = model_without_bn.fit_generator(datagen.flow(x_train, y_train, batch_size=batch_size), epochs=epochs,
                                  validation_data=(x_test, y_test), workers=4)

# with_BN模型的訓練
model_with_bn = Sequential()
model_with_bn.add(Conv2D(32, (3, 3), padding='same', input_shape=x_train.shape[1:]))
model_with_bn.add(BatchNormalization())
model_with_bn.add(Activation('relu'))
model_with_bn.add(Conv2D(32, (3, 3)))
model_with_bn.add(BatchNormalization())
model_with_bn.add(Activation('relu'))
model_with_bn.add(MaxPooling2D(pool_size=(2, 2)))

model_with_bn.add(Conv2D(64, (3, 3), padding='same'))
model_with_bn.add(BatchNormalization())
model_with_bn.add(Activation('relu'))
model_with_bn.add(Conv2D(64, (3, 3)))
model_with_bn.add(BatchNormalization())
model_with_bn.add(Activation('relu'))
model_with_bn.add(MaxPooling2D(pool_size=(2, 2)))

model_with_bn.add(Flatten())
model_with_bn.add(Dense(512))
model_with_bn.add(BatchNormalization())
model_with_bn.add(Activation('relu'))
model_with_bn.add(Dense(num_classes))
model_with_bn.add(BatchNormalization())
model_with_bn.add(Activation('softmax'))

opt = keras.optimizers.rmsprop(lr=0.001, decay=1e-6)

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

if not data_augmentation:
    history_with_bn = model_without_bn.fit(x_train, y_train,
                                              batch_size=batch_size,
                                              epochs=epochs,
                                              validation_data=(x_test, y_test),
                                              shuffle=True)
else:
    # 使用數據加強獲取更多的訓練數據
    datagen = ImageDataGenerator(width_shift_range=0.1, height_shift_range=0.1, horizontal_flip=True)
    datagen.fit(x_train)
    history_with_bn = model_with_bn.fit_generator(datagen.flow(x_train, y_train, batch_size=batch_size), epochs=epochs,
                                             validation_data=(x_test, y_test), workers=4)

# 比較兩種模型的精確度
plt.plot(history_without_bn.history['val_acc'])
plt.plot(history_with_bn.history['val_acc'])
plt.title('Model accuracy')
plt.ylabel('Validation Accuracy')
plt.xlabel('Epoch')
plt.legend(['No Batch Normalization', 'With Batch Normalization'], loc='lower right')
plt.grid(True)
plt.show()

# 比較兩種模型的損失率
plt.plot(history_without_bn.history['loss'])
plt.plot(history_without_bn.history['val_loss'])
plt.plot(history_with_bn.history['loss'])
plt.plot(history_with_bn.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Training Loss without BN', 'Validation Loss without BN', 'Training Loss with BN', 'Validation Loss with BN'], loc='upper right')
plt.show()

  兩種模型的代碼差別主要爲兩點:

  • with_BN模型放棄了dropout函數,由於BN自己帶有輕微地正則效果
  • with_BN的學習率較without_BN模型放大了10倍

  本模型中,咱們使用了數據加強技術,咱們來看看最終的比較圖像:

 

  上圖顯示,測試數據集的精確度明顯with_BN模型(87%)要高於without_BN模型(77%)。從訓練速度來講,with_BN模型大概在第22代時已經很接近於最終收斂,而without_BN模型大概在第40代時接近於最終收斂,說明with_BN模型也會比較快。

  再比較看看損失度,明顯能夠看出不管是訓練集仍是測試集,with_BN模型要低於without_BN模型。

4、結論

  BN對於優化神經網絡,加快訓練速度甚至在提升準確度、下降損失度方面都能發揮積極做用,固然想要取得理想的效果也得須要反覆地嘗試各類組合(好比上述的例子,若是去掉數據加強技術,在個人測試結果,顯示測試集的損失度反而更高,過擬合更嚴重)。

相關文章
相關標籤/搜索