基于卷积神经网络的图像分类

猫狗数据集的两阶段分类实验(A、原始数据直接训练;B、数据增强后训练)

import keras
keras.__version__

在这里插入图片描述
我们将使用的猫狗数据集没有打包在Keras中。2013年末,作为计算机视觉比赛的一部分,Kaggle.com推出了这款软件,当时convnets还不是主流。您可以下载原始数据集:https://www.kaggle.com/c/dogs-vs-cats/data(如果您还没有一个Kaggle帐户,您需要创建一个帐户——不用担心,这个过程很轻松)。
不出所料,2013年的猫狗Kaggle比赛中,使用convnets的参赛者获得了冠军。最佳参赛作品的正确率可达95%。在我们自己的示例中,我们将相当接近这个精度(在下一节中),尽管我们的模型所使用的数据还不到竞争对手可用数据的10%。这个原始数据集包含25,000张狗和猫的图像(每个类有12,500张),有543MB大(压缩)。下载并解压缩后,我们将创建一个包含三个子集的新数据集:一个包含每个类1000个样本的训练集,一个包含每个类500个样本的验证集,最后一个包含每个类500个样本的测试集。

这里有几行代码可以做到这一点:

import os, shutil
# The path to the directory where the original
# dataset was uncompressed
original_dataset_dir = 'C:/Users/ASUS/Desktop/train'

# The directory where we will
# store our smaller dataset
base_dir = 'C:/Users/ASUS/Desktop/cats_and_dogs_small'
os.mkdir(base_dir)

# Directories for our training,
# validation and test splits
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)

# Directory with our training cat pictures
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)

# Directory with our training dog pictures
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)

# Directory with our validation cat pictures
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)

# Directory with our validation dog pictures
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)

# Directory with our validation cat pictures
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)

# Directory with our validation dog pictures
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)

# Copy first 1000 cat images to 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)

# Copy next 500 cat images to 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)
    
# Copy next 500 cat images to 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)
    
# Copy first 1000 dog images to 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)
    
# Copy next 500 dog images to 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)
    
# Copy next 500 dog images to 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)

作为一个完整的检查,让我们计算一下在每个训练分割(训练/验证/测试)中有多少张图片:

print('total training cat images:', len(os.listdir(train_cats_dir)))

在这里插入图片描述

print('total training dog images:', len(os.listdir(train_dogs_dir)))

在这里插入图片描述

print('total validation cat images:', len(os.listdir(validation_cats_dir)))

在这里插入图片描述

print('total validation dog images:', len(os.listdir(validation_dogs_dir)))

在这里插入图片描述

print('total test cat images:', len(os.listdir(test_cats_dir)))

在这里插入图片描述

print('total test dog images:', len(os.listdir(test_dogs_dir)))

在这里插入图片描述
我们确实有2000张训练图像,1000张验证图像和1000张测试图像。在每一次分割中,来自每个类的样本数量都是相同的:这是一个平衡的二元分类问题,这意味着分类的准确性将是衡量成功的一个合适的标准。

from keras import layers
from keras import models

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.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

让我们看看feature map的尺寸是如何随着每个连续的层而变化的:

model.summary()

基于卷积神经网络的图像分类_第1张图片
对于编译步骤,我们将像往常一样使用RMSprop优化器。由于我们的网络是以一个单一的sigmoid单元结束的,所以我们将使用二元交叉矩阵作为我们的损失。

from keras import optimizers

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])

正如您现在所知道的,在将数据输入到我们的网络之前,应该将数据格式化为经过适当预处理的浮点张量。目前,我们的数据以JPEG文件的形式保存在硬盘上,因此将其导入网络的步骤大致如下:

读取图片文件。
解码JPEG内容到RBG像素网格。
把它们转换成浮点张量。
将像素值(从0到255)缩放到[0,1]区间(如您所知,神经网络更喜欢处理小的输入值)。

这看起来可能有点令人畏惧,但是谢天谢地,Keras有一些实用程序来自动处理这些步骤。Keras有一个包含图像处理辅助工具的模块,位于Keras .preprocessing.image。特别是,它包含类ImageDataGenerator,它允许快速设置Python生成器,这些生成器可以自动地将磁盘上的图像文件转换为一批预处理的张量。这就是我们要用的。

from keras.preprocessing.image import ImageDataGenerator

# All images will be rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        # This is the target directory
        train_dir,
        # All images will be resized to 150x150
        target_size=(150, 150),
        batch_size=20,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

在这里插入图片描述
让我们看一下其中一个生成器的输出:它生成一批150x150的RGB图像(shape(20, 150, 150, 3))和二进制标签(shape(20,))。20是每批样品的数量(批次尺寸)。请注意,生成器会无限期地生成这些批:它只是在目标文件夹中出现的图像上无休止地循环。由于这个原因,我们需要在某一点中断迭代循环。

for data_batch, labels_batch in train_generator:
    print('data batch shape:', data_batch.shape)
    print('labels batch shape:', labels_batch.shape)
    break

在这里插入图片描述
让我们使用生成器使我们的模型适合于数据。我们使用fit_generator方法来完成此操作,对于我们这样的数据生成器,它相当于fit方法。它期望Python生成器作为第一个参数,它将无限期地生成成批的输入和目标,就像我们的示例一样。因为数据是不断生成的,所以在宣告一个纪元结束之前,生成器需要知道示例从生成器中抽取多少样本。这就是steps_per_epoch参数的作用:在从生成器中绘制完steps_per_epoch批处理之后,即在运行完steps_per_epoch梯度下降步骤之后,拟合过程将转到下一个epoch。在我们的例子中,批次是20个样本大,所以在我们看到2000个样本的目标之前将需要100个批次。

在使用fit_generator时,可以传递validation_data参数,就像fit方法一样。重要的是,允许这个参数本身是一个数据生成器,但是它也可以是Numpy数组的元组。如果您传递一个生成器作为validation_data,那么这个生成器将会不断生成成批的验证数据,因此您还应该指定validation_steps参数,它告诉流程从验证生成器提取多少批来进行评估。

history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=30,
      validation_data=validation_generator,
      validation_steps=50)

运行时间较长,运行部分结果如下图:
基于卷积神经网络的图像分类_第2张图片
基于卷积神经网络的图像分类_第3张图片
这是一个很好的实践,总是保存您的模型后,培训:

model.save('cats_and_dogs_small_1.h5')

让我们在训练和验证数据上绘制模型的损失和准确性:

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']

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
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.title('Training and validation loss')
plt.legend()

plt.show()

基于卷积神经网络的图像分类_第4张图片
这些图具有过拟合的特点。我们的训练精度随着时间线性增长,直到接近100%,而我们的验证精度停留在70-72%。我们的验证损失在5个epoch后达到最小,然后停止,而训练损失继续线性下降,直到接近0。

因为我们只有相对较少的训练样本(2000),过度拟合将是我们首要关心的问题。你已经知道了一些技术,可以帮助减轻过度拟合,如dropout和重量衰减(L2正则化)。现在我们将介绍一种新的方法,专门针对计算机视觉,在深度学习模型处理图像时几乎普遍使用:数据增强。
在Keras中,这可以通过配置一系列随机转换来完成,这些转换将对ImageDataGenerator实例所读取的图像执行。让我们以一个例子开始:

datagen = ImageDataGenerator(
      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')

这些只是可用的选项中的一部分(更多信息,请参阅Keras文档)。让我们快速回顾一下我们刚刚写的:

rotation_range是一个角度值(0-180),在这个范围内可以随机旋转图片。
width_shift和height_shift是范围(作为总宽度或高度的一部分),在其中可以随机地垂直或水平地转换图片。
shear_range用于随机应用剪切转换。
zoom_range用于在图片内部随机缩放。
horizontal_flip是用于水平随机翻转一半的图像——当没有假设水平不对称时(例如真实世界的图片)。
fill_mode是用于填充新创建像素的策略,它可以在旋转或宽度/高度移动之后出现。
让我们来看看我们的增强图像:

# This is module with image preprocessing utilities
from keras.preprocessing import image

fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]

# We pick one image to "augment"
img_path = fnames[3]

# Read the image and resize it
img = image.load_img(img_path, target_size=(150, 150))

# Convert it to a Numpy array with shape (150, 150, 3)
x = image.img_to_array(img)

# Reshape it to (1, 150, 150, 3)
x = x.reshape((1,) + x.shape)

# The .flow() command below generates batches of randomly transformed images.
# It will loop indefinitely, so we need to `break` the loop at some point!
i = 0
for batch in datagen.flow(x, batch_size=1):
    plt.figure(i)
    imgplot = plt.imshow(image.array_to_img(batch[0]))
    i += 1
    if i % 4 == 0:
        break

plt.show()

基于卷积神经网络的图像分类_第5张图片
基于卷积神经网络的图像分类_第6张图片
如果我们使用这种数据增加配置训练一个新的网络,我们的网络将永远不会看到两次相同的输入。然而,它看到的输入仍然是高度相关的,因为它们来自少量的原始图像——我们不能产生新的信息,我们只能混合现有的信息。因此,这可能还不足以完全消除过度拟合。为了进一步对抗过拟合,我们还将在我们的模型中增加一个Dropout层,就在密集连接分类器之前:

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'))

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])

让我们用数据增加和退出来训练我们的网络:

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,)

# Note that the validation data should not be augmented!
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        # This is the target directory
        train_dir,
        # All images will be resized to 150x150
        target_size=(150, 150),
        batch_size=32,
        # Since we use binary_crossentropy loss, we need binary labels
        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,
      epochs=100,
      validation_data=validation_generator,
      validation_steps=50)

部分运行结果:
基于卷积神经网络的图像分类_第7张图片
让我们保存我们的模型——我们将在convnet可视化部分使用它。

model.save('cats_and_dogs_small_2.h5')

让我们再把结果画出来:

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
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.title('Training and validation loss')
plt.legend()

plt.show()

基于卷积神经网络的图像分类_第8张图片
由于数据的增加和遗漏,我们不再过度拟合:训练曲线相当紧密地跟踪验证曲线。我们现在能够达到82%的精度,相对于非正则化模型有15%的改进。

通过进一步利用正则化技术和调整网络参数(比如每个卷积层的滤波器数量,或者网络中的层数),我们可能能够获得更好的精度,可能达到86-87%。然而,仅仅通过从零开始训练我们自己的卷积神经网络来达到更高的高度是非常困难的,因为我们可以使用的数据太少了。作为提高这个问题准确性的下一步,我们必须利用一个预先训练好的模型。

你可能感兴趣的:(基于卷积神经网络的图像分类)