Keras学习教程七

原文地址:https://nbviewer.jupyter.org/github/fchollet/deep-learning-with-python-notebooks/blob/master/5.2-using-convnets-with-small-datasets.ipynb

使用带有小数据集的网点

在小数据集上从头开始训练一个convnet

    不得不仅使用非常少的数据来训练图像分类模型就是一种常见的情况,如果您在专业环境中进行计算机视觉,那么您可能会在实践中遇到自己。
    拥有“少量”样本可能意味着从几百到几万的图像。作为一个实际例子,我们将集中于将图像分类为“狗”或“猫”,在包含4000张猫和狗图片(2000只猫,2000只狗)的数据集中。我们将使用2000张照片进行训练,1000张进行验证,最后使用1000张进行测试。
    在本节中,我们将回顾解决这个问题的一个基本策略:从零开始培训一个新模型,了解我们拥有的小数据。我们将从2000年的培训样本中毫不费力地训练一个小小的转折点开始,为其所能达到的目标设定基准。这将使我们的分类准确率达到71%。那时,我们的主要问题是过度配合。然后我们将介绍数据增强,这是一种用于缓解计算机视觉过度拟合的强大技术。通过利用数据增强,我们将改进我们的网络,以达到82%的准确度。

    在下一节中,我们将回顾将深度学习应用于小数据集的两个更重要的技术:使用预先训练的网络进行特征提取(这将使我们的准确率达到90%到93%),并且微调预先训练好的网络(这将使我们达到95%的最终准确度)。总而言之,这三种策略 - 从零开始训练一个小模型,使用预先训练的模型进行特征提取,并对预先训练好的模型进行微调 - 将构成未来的工具箱,用于解决小型计算机视觉问题数据集。

深度学习对于小数据问题的相关性

    有时您会听到只有大量数据可用时,深度学习才有效。这在一定程度上是一个有效的观点:深度学习的一个基本特征是它能够独立地在训练数据中找到有趣的特征,而不需要任何手动特征工程,并且这只能在大量训练实例可用。对于输入样本非常高维的问题(如图像)尤其如此。
    然而,对于初学者来说,构成“大量”样本的是相对的 - 相对于您尝试培训的网络的规模和深度。只用几十个样本来训练一个小圆点来解决一个复杂的问题是可能的,但是如果模型很小并且正规化并且任务很简单,那么几百个就足够了。由于可伸缩网络学习本地,翻译不变特征,因此它们对感知问题的数据效率非常高。在非常小的图像数据集上从头开始训练convnet,即使相对缺乏数据,仍然会产生合理的结果,而无需任何自定义特征工程。您将在本节中看到这一点。
但更重要的是,深度学习模式在本质上是高度可重复使用的:您可以采用例如大规模数据集上训练的图像分类或语音 - 文本模型,然后在极其不同的问题上重用它,只需稍作更改即可。具体而言,在计算机视觉的情况下,许多预先训练的模型(通常在ImageNet数据集上进行培训)现在已公开可供下载,并且可用于以非常少的数据引导强大的视觉模型。这就是我们将在下一节中做的。

    现在,让我们开始掌握数据。

下载数据

    我们将使用的猫与狗数据集不包含在Keras中。 它在2013年后期作为计算机视觉竞赛的一部分由Kaggle.com提供,当时还不是主流。 您可以在以下网址下载原始数据集:https://www.kaggle.com/c/dogs-vs-cats/data(如果您还没有帐户,则需要创建一个Kaggle帐户 - 不要担心 ,这个过程是无痛的)。

    这些图片是中等分辨率的彩色JPEG。 他们看起来像这样:

    不出所料,2013年的猫与狗Kaggle比赛是由使用了小脚印的参赛者所赢得的。 最好的条目可以实现高达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 = '/Users/fchollet/Downloads/kaggle_original_data'

# The directory where we will
# store our smaller dataset
base_dir = '/Users/fchollet/Downloads/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)))
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

    所以我们确实有2000个训练图像,然后是1000个验证图像和1000个测试图像。 在每个分组中,每个分类中都有相同数量的样本:这是一个平衡的二元分类问题,这意味着分类准确性将是衡量成功的一个适当指标。

建立我们的网络

    在前面的例子中,我们已经为MNIST建立了一个小小的转折点,所以你应该熟悉它们。我们将重用相同的总体结构:我们的convnet将是一堆交替Conv2D(带有Relu激活)和MaxPooling2D图层。
    但是,由于我们正在处理更大的图像和更复杂的问题,因此我们将使网络相应地变大:它将有一个Conv2D + MaxPooling2D阶段。这既可以增加网络的容量,又可以进一步减小特征映射的大小,以便在到达平展层时它们不会太大。在这里,由于我们从尺寸为150x150的输入(稍微任意的选择)开始,我们最终在Flatten图层之前获得尺寸为7x7的特征地图。
    请注意,特征映射的深度在网络中逐渐增加(从32增加到128),而特征映射的大小却在减小(从148×148到7×7)。这是你几乎在所有方面都会看到的模式。

    由于我们正在攻击二进制分类问题,因此我们用单个单元(大小为1的密集层)和S形激活来结束网络。该单元将编码网络正在看一个类别或另一个类别的概率。

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

    让我们来看看特征映射的维度如何随每个连续图层而改变:

model.summary()

    对于我们的编译步骤,我们将像往常一样使用RMSprop优化器。 由于我们用单个sigmoid单元结束了我们的网络,因此我们将使用二进制交叉熵作为我们的损失(作为提醒,请查看第4章第5节中关于在各种情况下使用什么损失函数的表格)。

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

Found 2000 images belonging to 2 classes.

Found 1000 images belonging to 2 classes.

    我们来看看其中一个发生器的输出:它会产生150x150个RGB图像(形状(20,150,150,3))和二进制标签(形状(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
data batch shape: (20, 150, 150, 3)
labels batch shape: (20,)


    让我们使用生成器将我们的模型拟合到数据上。我们使用fit_generator方法来完成,相当于适合像我们这样的数据生成器。它期望作为第一个参数的Python生成器能够无限期地生成批量的输入和目标,就像我们的那样。由于数据是无休止地生成的,因此生成器需要知道示例在声明历元之前需要从生成器中抽取多少个样本。这是steps_per_epoch参数的作用:在从发生器中绘制steps_per_epoch批次后,即在运行steps_per_epoch梯度下降步骤之后,拟合过程将进入下一个时期。在我们的例子中,批次是20个样本大小,所以它将需要100个批次,直到我们看到我们的2000个样本的目标为止。

    使用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)

    训练后始终保存模型是一种很好的做法:

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()
    这些情节是过度拟合的特征。 我们的训练精度随时间线性增加,直到达到接近100%,而我们的验证准确度在70-72%。 我们的验证损失达到其最小值后,只有五个时期,然后失速,而训练损失保持线性下降,直到接近0。
    由于我们只有相对较少的培训样本(2000年),过度配合将成为我们的首要关注点。 您已经了解了一些可以帮助缓解过度劳累的技术,例如辍学和体重衰退(L2正规化)。 我们现在要介绍一种新的,专门用于计算机视觉的,当用深度学习模型处理图像时几乎普遍使用:数据增强。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值