首先注册kaggle并下载dogs-vs-cats数据集大概800M【最好使用科学上网,速度较快】,将其解压到指定路径(注意要逐层解压至文件夹形式)
#os.mkdir()为创建文件夹【执行一次后应当将其注释掉只留后面可能用到的一些路径配置】
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pylab
from pandas import DataFrame, Series
from keras import models, layers, optimizers, losses, metrics
from keras.utils.np_utils import to_categorical
import os,shutil
#-------------数据划分-------------------------------
# 原始数据集解压目录的路径
original_dataset_dir = 'F:/dogs-vs-cats/train/'
#保存较小数据集的目录
base_dir = 'F:/dogs-vs-cats/cats_and_dogs_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_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_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_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_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_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_dir, fname)
# dst = os.path.join(test_dogs_dir, fname)
# shutil.copyfile(src, dst)
#------------------------------------------------------
#每个分组中两个类别的样本数相同,这是一个平衡的二分类问题,分类精度可作为衡量成功的指标
#构建网络
#---------------------------------------
#卷积神经网络由 Conv2D 层(使用 relu 激活)和 MaxPooling2D 层交替堆叠构成
#由于这里要处理的是更大的图像和更复杂的问题,需要相应地增大网络,即再增
# 加一个 Conv2D+MaxPooling2D 的组合。
# 这既可以增大网络容量,也可以进一步减小特征图的尺寸, 使其在连接 Flatten
# 层时尺寸不会太大。本例中初始输入的尺寸为 150×150(有些随意的选择),所
# 以最后在 Flatten 层之前的特征图大小为 7×7
#-----------------------------------------
#注意 网络中特征图的深度在逐渐增大(从 32 增大到 128),而特征图的尺寸在逐渐减小(从 150×150 减小到 7×7)。这几乎是所有卷积神经网络的模式。
def model_build():
model = models.Sequential()
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'))
#二分类问题,你面对的是一个二分类问题,所以网络最后一层是使用 sigmoid 激活的单一单元(大小为 1 的 Dense 层)。输出为某一类的概率
print(model.summary())
#编译模型
model.compile(optimizer=optimizers.RMSprop(lr=1e-4),
loss='binary_crossentropy',
metrics=['acc'])
return model
#数据预处理
#---------------------------------------------------------
#将数据输入神经网络之前,应该将数据格式化为经过预处理的浮点数张量
#现在,数据以 JPEG 文件的形式保存在硬盘中,所以数据预处理步骤大致如下。
# (1) 读取图像文件。
# (2) 将 JPEG 文件解码为RGB像素网格。
# (3) 将这些像素网格转换为浮点数张量。
# (4) 将像素值(0~255 范围内)缩放到 [0, 1] 区间(正如你所知,神经网络喜欢处理较小的输入值)。
#Keras 拥有自动完成这些步骤的工具。
# Keras 有一个图像处理辅助工具的模块,位于 keras.preprocessing.image
#特别地,它包含 ImageDataGenerator 类,可以快速创建 Python生成器,
# 能够将硬盘上的图像文件自动转换 为预处理好的张量批量。
#---------------------------------------------------------
from keras.preprocessing.image import ImageDataGenerator
def model_fit1(model):
train_datagen=ImageDataGenerator(rescale=1./255)#将所有图像乘以 1/255 缩放至[0,1]
test_datagen=ImageDataGenerator(rescale=1./255)
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),
batch_size=20,
class_mode='binary'
)
'''
Python 生成器(Python generator)是一个类似于迭代器的对象,一个可以和 for ...
in 运算符一起使用的对象。生成器是用 yield 运算符来构造的。 下面一个生成器的例子,可以生成整数。
def generator():
i=0
while True:
i += 1 yield i
for item in generator():
print(item)
if item > 4:
break
输出结果如下。 1/n 2/n 3/n 4/n 5/n
'''
for data_batch,labels_batch in train_generator:
print(data_batch.shape)
print(labels_batch.shape)
break#生成器会不停地生成这些批量,它会不断循环目标文件夹中的图像
#它生成了 150×150 的 RGB 图像[形状为 (20, 150, 150,3)]与二进制标签[形状为 (20,)]组成的批量。每个批量中包含 20 个样本(批量大小)
#下面我们利用生成器,我们让模型对数据进行拟合
#fit_generator 方法来拟合,它在数据生成器上的效果和 fit 相同
history=model.fit_generator(
train_generator,
steps_per_epoch=100,
#从生成器中抽取 steps_per_epoch个批量后(即运行了 steps_per_epoch
#次梯度下降),拟合过程将进入下一个轮次[本例共有1000+1000个样本,
# 前面训练数据生成器的batch=20,故这里为100]
epochs=30,
validation_data=validation_generator,
#参数可以是一个数据生成器,但也可以是 Numpy 数组组成的元组
#如果参数为生成器,那么这个生成器会不停地生成验证数据批量,
# 因此还需要指定 validation_steps 参数与前面类似500+500 /20
validation_steps=50
)
#保存模型
model.save('cats_and_dogs_small_1.h5')
return history
def acc_loss_plot(history):
fig=plt.figure()
ax1=fig.add_subplot(2,1,1)
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
ax1.plot(epochs, acc, 'bo', label='Training acc')
ax1.plot(epochs, val_acc, 'b', label='Validation acc')
ax1.set_title('Training and validation accuracy')
ax2=fig.add_subplot(2,1,2)
ax2.plot(epochs, loss, 'bo', label='Training loss')
ax2.plot(epochs, val_loss, 'b', label='Validation loss')
ax2.set_title('Training and validation loss')
plt.legend()
plt.tight_layout()
plt.show()
#训练精度随着时间线性增加,直到接近 100%,而验 证精度则停留在 70%~72%。验证损失仅在 5 轮后就达到最小值,然后保持不变,而训练损失则 一直线性下降,直到接近于 0
#因为训练样本相对较少(2000个),所以最关心的问题是过拟合。这里不使用dropout 和权重衰减(L2 正则化等,而是针对计算机视觉领域的新方法-----数据增强
#利用ImageDataGenerator设置数据增强
#数据增强
#------------------------------------------------------
# 是从现有的训练样本中生成更多的训练数据,其方法是利用多种能够生成可信图像的随机变换来增加(augment)样本。其目标是,模型在训练时不会两次查看完全相同的图像。这让模型能够观察到数据的更多内容,从而具有更好的泛化能力。
'''
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 image
fnames=[os.path.join(train_cats_dir,fname) for fname in os.listdir(train_cats_dir)]#获取猫的训练集数据路径
img_path=fnames[3]
img=image.load_img(img_path,target_size=(150,150))#读取图像并调整大小
x=image.img_to_array(img)#将其转换为形状 (150, 150, 3) 的 Numpy 数组
x=x.reshape((1,)+x.shape)#将其形状改变为(1, 150, 150, 3)
i=0
fig2=plt.figure()
for batch in datagen.flow(x, batch_size=1):#生成随机变换后的图像批量。4个
ax=fig2.add_subplot(2,2,i+1)
imgplot = ax.imshow(image.array_to_img(batch[0]))
i += 1
if i % 4 == 0:
break
plt.show()
'''
#如果你使用这种数据增强来训练一个新网络,那么网络将不会
# 两次看到同样的输入。但网络看到的输入仍然是高度相关的
# ,因为这些输入都来自于少量的原始图像。你无法生成新信
# 息, 而只能混合现有信息。因此,这种方法可能不足以完全
# 消除过拟合。
# 为了进一步降低过拟合,你还需要向模型中添加一个
# Dropout 层,添加到密集连接分类器之前
#--------------------------------------------------------
#利用数据增强生成器训练卷积神经网络
def model_fit2(model,train_dir,validation_dir):
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,
fill_mode='nearest'#默认
)
test_datagen=ImageDataGenerator(rescale=1./255)#注意,不能增强验证数据
train_generator=train_datagen.flow_from_directory(
train_dir,
target_size=(150,150),#将所有图像的大小调整为 150×150
batch_size=32,
class_mode='binary'#因为使用了 binary_crossentropy,损失,所以需要用二进制标签
)
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,
epochs=100,
validation_data=validation_generator,
validation_steps=50
)
model.save('cats_and_dogs_small_2.h5')
return history
# model=model_build()
# history=model_fit2(model,train_dir,validation_dir)
# acc_loss_plot(history)#得到了82%的精度,并且模型不再过拟合:训练曲线紧紧跟随着验证曲线