批标准化介绍:
在训练的时候,前面层训练参数的更新将导致后面层输入数据分布的变化。以网络第二层为例:网络的第二层输入,是由第一层的参数和input计算得到的,而第一层的参数在整个训练过程中一直在变化,因此必然会引起后面每一层输入数据分布的改变。
假如激活函数是sigmoid函数,如果数据在梯度很小的区域,那么学习率就会很慢甚至陷入长时间的停滞。如果上一层传递下来的数据分布为如下图:
那么将会出现梯度消失的问题,同样如果全分布在右侧的或会出现梯度爆炸的问题。
为此提出了一种方法批标准化(BN),思想是先将数据的分布改成围绕0轴的数据,这样就不担心出现梯度消失或者爆炸问题。
具体的先将数据进行标准化
减均值除方差后,数据就被移到中心区域如a右图所示。
减均值除方差得到的分布是正态分布,我们能否认为正态分布就是最好或最能体现我们训练样本的特征分布呢?不能,比如数据本身就很不对称,或者激活函数未必是对方差为1的数据最好的效果,比如Sigmoid激活函数,在-1~1之间的梯度变化不大,那么非线性变换的作用就不能很好的体现,换言之就是,减均值除方差操作后可能会削弱网络的性能!
为此进行尺度变换和偏移。将x乘以γ调整数值大小,再加上β增加偏移后得到y,这里的γ是尺度因子,β是平移因子。这一步是BN的精髓,由于归一化后的xi基本会被限制在正态分布下,使得网络的表达能力下降。为解决该问题,我们引入两个新的参数:γ,β。 γ和β是在训练时网络自己学习得到的。
y即为批标准化后的数据。
import pathlib
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
%matplotlib inline
data_dir='./DataSet/airplane_lake'
data_root=pathlib.Path(data_dir)
all_image_path=list(data_root.glob('*/*'))
all_image_path=[str(path) for path in all_image_path] #将所有的路径对象转化成字符串类型
import random
random.shuffle(all_image_path) #将数据打乱,原来上半部分是plane图片,下半部分是lake图片
image_count=len(all_image_path)
label_name=sorted(item.name for item in data_root.glob('*/')) #找到所有父目录名,目录名为标签
label_to_index=dict((name,index) for index,name in enumerate(label_name))#给标签编号{0,1}
all_image_label=[label_to_index[pathlib.Path(p).parent.name]for p in all_image_path]
#最后获取每个数据的标签,方法为获取数据的父目录名字,再转化为编号。
#读取一张图片,返回归一化后的图片
def load_preprosess_image(path):
img_raw=tf.io.read_file(path)
img_tensor=tf.image.decode_jpeg(img_raw,channels=3)
img_tensor=tf.image.resize_with_crop_or_pad(img_tensor,256,256)
img_tensor=tf.cast(img_tensor,tf.float32)
img=img_tensor/255
return img
#tf.data 导入数据集
path_ds=tf.data.Dataset.from_tensor_slices(all_image_path)
image_dataset=path_ds.map(load_preprosess_image)
label_dataset=tf.data.Dataset.from_tensor_slices(all_image_label)
dataset=tf.data.Dataset.zip((image_dataset,label_dataset)) #合并数据与标签
#dataset 划分数据集
test_count=int(image_count*0.2)
train_count=image_count-test_count
train_dataset=dataset.skip(test_count)
test_dataset=dataset.take(test_count)
BATCH_SIZE=32
train_dataset=train_dataset.repeat().shuffle(100).batch(BATCH_SIZE) #先shuffle再batch
test_datset=test_dataset.batch(BATCH_SIZE)
#建立模型
model=tf.keras.Sequential()
model.add(tf.keras.layers.Conv2D(64,(3,3),input_shape=(256,256,3)))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Conv2D(64,(3,3)))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.MaxPool2D())
model.add(tf.keras.layers.Conv2D(128,(3,3)))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Conv2D(128,(3,3)))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.GlobalAveragePooling2D())
model.add(tf.keras.layers.Dense(128))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Dense(1,activation='sigmoid'))
model.summary()
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['acc'])
steps_per_epoch=train_count//BATCH_SIZE
validation_steps=test_count//BATCH_SIZE
history=model.fit(train_dataset,
epochs=30,
steps_per_epoch=steps_per_epoch,
validation_data=test_datset,
validation_steps=validation_steps)
plt.plot( history.epoch, history.history.get('acc'),label='acc')
plt.plot( history.epoch, history.history.get('val_acc'),label='val_acc')
plt.legend()
plt.plot( history.epoch, history.history.get('loss'),label='loss')
plt.plot( history.epoch, history.history.get('val_loss'),label='val_loss')
plt.legend()