深度学习笔记:利用数据增强在小数据集上从头训练卷积神经网络

目录

0. 前言

1. 数据增强处理

2. 为什么要数据增强?

 3. 模型训练

4. 测试集上的性能

5. 小结


0. 前言

本文(以及接下来的几篇)介绍如何搭建一个卷积神经网络用于图像分类的深度学习问题,尤其是再训练数据集比较小的场合。通常来说,深度学习需要大量的数据进行训练,尤其是像在图像处理这种通常数据维度非常高的场合。但是当你没有一个足够大的数据集进行训练的时候应该怎么办呢?

解决训练数据集太小的方法通常有两种:

(1) 使用数据增强策略

(2) 使用预训练模型

扫描二维码关注公众号,回复: 13634921 查看本文章

上一篇我们搭建一个了小型卷积神经网络从头开始训练用于猫狗数据集的图像分类。仅利用了原始的猫狗数据集有25000张图片中的3000张图片(包括训练集和测试集)。实验表明在训练集、验证集以及测试集上分别得到了99.5%, 77.3%和66.6%的accuracy。参见:深度学习笔记:在小数据集上从头训练卷积神经网络

从这个结果来看,很明显地存在严重的过拟合。考虑到我们只用了3000张图片,对于图像(大小为180*180*3)分类问题来说这个非常小的一个数据集,这个结果并不令人惊讶。

在本篇中,我们来看看使用数据增强策略看看其效果如何。

在下一篇,我们将进一步考虑基于(已经在大数据集上训练过的)预训练模型的基础上,在小数据集上进一步训练得到最终模型的效果如何。

1. 数据增强处理

        tf.keras.preprocessing.image.ImageDataGenerator提供了丰富的图像数据的数据增强处理功能,具体的各种参数如何的使用解说不是本文目的,有兴趣的小伙伴可以自行参考tensorflow在线文档(tf.keras.preprocessing.image.ImageDataGenerator  |  TensorFlow Core v2.7.0). 本文所对ImageDataGenerator的调用与上一篇的差别如下图所示(上一篇没有红线圈住部分表示没有数据增强处理)。各参数的意思也几乎是不言自明,这里就不一一解释了。

         我们可以看看数据增强对图片所起的效果:

fig,ax = plt.subplots(2,4, figsize=[16,8])

np.random.seed(42)
cnt = 0
for new_batch in train_generator:       
    img1 = new_batch[0][np.random.randint(32)]
    img2 = new_batch[0][np.random.randint(32)]

    ax[cnt//4][cnt%4].imshow(img1)
    ax[cnt//4][cnt//4].imshow(img2)    
    
    cnt += 1
    if cnt == 8:
        break

        从以上图片中应该不难看出数据增强所带来的一些效果(扭曲、填充、旋转等) 。

2. 为什么要数据增强?

        过拟合的原因是学习样本太少,导致无法训练出能够泛化到新数据的模型。如果拥有无限的数据,那么模型能够观察到数据分布的所有内容,这样就永远不会过拟合。数据样本是从现有的训练样本中生成更多的训练数据,其方法是利用多种能够生成可信图像的随机变化来增加样本。新增加的样本虽然是在原有的样本的基础进行变换而得,但是的确是有变化,因此训练器不会看到完全相同的图像样本。训练器籍此可以观察并提取数据中更多的内容,从而具有更好的泛化能力。

        当然,由于新的数据样本毕竟是基于原有的数据样本变化而得,因此和原样本之间是存在相关性的(即不是完全独立的)。你无法生成新的信息,而只能混合现有信息。因此,这种方法可能不足以完全消除过拟合。为了能够充分取得降低过拟合的效果,还需要向模型中添加一个Dropout层,添加到Dense层之前(But Why ?),如下图所示(红线所划部分):

 3. 模型训练

        相对于上一篇的模型代码,本文中的两处关键代码如上所述,这里就不再一一解释。为了方便,这里将所有代码串在一起(方便有兴趣的小伙伴复制下载试运行)。数据目录cats_vs_dogs_small的生成代码这里就忽略了(需要的话从上一篇中复制即可)

from tensorflow import keras
from tensorflow.keras import layers

# Model construct
inputs = keras.Input(shape=(180, 180, 3))
x = layers.Conv2D(filters=32, kernel_size=3, activation="relu")(inputs)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(filters=64, kernel_size=3, activation="relu")(x)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(filters=128, kernel_size=3, activation="relu")(x)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(filters=256, kernel_size=3, activation="relu")(x)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(filters=256, kernel_size=3, activation="relu")(x)
x = layers.Flatten()(x)
x = layers.Dropout(0.5)(x)
outputs = layers.Dense(1, activation="sigmoid")(x)
model = keras.Model(inputs=inputs, outputs=outputs)
model.summary()
# Configuring the model for training
model.compile(loss="binary_crossentropy",optimizer="rmsprop",metrics=["accuracy"])

# Data generators
import os
from tensorflow.keras.preprocessing.image import ImageDataGenerator

batch_size = 32
train_dir = os.path.join('F:\DL\cats_vs_dogs_small', 'train')
test_dir = os.path.join('F:\DL\cats_vs_dogs_small', 'test')
train_datagen = ImageDataGenerator(rescale=1./255,validation_split=0.3,
      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(
    directory=train_dir,
    target_size=(180, 180),
    color_mode="rgb",
    batch_size=batch_size,
    class_mode="binary",
    subset='training',
    shuffle=True,
    seed=42
)
valid_generator = train_datagen.flow_from_directory(
    directory=train_dir,
    target_size=(180, 180),
    color_mode="rgb",
    batch_size=batch_size,
    class_mode="binary",
    subset='validation',
    shuffle=True,
    seed=42
)
test_generator = test_datagen.flow_from_directory(
    directory=test_dir,
    target_size=(180, 180),
    color_mode="rgb",
    batch_size=batch_size,
    class_mode='binary',
    shuffle=False,
    seed=42
)

# Training
callbacks = [
    keras.callbacks.ModelCheckpoint(
        filepath="convnet_from_scratch_with_augmentation.keras",
        save_best_only=True,
        monitor="val_loss")
]
history = model.fit(
    x = train_generator,
    validation_data=valid_generator,
    steps_per_epoch = train_generator.n//train_generator.batch_size,
    validation_steps = valid_generator.n//valid_generator.batch_size,
    epochs=100,    
    callbacks=callbacks)

# Displaying curves of loss and accuracy during training

import matplotlib.pyplot as plt
accuracy = history.history["accuracy"]
val_accuracy = history.history["val_accuracy"]
loss = history.history["loss"]
val_loss = history.history["val_loss"]
epochs = range(1, len(accuracy) + 1)
plt.plot(epochs, accuracy, "bo", label="Training accuracy")
plt.plot(epochs, val_accuracy, "b", label="Validation accuracy")
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轮,而上一篇中只训练了30轮。但是即便是100轮,至少在训练集上还没有看到accuracy完全到顶的迹象。另一个值得注意的是,验证集上的accracy和loss都显示除了较大的波动,这个是为什么?

4. 测试集上的性能

        最后,来看看这个新训练出来的模型在测试集上表现如何。

test_model = keras.models.load_model("convnet_from_scratch_with_augmentation.keras")
test_loss, test_acc = test_model.evaluate(test_generator)
print(f"Test accuracy: {test_acc:.3f}")

        虽然新的模型在训练集和验证集上分别只有85%和80%不到一点,但是在测试集上却达到了82.6%。在上一篇的原始模型中只有66%!在测试集上accuracy的相对提升高达40%!虽然新的模型在训练上花了更多的时间,但是仍然必须承认这是一个巨大的提升!

5. 小结

        本篇中我们看到数据增强带来了不错的效果,虽然,82.6%仍然不够好。通过进一步地使用正则化和调节网络参数(比如说每个卷积层的过滤器个数,或网络中的层数),有可能得到更高的精度。但是靠从头开始训练自己的卷积神经网络,想要提升到90%以上非常困难,因为可用的数据太少了。想要在这个问题上进一步提高精度,下一步需要使用预训练模型,这是下一篇的重点。

猜你喜欢

转载自blog.csdn.net/chenxy_bwave/article/details/122276708