本文介绍使用CNN卷积神经网络完成猫狗图像识别,数据集来源于kaggle:[猫狗分类数据集]。主要包括以下三部分:
- 数据创建与预处理
- 神经网络模型搭建
- 数据增强实现减小正则化
数据处理
数据集包含25000张图片,猫和狗各有12500张;创建每个类别1000个样本的训练集、500个样本的验证集和500个样本的测试集(只使用部分数据进行建模)
import os
import shutilcurrent_dir = %pwd
current_dir #当前目录
base_dir = current_dir + '/cats_dogs_small'
os.mkdir(base_dir)#建立本次数据文件夹
train_dir = os.path.join(base_dir,'train')
os.mkdir(train_dir)#创建训练集val_dir = os.path.join(base_dir,'val')
os.mkdir(val_dir)#创建验证集test_dir = os.path.join(base_dir,'test')
os.mkdir(test_dir)#创建测试集
train_dir_cat = os.path.join(train_dir,'cat')
os.mkdir(train_dir_cat)#训练集猫train_dir_dog = os.path.join(train_dir,'dog')
os.makedirs(train_dir_dog)#训练集狗val_dir_cat = os.path.join(val_dir,'cat')
os.mkdir(val_dir_cat)#验证集猫val_dir_dog = os.path.join(val_dir,'dog')
os.mkdir(val_dir_dog)#验证集狗test_dir_cat = os.path.join(test_dir,'cat')
os.mkdir(test_dir_cat)#测试集猫test_dir_dog = os.path.join(test_dir,'dog')
os.mkdir(test_dir_dog)#测试集狗
将原有数据复制进创建好的文件中
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]for name in fnames:src = os.path.join('../猫狗/cats_dogs/',name)dst = os.path.join(train_dir_cat,name)shutil.copyfile(src,dst) #复制文件内容fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]for name in fnames:src = os.path.join('../猫狗/cats_dogs/',name)dst = os.path.join(train_dir_dog,name)shutil.copyfile(src,dst) #复制文件内容fnames = ['cat.{}.jpg'.format(i) for i in range(1000,1500)]for name in fnames:src = os.path.join('../猫狗/cats_dogs/',name)dst = os.path.join(val_dir_cat,name)shutil.copyfile(src,dst) #复制文件内容fnames = ['cat.{}.jpg'.format(i) for i in range(1000,1500)]for name in fnames:src = os.path.join('../猫狗/cats_dogs/',name)dst = os.path.join(test_dir_cat,name)shutil.copyfile(src,dst) #复制文件内容fnames = ['dog.{}.jpg'.format(i) for i in range(1500,2000)]for name in fnames:src = os.path.join('../猫狗/cats_dogs/',name)dst = os.path.join(val_dir_dog,name)shutil.copyfile(src,dst) #复制文件内容fnames = ['dog.{}.jpg'.format(i) for i in range(1500,2000)]for name in fnames:src = os.path.join('../猫狗/cats_dogs/',name)dst = os.path.join(test_dir_dog,name)shutil.copyfile(src,dst) #复制文件内容
显示图片内容
from PIL import Imagepath = os.path.join('../猫狗/cats_dogs_small/train/cat/cat.0.jpg')
Image.open(path)
或者
import matplotlib.pyplot as plt
path = os.path.join('../猫狗/cats_dogs_small/train/cat/cat.0.jpg')
img = plt.imread(path)
plt.imshow(img)
img.shape #打印图片大小:(374, 500, 3)
搭建神经网路
#导入需要的包
import tensorflow as tf
from keras import layers
from keras import models
#构建网络
model = models.Sequential()model.add(tf.keras.layers.Conv2D(32,(3,3),activation='relu',input_shape=(150,150,3))) #输入图片大小为(150,150,3)
model.add(tf.keras.layers.MaxPooling2D((2,2)))model.add(tf.keras.layers.Conv2D(64,(3,3),activation='relu'))
model.add(tf.keras.layers.MaxPooling2D((2,2)))model.add(tf.keras.layers.Conv2D(128,(3,3),activation='relu'))
model.add(tf.keras.layers.MaxPooling2D(2,2))model.add(tf.keras.layers.Conv2D(128,(3,3),activation='relu'))
model.add(tf.keras.layers.MaxPooling2D(2,2))model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(512,activation='relu'))
model.add(tf.keras.layers.Dense(1,activation='sigmoid'))model.summary()#打印网络结构
网络结构以及参数量
模型编译
from tensorflow.keras import optimizersmodel.compile(loss="binary_crossentropy",optimizer = optimizers.RMSprop(learning_rate=1e-4),metrics=['acc'])
#损失函数交叉熵损失函数,优化方法RMSprop,评价指标acc
数据预处理
数据输入到神经网络之前必须先转成浮点数张量。
keras有个处理图像的模块:keras.preprocessing.image
它包含ImageDataGenerator类,可以快速创建Python生成器,将图形文件处理成张量批量
from keras.preprocessing.image import ImageDataGeneratortrain_datagen = ImageDataGenerator(rescale=1./255)#进行缩放
test_datagen = ImageDataGenerator(rescale=1./255)train_generator = train_datagen.flow_from_directory(train_dir,#训练集所在目录target_size=(150,150),#将图片转换为目标大小batch_size=20,#每一批的数量class_mode='binary' # 损失函数是binary_crossentropy 所以使用二进制标签)valid_generator = test_datagen.flow_from_directory(val_dir,target_size=(150,150),batch_size=20,class_mode = 'binary'
)
for data_batch,labels_batch in train_genertor:print(data_batch.shape)print(labels_batch.shape)break(20, 150, 150, 3)
(20,)
生成器的输出是150-150的RGB图像和二进制标签,形状为(20,)组成的批量。每个批量包含20个样本(批量的大小)。
生成器会不断地生成这些批量,不断地循环目标文件夹中的图像。
keras模型使用fit_generator方法来拟合生成器的效果。模型有个参数steps_per_epoch参数:从生成器中抽取steps_per_epoch个批量后,拟合进入下一轮。
本例中:总共是2000个样本,每个批量是20个样本,所以需要100个批量
模型拟合
history = model.fit_generator(train_generator,#第一个参数必须是python生成器steps_per_epoch=100,#批量数epochs = 30,#迭代次数validation_data = valid_generator,#待验证的数据集validation_steps = 50
)
保存模型
model.save("cats_and_dogs_small.h5")
损失和精度曲线
import matplotlib.pyplot as plthistory_dict = history.history # 字典形式
for key, _ in history_dict.items():print(key)
#history.history保存以下四个参数
loss
acc
val_loss
val_acc
acc = history_dict["acc"]
val_acc = history_dict["val_acc"]loss = history_dict["loss"]
val_loss = history_dict["val_loss"]
epochs = range(1, len(acc)+1)# acc
plt.plot(epochs, acc, "bo", label="Training acc")
plt.plot(epochs, val_acc, "b", label="Validation acc")
plt.title("Training and Validation acc")
plt.legend()plt.figure()# loss
plt.plot(epochs, loss, "bo", label="Training loss")
plt.plot(epochs, val_loss, "b", label="Validation loss")
plt.title("Training and Validation loss")
plt.legend()
上传训练集和验证集的时候搞错了,,,出现了如下bug
小结:得到过拟合的结论
随着时间的增加,训练精度在不断增加,接近100%,而验证精度则停留在70%
验证的损失差不多在第6轮后达到最小值,后面一定轮数内保持不变,训练的损失一直下降,直接接近0
数据增强
数据增强也是解决过拟合的一种方法,另外两种是:
- dropout
- 权重衰减正则化
什么是数据增强:从现有的训练样本中生成更多的训练数据,利用多种能够生成可信图像的随机变化来增加数据样本。
模型在训练时候不会查看两个完全相同的图像
设置数据增强
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" # 用于填充新创建像素的方法
)
显示增强后图像
from keras.preprocessing import imagefnames = [os.path.join(train_dir_cat,fname) for fname in os.listdir(train_dir_cat)]img_path = fnames[3]img_path #打印图形路径img = image.load_img(img_path,target_size=(150,150))x = image.img_to_array(img)x.shape #(150,150,3)x = x.reshape((1,) + x.shape)x.shape #(1,150,150,3)i = 0
for batch in datagen.flow(x, batch_size=1): # 生成随机变换后的图像批量plt.figure() imgplot = plt.imshow(image.array_to_img(batch[0]))i += 1if i % 4 == 0:break # 循环是无限,需要在某个时刻终止plt.show()
包含Dropout层的新卷积神经网络
数据增强来训练网络的话,网络不会看到两次相同的输入。但是输入仍是高度相关的,不能完全消除过拟合。
可以考虑添加一个Dropout层,添加到密集分类连接器之前
model = models.Sequential()model.add(tf.keras.layers.Conv2D(32,(3,3),activation='relu',input_shape=(150,150,3)))
model.add(tf.keras.layers.MaxPooling2D((2,2)))model.add(tf.keras.layers.Conv2D(64,(3,3),activation='relu'))
model.add(tf.keras.layers.MaxPooling2D((2,2)))model.add(tf.keras.layers.Conv2D(128,(3,3),activation='relu'))
model.add(tf.keras.layers.MaxPooling2D(2,2))model.add(tf.keras.layers.Conv2D(128,(3,3),activation='relu'))
model.add(tf.keras.layers.MaxPooling2D(2,2))model.add(tf.keras.layers.Flatten())model.add(tf.keras.layers.Dropout(0.5))#新加的dropout层model.add(tf.keras.layers.Dense(512,activation='relu'))
model.add(tf.keras.layers.Dense(1,activation='sigmoid'))model.summary()
再次训练
# 训练数据的增强
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.0 / 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"
)# 优化:报错有修改
history = model.fit_generator(train_generator,# 原文 steps_per_epoch=100,steps_per_epoch=63, # steps_per_epoch=2000/32≈63 取上限epochs=100,validation_data=validation_generator,# 原文 validation_steps=50validation_steps=32 # validation_steps=1000/32≈32
)
模型保存
model.save("cats_and_dogs_small_2.h5")
损失和精度曲线
history_dict = history.history # 字典形式acc = history_dict["acc"]
val_acc = history_dict["val_acc"]
loss = history_dict["loss"]
val_loss = history_dict["val_loss"]
具体绘图代码
epochs = range(1, len(acc)+1)# acc
plt.plot(epochs, acc, "bo", label="Training acc")
plt.plot(epochs, val_acc, "b", label="Validation acc")
plt.title("Training and Validation acc")
plt.legend()plt.figure()# loss
plt.plot(epochs, loss, "bo", label="Training loss")
plt.plot(epochs, val_loss, "b", label="Validation loss")
plt.title("Training and Validation loss")
plt.legend()plt.show()
结论:在使用了数据增强之后,模型不再拟合,训练集曲线紧跟着验证曲线;而且精度也变为81%,相比未正则之前得到了提高。