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


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

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

  但為什么BN能夠如此有效?讓我們來一探究竟。

一、Covariate Shift

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

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

  很明顯,這部分數據的玫瑰花各種顏色都有,其特征分布與上述數據集是不一樣的。通過下圖我們可以再比較下:

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

  為什么這么說呢?輸入值的分布不同,也可以理解為輸入特征值的scale差異較大,與權重進行矩陣相乘后,會產生一些偏離較大地差異值;而深度學習網絡需要通過訓練不斷更新完善,那么差異值產生的些許變化都會深深影響后層,偏離越大表現越為明顯;因此,對於反向傳播來說,這些現象都會導致梯度發散,從而需要更多的訓練步驟來抵消scale不同帶來的影響,也需要更多地步驟才能最終收斂。

  而BN的作用就是將這些輸入值進行標准化,降低scale的差異至同一個范圍內。這樣做的好處在於一方面提高梯度的收斂程度,加快訓練速度;另一方面使得每一層可以盡量面對同一特征分布的輸入值,減少了變化帶來的不確定性,也降低了對后層網路的影響,各層網路變得相對獨立。

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

 二、Batch Normalization

  BN的計算公式如下圖所示:

  簡單地說,通過計算均值和方差后,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激活函數在深層網絡變成可能,並且在實際應用中β和γ是可以學習的,下圖是一個直觀的解釋圖。

  

三、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模型。

四、結論

  BN對於優化神經網絡,加快訓練速度甚至在提高准確度、降低損失度方面都能發揮積極作用,當然想要取得理想的效果也得需要反復地嘗試各種組合(比如上述的例子,如果去掉數據增強技術,在我的測試結果,顯示測試集的損失度反而更高,過擬合更嚴重)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM