深度学习 Keras 1.4 如何训练小数据集

  1. 收集数据
  2. 准备三个数据集
  3. 确认三个数据集
  4. 数据预处理(Data Preprocessing)
  5. 创建网络模型(Model)
  6. 训练(Training)
  7. 展示
  8. 附本文完整代码

现如今,机器学习的工具越来越成熟,相对入门门槛还是低的。

我们除了能获得强力\便捷的工具还能获取到特定行业的训练结果(比如人脸等)。

若是结合业务,最关键的步骤还是训练出自己想要的内容,那么现在,我们尝试去动手训练一个自己的模型吧

我们的课题:训练一个能识别猫与狗的程序

收集数据

机器学习的需要大量的数据来“喂”,因此我们需要收集猫与狗的数据集。

Kaggle.com 在 2013 年底提供了这些数据来作为机器视觉竞赛题目,

这里是猫和狗的数据集 点击链接下载

该原始数据集包含25,000张狗和猫的图像(每个类别12,500个),大小为543MB(压缩)。

下载完毕后解压缩一下吧~

准备三个数据集

我们将创建一个包含三个子集的新数据集:

  • 一组 1000 个样本的训练集
  • 一组 500 个样本的验证集
  • 一组 500 个样本的测试集
import os

# 项目的根目录路径
ROOT_DIR = os.getcwd()

# 置放coco图像资料与标注资料的目录
DATA_PATH = os.path.join(ROOT_DIR, "data")

import os, shutil

# 原始数据集的路径
original_dataset_dir = os.path.join(DATA_PATH, "train")

# 存储小数据集的目录
base_dir = os.path.join(DATA_PATH, "cats_and_dogs_small")
if not os.path.exists(base_dir):
    os.mkdir(base_dir)

# 我们的训练资料的目录
train_dir = os.path.join(base_dir, 'train')
if not os.path.exists(train_dir):
    os.mkdir(train_dir)

# 我们的验证资料的目录
validation_dir = os.path.join(base_dir, 'validation')
if not os.path.exists(validation_dir):
    os.mkdir(validation_dir)

# 我们的测试资料的目录
test_dir = os.path.join(base_dir, 'test')
if not os.path.exists(test_dir):
    os.mkdir(test_dir)

# 猫的图片的训练资料目录
train_cats_dir = os.path.join(train_dir, 'cats')
if not os.path.exists(train_cats_dir):
    os.mkdir(train_cats_dir)

# 狗的图片的训练资料目录
train_dogs_dir = os.path.join(train_dir, 'dogs')
if not os.path.exists(train_dogs_dir):
    os.mkdir(train_dogs_dir)

# 猫的图片的验证资料目录
validation_cats_dir = os.path.join(validation_dir, 'cats')
if not os.path.exists(validation_cats_dir):
    os.mkdir(validation_cats_dir)

# 狗的图片的验证资料目录
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
if not os.path.exists(validation_dogs_dir):
    os.mkdir(validation_dogs_dir)

# 猫的图片的测试资料目录
test_cats_dir = os.path.join(test_dir, 'cats')
if not os.path.exists(test_cats_dir):
    os.mkdir(test_cats_dir)

# 狗的图片的测试资料目录
test_dogs_dir = os.path.join(test_dir, 'dogs')
if not os.path.exists(test_dogs_dir):
    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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy first 1000 cat images to train_cats_dir complete!')

# 复制下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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy next 500 cat images to validation_cats_dir complete!')

# 复制下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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy next 500 cat images to test_cats_dir complete!')

# 复制前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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy first 1000 dog images to train_dogs_dir complete!')

# 复制下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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy next 500 dog images to validation_dogs_dir complete!')

# 复制下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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy next 500 dog images to test_dogs_dir complete!')

确认三个数据集

我们现在有了 2000 个训练素材,1000个测试素材,1000个验证素材。在每个资料分割中每个分类都有相同数量的样本:这是一个平衡的二元分类问题,这意味着分类准确度将成为适当的度量。

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

数据预处理(Data Preprocessing)

数据应该被格式化成 浮点张量,然后才能喂进我们的神经网络

目前,我们的数据还是图片,所以进入我们神经网络的前处理步骤大概是:

  • 读取图像集
  • 将图片内容解码为 RGB 网格的像素
  • 将其转换为浮点张量
  • 将像素值(0和255之间)重新缩放到 [0,1] 间隔(神经网络更喜欢处理小的输入值)

这些步骤听着就有点麻烦,但是幸好 Keras 已经封装工具方法让我们可以方便的处理这些步骤

Keras 有一个图像处理助手工具的模块,位于 keras.preprocessing.image。其中的 ImageDataGenerator 类别,可以快速的自动将磁盘上的图像文件转换成张量(tensors)。

现在我们来使用这个工具:

from keras.preprocessing.image import ImageDataGenerator

# 所有的图像将重新被进行归一化处理 Rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

图像张量生成器(generator)的输出是什么?

看下面代码的参数可知,产生 150*150 RGB 图像(形状(20,150,150,3))和二进制标签(形状(20,))的批次张量。

# 直接从档案目录读取图像档资料
train_generator = train_datagen.flow_from_directory(
        # 这是图像资料的目录
        train_dir,
        # 所有的图像大小会被转换成150x150
        target_size=(150, 150),
        # 20 是每个批次中的样品数(批次大小)
        batch_size=20,  
        # 由于这是一个二元分类问题, y的lable值也会被转换成二元的标签
        class_mode='binary')

# 直接从档案目录读取图像档资料
validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

这里要注意,产生器可以无限制的产生这些批次:因为它是持续的循环遍历目标文件夹中的图像。

因此,我们需要在某些时候调用 break 跳出循环。

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

创建网络模型(Model)

我们的卷积网络(connvnets)将是一组交替的 Conv2D(具有relu激活)和 MaxPooling2D 层。

我们从大小 150x150 的输入开始,我们最终得到了尺寸为 7x7 的 Flattern 层之前的特征图。

注意,特征图的深度在网络中逐渐增加(从 32x32 到 128x128 ),而特征图的大小正在减少(从 148x148 到 7x7 )。这是一个你将在几乎所有的卷积网络(convnets)构建中会看到的模式

由于我们正在处理二元分类问题,所以我们用一个神经元(一个大小为 1 的密集层(Dense))和一个 sigmoid 激活函数来结束网络。该神经元将会被用来查看图像最终属于哪一个类的几率图。

from keras import layers
from keras import models
from keras.utils import plot_model

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的激活函数)结束了我们的网络,我们将使用二进制交叉熵(binary crossentropy)作为我们的损失函数。

from keras import optimizers

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

训练(Training)

让我们将图像张量生成器的数据输入模型进行训练,这里使用 fit_generator 方法。

当使用 fit_generator 时,可以传入一个 validation_data 参数。重要的是,这个参数被允许设置为数据生成器本身,但它也可以是一个 Numpy 数组的元組。如果你将其设置为 validation_data,那么这个生成器有望不断生成一批验证数据,与此同时,我们应该指定 validation_steps 参数,这个参数告诉进程从验证生成器中抽取多少批次以进行评估。

因为数据是可以持续生成的,所以图像张量产生器需要知道在一個训练循环(epoch)要抽取多少个数据。这是 steps_per_epoch 参数的作用:在从生成器中跑过 steps_per_epoch 批次之后,即在运行 steps_per_epoch 梯度下降步骤之后,训练过程将转到下一个循环(epoch)。在我們目前的情況下,批次是 20个样本,所以它需要100次,直到我们的模型读进了 2000 个目标样本。

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_2.h5')

展示

让我们使用图表来展示训练过程中模型对训练和验证数据的损失(loss)和准确性(accuracy)

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

plt.figure()

plt.plot(epochs, loss, label='Training loss')
plt.plot(epochs, val_loss, label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

这些图表展示了过度拟合(overfitting)的特征。我们的训练精确度随着时间线的增长,直到接近 100%,然而我们的验证精确度缺停在 70% ~ 72%。我们的验证损失在第五个循环(epochs)之后达到了最小值,然后停顿,而训练损失在线性上保持直到达到接近 0。

过度拟合(overfitting)是由于样本数量太少而导致的。

因为我们只有相对较少的训练数据(2000),过度拟合(overfitting)将成为我们的首要的关注点。你已经知道了许多可以帮助减轻过度拟合的技术,例如 Dropot 和权重衰减(L2正规化)。我们现在要引入一个新的技术,特定于机器视觉影像领域,并在使用深度学习模型处理图像时几乎普遍使用的技巧:数据扩充(data augmentation)。

数据扩充采用从现有训练样本生成更多训练数据的方法,用过产生可信图像的多个随机变换来达到增加样本的目的。它的特性是在训练的时候,我们的模型永远不会再看到完全相同的画面两次。

关于数据扩充,见下一篇文章

附本文完整代码

import os

# 项目的根目录路径
ROOT_DIR = os.getcwd()

# 置放coco图像资料与标注资料的目录
DATA_PATH = os.path.join(ROOT_DIR, "data")

import os, shutil

# 原始数据集的路径
original_dataset_dir = os.path.join(DATA_PATH, "train")

# 存储小数据集的目录
base_dir = os.path.join(DATA_PATH, "cats_and_dogs_small")
if not os.path.exists(base_dir):
    os.mkdir(base_dir)

# 我们的训练资料的目录
train_dir = os.path.join(base_dir, 'train')
if not os.path.exists(train_dir):
    os.mkdir(train_dir)

# 我们的验证资料的目录
validation_dir = os.path.join(base_dir, 'validation')
if not os.path.exists(validation_dir):
    os.mkdir(validation_dir)

# 我们的测试资料的目录
test_dir = os.path.join(base_dir, 'test')
if not os.path.exists(test_dir):
    os.mkdir(test_dir)

# 猫的图片的训练资料目录
train_cats_dir = os.path.join(train_dir, 'cats')
if not os.path.exists(train_cats_dir):
    os.mkdir(train_cats_dir)

# 狗的图片的训练资料目录
train_dogs_dir = os.path.join(train_dir, 'dogs')
if not os.path.exists(train_dogs_dir):
    os.mkdir(train_dogs_dir)

# 猫的图片的验证资料目录
validation_cats_dir = os.path.join(validation_dir, 'cats')
if not os.path.exists(validation_cats_dir):
    os.mkdir(validation_cats_dir)

# 狗的图片的验证资料目录
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
if not os.path.exists(validation_dogs_dir):
    os.mkdir(validation_dogs_dir)

# 猫的图片的测试资料目录
test_cats_dir = os.path.join(test_dir, 'cats')
if not os.path.exists(test_cats_dir):
    os.mkdir(test_cats_dir)

# 狗的图片的测试资料目录
test_dogs_dir = os.path.join(test_dir, 'dogs')
if not os.path.exists(test_dogs_dir):
    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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy first 1000 cat images to train_cats_dir complete!')

# 复制下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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy next 500 cat images to validation_cats_dir complete!')

# 复制下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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy next 500 cat images to test_cats_dir complete!')

# 复制前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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy first 1000 dog images to train_dogs_dir complete!')

# 复制下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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy next 500 dog images to validation_dogs_dir complete!')

# 复制下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)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('Copy next 500 dog images to test_dogs_dir complete!')

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

from keras.preprocessing.image import ImageDataGenerator

# 所有的图像将重新被进行归一化处理 Rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

# 直接从档案目录读取图像档资料
train_generator = train_datagen.flow_from_directory(
    # 这是图像资料的目录
    train_dir,
    # 所有的图像大小会被转换成150x150
    target_size=(150, 150),
    # 20 是每个批次中的样品数(批次大小)
    batch_size=20,
    # 由于这是一个二元分类问题, y的lable值也会被转换成二元的标签
    class_mode='binary')

# 直接从档案目录读取图像档资料
validation_generator = test_datagen.flow_from_directory(
    validation_dir,
    target_size=(150, 150),
    batch_size=20,
    class_mode='binary')

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

from keras import layers
from keras import models
from keras.utils import plot_model

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

from keras import optimizers

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

plt.figure()

plt.plot(epochs, loss, label='Training loss')
plt.plot(epochs, val_loss, label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

转载请注明来源。 欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。 可以在下面评论区评论,也可以邮件至 sharlot2050@foxmail.com。

文章标题:深度学习 Keras 1.4 如何训练小数据集

字数:4k

本文作者:夏来风

发布时间:2021-01-29, 22:27:01

原始链接:http://www.demo1024.com/blog/ai-ml-keras-1.4/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。