深度学习基础系列(七)| 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对于优化神经网络,加快训练速度甚至在提升准确度、下降损失度方面都能发挥积极做用,固然想要取得理想的效果也得须要反复地尝试各类组合(好比上述的例子,若是去掉数据加强技术,在个人测试结果,显示测试集的损失度反而更高,过拟合更严重)。

相关文章
相关标签/搜索