首先小小的庆祝一下自己的微信公众号订阅用户已达到100人了,是小小的虚荣心也是收获也是鞭策,希望自己砥砺前行,努力进步,做到更好。
其实写文章,比较难的是对知识的沉淀,这个沉淀有多种,代码的沉淀,一件事物理解上的沉淀,甚至整合代码和文章的沉淀,要考虑他人的感受,也要在自己的能力范围内进行控制等等不一而足。
本笔记是经典的猫狗识别问题,大概25000的训练集图片加上12500的测试集图片,猫狗图片各一半,共约700多M,而之前的手写数字也不过10M多一些,对计算机处理而言都是比较耗资源的,何况这个实验的目的本来就是从小样本提升准确率。从实质上这些模型和前面的模型没有本质区别,唯一的区别是ImageDataGenerator的应用,能够把图片直接转换为浮点数向量。
可视化训练集和验证集的损失率和准确率
可以看出随着训练轮次的增加,
训练集的准确率呈对数级上升,而验证集的准确率则在第十轮左右维持在72%的准确率
训练集的损失度呈对数级下降,而验证集的损失度则在第十轮左右最低,此后不断上升
因此本例子主要还是过度拟合导致,根本原因是样本数量不足,只有2000训练集样本
在增加dropout层以后,训练准确率较之前有所下降,但验证准确率较之前有所提升达到75%。
在利用数据增强生成器训练卷积神经网络后,训练集和验证集的准确率基本是同步的,最高上升到78%
代码示例
代码语言:javascript复制import os, shutil
def initdata():
#原始数据集解压目录的两个路径
original_dataset_train_dir = 'D:/Python36/Coding/PycharmProjects/ttt/dataset/train'
original_dataset_test_dir = 'D:/Python36/Coding/PycharmProjects/ttt/dataset/test'
#保存较小数据集的目录
base_dir = 'D:/Python36/Coding/PycharmProjects/ttt/dataset/small'
os.mkdir(base_dir)
#分别对应划分后的训练、验证和测试的目录
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)
#猫的训练图像目录
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
#狗的训练图像目录
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)
#猫的验证图像目录
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)
#狗的验证图像目录
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)
#猫的测试图像目录
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
#狗的测试图像目录
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)
#将前1000 张猫的图像复制到train_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_train_dir, fname)
dst = os.path.join(train_cats_dir, fname)
shutil.copyfile(src, dst)
#将接下来500张猫的图像复制到validation_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_train_dir, fname)
dst = os.path.join(validation_cats_dir, fname)
shutil.copyfile(src, dst)
#将接下来的500 张猫的图像复制到test_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_train_dir, fname)
dst = os.path.join(test_cats_dir, fname)
shutil.copyfile(src, dst)
#将前1000 张狗的图像复制到train_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_train_dir, fname)
dst = os.path.join(train_dogs_dir, fname)
shutil.copyfile(src, dst)
#将接下来500张狗的图像复制到validation_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_train_dir, fname)
dst = os.path.join(validation_dogs_dir, fname)
shutil.copyfile(src, dst)
#将接下来的500 张狗的图像复制到test_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_train_dir, fname)
dst = os.path.join(test_dogs_dir, fname)
shutil.copyfile(src, dst)
#检查一下,看看每个分组(训练/ 验证/ 测试)中分别包含多少张图像。
print('total training cat images:', len(os.listdir(train_cats_dir)))
# total training cat images: 1000
print('total training dog images:', len(os.listdir(train_dogs_dir)))
# total training dog images: 1000
print('total validation cat images:', len(os.listdir(validation_cats_dir)))
# total validation cat images: 500
print('total validation dog images:', len(os.listdir(validation_dogs_dir)))
# total validation dog images: 500
print('total test cat images:', len(os.listdir(test_cats_dir)))
# total test cat images: 500
print('total test dog images:', len(os.listdir(test_dogs_dir)))
# total test dog images: 500
#即整体目录和图片数量如下
# small
# test
# cats 500
# dogs 500
# train
# cats 1000
# dogs 1000
# validation
# cats 1000
# dogs 1000
return train_dir,validation_dir
from keras.preprocessing.image import ImageDataGenerator
#数据预处理
def previewprocess(train_dir,validation_dir):
#(1) 读取图像文件。
#(2) 将JPEG 文件解码为RGB 像素网格。
#(3) 将这些像素网格转换为浮点数张量。
#(4) 将像素值(0~255 范围内)缩放到[0, 1] 区间(正如你所知,神经网络喜欢处理较小的输入值)。
train_datagen = ImageDataGenerator(rescale=1./255) #将所有图像乘以1/255 缩放
test_datagen = ImageDataGenerator(rescale=1./255) #将所有图像乘以1/255 缩放
# Found 2000 images belonging to 2 classes.
# Found 1000 images belonging to 2 classes.
# 它生成了150×150的RGB图像[形状为(20, 150, 150, 3)]与二进制标签[形状为(20, )]组成的批量。
# 每个批量中包含20个样本(批量大小)
train_generator = train_datagen.flow_from_directory(
train_dir,
target_size=(150, 150), #将所有图像的大小调整为150×150
batch_size=20,
class_mode='binary') #因为使用了binary_crossentropy损失,所以需要用二进制标签
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150), #将所有图像的大小调整为150×150
batch_size=20,
class_mode='binary') #因为使用了binary_crossentropy损失,所以需要用二进制标签
#train_generator和validation_generator数据格式如下:即4维数组和一个目标标签数组
#array([[[[0.26666668, 0.27058825, 0.2509804],
# [0.26666668, 0.27058825, 0.2509804],
# [0.26666668, 0.27058825, 0.2509804],
# ...,
# [0.6745098, 0.67058825, 0.6627451],
# [0.65882355, 0.654902, 0.64705884],
# [0.64705884, 0.6431373, 0.63529414]]]],
# dtype=float32),
#array([0., 1., 0., 0., 1., 1., 1., 0., 0., 1., 1., 1., 1., 0., 1., 0., 1., 1., 1., 0.],
# type=float32))
#begin time= Fri May 17 14:43:46 2019
for data_batch, labels_batch in train_generator:
print('data batch shape:', data_batch.shape)
#data batch shape: (20, 150, 150, 3)
print('labels batch shape:', labels_batch.shape)
break
return train_generator,validation_generator
def enhenceimage(train_dir,validation_dir):
#利用ImageDataGenerator来设置数据增强
'''
datagen = ImageDataGenerator(
rotation_range=40, #是角度值(在 0~180 范围内),表示图像随机旋转的角度范
width_shift_range=0.2, #是图像在水平或垂直方向上平移的范围(相对于总宽度或总高度的比例
height_shift_range=0.2, #是图像在水平或垂直方向上平移的范围(相对于总宽度或总高度的比例
shear_range=0.2, #随机错切变换的角度
zoom_range=0.2, #是图像随机缩放的范围
horizontal_flip=True, #是随机将一半图像水平翻转
fill_mode='nearest') #是用于填充新创建像素的方法,这些新像素可能来自于旋转或宽度/高度平移
'''
#利用数据增强生成器训练卷积神经网络
train_datagen = ImageDataGenerator(
rescale=1. / 255,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
train_dir,
target_size=(150, 150),
batch_size=32,
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=32,
class_mode='binary')
return train_generator,validation_generator
from keras import layers
from keras import models
def buildmodel():
#构建模型
model = models.Sequential()
#4个卷积层和4个池化层以及2个全连接层
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
print(model.summary())
return model
def buildmodelwithdropout():
#构建模型
model = models.Sequential()
#4个卷积层和4个池化层以及2个全连接层
model.add(layers.Conv2D(32, (3, 3), activation='relu',input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
print(model.summary())
return model
from keras import optimizers
# initdata()
train_generator = 'D:/Python36/Coding/PycharmProjects/ttt/dataset/small/train'
validation_generator = 'D:/Python36/Coding/PycharmProjects/ttt/dataset/small/validation'
train_generator,validation_generator=previewprocess(train_generator,validation_generator)
model=buildmodel()
modelwithdropout=buildmodelwithdropout()
#Layer (type) Output Shape Param #
#=================================================================
#conv2d_1 (Conv2D) (None, 148, 148, 32) 896
#_________________________________________________________________
#max_pooling2d_1 (MaxPooling2 (None, 74, 74, 32) 0
#_________________________________________________________________
#conv2d_2 (Conv2D) (None, 72, 72, 64) 18496
#_________________________________________________________________
#max_pooling2d_2 (MaxPooling2 (None, 36, 36, 64) 0
#_________________________________________________________________
#conv2d_3 (Conv2D) (None, 34, 34, 128) 73856
#_________________________________________________________________
#max_pooling2d_3 (MaxPooling2 (None, 17, 17, 128) 0
#_________________________________________________________________
#conv2d_4 (Conv2D) (None, 15, 15, 128) 147584
#_________________________________________________________________
#max_pooling2d_4 (MaxPooling2 (None, 7, 7, 128) 0
#_________________________________________________________________
#flatten_1 (Flatten) (None, 6272) 0
#_________________________________________________________________
#dense_1 (Dense) (None, 512) 3211776
#_________________________________________________________________
#dense_2 (Dense) (None, 1) 513
#=================================================================
# 使用RMSprop 优化器。
# 因为网络最后一层是单一sigmoid单元,所以我们将使用二元交叉熵作为损失函数
#为模型选择正确的最后一层激活和损失函数
#问题类型 最后一层激活 损失函数
#二分类问题 sigmoid binary_crossentropy
#多分类、单标签问题 softmax categorical_crossentropy
#多分类、多标签问题 sigmoid binary_crossentropy
#回归到任意值 无 mse
#回归到0~1 范围内的值 sigmoid mse 或binary_crossentropy
model.compile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])
history = model.fit_generator(train_generator,steps_per_epoch=100,epochs=30,validation_data=validation_generator,validation_steps=50)
#保存模型
model.save('cats_and_dogs_small_1.h5')
#编译、训练、保存带dropout模式的模型
modelwithdropout.compile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])
historywithdropout = modelwithdropout.fit_generator(train_generator,steps_per_epoch=100,epochs=30,validation_data=validation_generator,validation_steps=50)
#保存模型
modelwithdropout.save('cats_and_dogs_small_1_withdropout.h5')
#和上面同样的模型,只是增强了图片样本
train_generator = 'D:/Python36/Coding/PycharmProjects/ttt/dataset/small/train'
validation_generator = 'D:/Python36/Coding/PycharmProjects/ttt/dataset/small/validation'
train_generator,validation_generator=enhenceimage(train_generator,validation_generator)
modelwithenhencedropout=buildmodelwithdropout()
modelwithenhencedropout.compile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])
historywithenhencedropouthistory = modelwithenhencedropout.fit_generator(train_generator,steps_per_epoch=100,epochs=30,validation_data=validation_generator,validation_steps=50)
modelwithenhencedropout.save('cats_and_dogs_small_1_withehencedropout.h5')
#可视化训练集和验证集的损失率和准确率
# 可以看出随着训练轮次的增加,
# 训练集的准确率呈对数级上升,而验证集的准确率则在第十轮左右维持在72%的准确率
# 训练集的损失度呈对数级下降,而验证集的损失度则在第十轮左右最低,此后不断上升
# 因此本例子主要还是过度拟合导致,根本原因是样本数量不足,只有2000训练集样本
# 在增加dropout层以后,训练准确率较之前有所下降,但验证准确率较之前有所提升达到75%。
# 在利用数据增强生成器训练卷积神经网络后,训练集和验证集的准确率基本是同步的,最高上升到78%
#150s 2s/step - loss: 0.0340 - acc: 0.9900 - val_loss: 1.1205 - val_acc: 0.7190
#153s 2s/step - loss: 0.2005 - acc: 0.9245 - val_loss: 0.6639 - val_acc: 0.7500
#244s 2s/step - loss: 0.4792 - acc: 0.7687 - val_loss: 0.4896 - val_acc: 0.7494
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
accwithdropout = historywithdropout.history['acc']
val_accwithdropout = historywithdropout.history['val_acc']
losswithdropout = historywithdropout.history['loss']
val_losswithdropout = historywithdropout.history['val_loss']
accwithenhencedropout = historywithenhencedropouthistory.history['acc']
val_accwithenhencedropout = historywithenhencedropouthistory.history['val_acc']
losswithenhencedropout = historywithenhencedropouthistory.history['loss']
val_losswithenhencedropout = historywithenhencedropouthistory.history['val_loss']
epochs = range(1, len(acc) 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.plot(epochs, accwithdropout, 'bo', label='Training acc withdropout',color='red')
plt.plot(epochs, val_accwithdropout, 'b', label='Validation acc withdropout',color='red')
plt.plot(epochs, accwithenhencedropout, 'bo', label='Training acc withenhencedropout',color='yellow')
plt.plot(epochs, val_accwithenhencedropout, 'b', label='Validation acc withenhencedropout',color='yellow')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.plot(epochs, losswithdropout, 'bo', label='Training loss withdropout',color='red')
plt.plot(epochs, val_losswithdropout, 'b', label='Validation loss withdropout',color='red')
plt.plot(epochs, losswithenhencedropout, 'bo', label='Training loss withenhencedropout',color='yellow')
plt.plot(epochs, val_losswithenhencedropout, 'b', label='Validation loss withenhencedropout',color='yellow')
plt.title('Training and validation loss')
plt.legend()
plt.show()