深度学习 Keras 1.4 数据扩充(Data Augmentation)

  1. Keras 中的数据扩充
  2. 优化我们1.4章节的程序
  3. 总结
  4. 附本文完整代码

数据扩充(data augmentation),又名 数据增强/数据增广。

其本质即:缺少海量数据时,为了保证模型的有效训练,一分钱掰成两半花

我们知道,丰富的高质量数据是训练出好的机器学习模型的关键。但是良好的数据不会从天上掉下来,靠人工收集数据是一个非常费时费力的工作,关键是,在特定的领域,有效的数据很难获取,比如医学影像数据。

这个时候,采用一些程序手段扩充数据集就成为了解决数据缺乏的一种方法,它可以将训练集的大小增加10倍或更多。更让人鼓舞的是,这样训练出的模型通常会更加健壮,减少过拟合。

数据扩充方法包括:

  • 简单方法(翻转、旋转、尺度变换、随机抠取、色彩抖动)
  • 复杂方法(Fancy PCA、监督式抠取、GAN生成)

不是所有数据扩充方法都可以一股脑儿随便用。比如对于人脸图片,垂直翻转就变得不可行了。因为现实中基本不会出现对倒过来的人脸进行识别,那么垂直翻转后产生的就几乎是对模型有害的噪声了,这会干扰到模型的正常收敛。

另外,如果是图像检测任务或者是图像分割任务,记得将图像数据和标记数据进行同步扩充(比如图像翻转时,对应的标记坐标跟着做相应翻转)。

Keras 中的数据扩充

在 Keras 中,可以通过配置对我们的 ImageDataGenerator 实例读取的图像执行多个随机变换来完成。

让我们从下面的这个例子中了解 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 是用于填充新创建的像素的策略,可以旋转或宽/高移位后填充

那么,让我们调用 ImageDataGenerator 来看看数据扩充后的图片长什么样

# 随便找一张图片
import os
img_path = os.path.join(os.getcwd(), "data")
img_path = os.path.join(img_path, "cats_and_dogs_small")
img_path = os.path.join(img_path, "train")
img_path = os.path.join(img_path, "cats")
img_path = os.path.join(img_path, "cat.119.jpg")

from keras.preprocessing.image import ImageDataGenerator
from keras.preprocessing import image

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

# 读图像并进行大小处理
img = image.load_img(img_path, target_size=(150, 150))

# 转换成Numpy array并且shape (150, 150, 3)
x = image.img_to_array(img)

# 重新Reshape成 (1, 150, 150, 3)以便输入到模型中
x = x.reshape((1,) + x.shape)

# 透过flow()方法将会随机产生新的图像
# 它会无限循环,所以我们需要在某个时候“断开”循环
i = 0
import matplotlib.pyplot as plt
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()

优化我们1.4章节的程序

如果我们使用这种数据扩充配置来训练一个新的网络,我们的网络将永远不会看到相同重复的输入。然而,它看到的输入仍然是相互关联的(因为我们只是对同一种图片做了一点随机改动),我们不能产生新的信息,我们只能重新混合现有的信息。因此,这可能不足以完全摆脱过度拟合(overfitting)。为了进一步去除过度拟合(overfitting),我们还将在密集连接(densely-connected)的分类器之前添加一个 Dropout 层

model.add(layers.Dropout(0.5))

修改 ImageDataGenerator ,在读取图像时执行多个随机变换

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

调整训练集每批次样本个数 batch_size

train_generator = train_datagen.flow_from_directory(
    train_dir,
    target_size=(150, 150),
    batch_size=32,
    class_mode='binary')

调整验证集每批次样本个数 batch_size

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

调整训练循环 epochs

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

由于数据扩充(data augmentation)和丢弃(dropout)的使用,我们不在有过度拟合(overfitting)的问题:训练曲线相当密切的跟随验证曲线。我们现在能够达到 82% 的准确度,比之前的模型相比改善了15%。

通过进一步利用正规化技术,及调整网络参数(例如每个卷积层的滤波器数量或网络层数),我们可以获得更好的准确度,可能高达 86% ~ 87%。然而,只要我们从头开始训练我们自己的卷积网络(converts),我们可以证明使用这么少的数据来训练处一个准确率高的模型是非常困难的。

为了继续提高我们模型的准确性,下一步我们将利用预先训练好的模型(pre-trained model)来进行操作。

总结

善用数据扩充(Data Augmentation)对训练集不多的图像识别可以提升效能

使用 Dropout 可以抑制过拟合(overfitting)的问题

附本文完整代码

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,
    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./255)

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

# 直接从档案目录读取图像档资料
validation_generator = test_datagen.flow_from_directory(
    validation_dir,
    target_size=(150, 150),
    batch_size=32,
    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.Dropout(0.5))
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=50,
    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 数据扩充(Data Augmentation)

字数:2.6k

本文作者:夏来风

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

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

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