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

目录

0. 前言

1. 数据下载和预处理¶

2. 搭建一个小的卷积网络

3. 数据预处理

4. 模型训练¶

5. 在测试集进行模型性能评估

6. 小结¶


0. 前言

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

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

(1) 使用数据增强策略

(2) 使用预训练模型

本文先考虑搭建一个小型卷积神经网络从头开始训练用于猫狗数据集的图像分类。原始的猫狗数据集有25000张图片,猫和狗各12500张。但是为了体现小数据集所可能带来的问题,我们仅使用其中的6000张图片(包括训练集和测试集)。

然后,我们考虑使用数据增强策略看看其效果如何。

再进一步,我们考虑如何在(已经在大数据集上训练过的)预训练模型的基础上,在小数据集上进一步训练得到最终模型,看看这种做法的效果如何。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras import utils
import numpy as np
print(tf.__version__)

本文中代码在{Windows10, Jupyter Notebook, Tensorflow 2.5} 环境下调试运行通过。

1. 数据下载和预处理

猫狗数据集出自kaggle竞赛,可以从kaggle网页下载,但是在国内访问kaggle好像是有问题。幸好从microsoft网页下载也可以方便地下载到。 Download Kaggle Cats and Dogs Dataset from Official Microsoft Download Center

下载展开后目录结构如下所示:

(1) cats-vs-dogs\cat

(2) cats-vs-dogs\dog

首先我们从中随机挑选一些数据出来,并且(为了迎合后面使用flow_from_directory()进行训练、验证和测试集的生成)生成合适的数据集目录结构,如下图所示:

def make_subset(subset_name, start_index, end_index):
    for category in ("cat", "dog"):
        dir = new_base_dir / subset_name / category
        src_dir = original_dir / category
        print(dir)
        os.makedirs(dir)
        fnames = [f"{i}.jpg" for i in range(start_index, end_index)]
        for fname in fnames:
            shutil.copyfile(src=src_dir / fname, dst=dir / fname)

import os, shutil, pathlib

original_dir = pathlib.Path("F:\DL\cats-vs-dogs")
new_base_dir = pathlib.Path("F:\DL\cats_vs_dogs_small")

#print(original_dir, new_base_dir)

start_index = np.random.randint(0,8000)
end_index   = start_index + 1000

start_index3 = end_index2
end_index3   = start_index3 + 500

if os.path.exists(new_base_dir):
    shutil.rmtree(new_base_dir)
    
make_subset("train", start_index=start_index, end_index=end_index)
make_subset("test", start_index=start_index3, end_index=end_index3)    

 挑几张图片看看长得什么样子。

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
subset_name = 'train'

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

# Randomly select 4 pictures from train/cat and train/dog folders, respectively.
cat_fnames = [f"{start_index+i}.jpg" for i in np.random.randint(0,1000,4)]
dog_fnames = [f"{start_index+i}.jpg" for i in np.random.randint(0,1000,4)]

for k in range(4):
    img = Image.open(new_base_dir /  subset_name / 'cat' / cat_fnames[k])
    ax[0][k].imshow(img)
    img = Image.open(new_base_dir /  subset_name / 'dog' / dog_fnames[k])
    ax[1][k].imshow(img)

2. 搭建一个小的卷积网络

以下以函数API的方式搭建一个小型卷积网络用于猫狗数据集的分类。

注意,因为在后面ImageDataGenerator()调用时进行了scaling处理,在模型搭建的地方就不需要了。这里有一个血泪的教训。。。一开始我例行公事地在这里加了Rescaling层,然后在后面基于ImageDataGenerator生成数据集Generator时又设置了参数rescale=1./255。结果可想而知,始终都是50%的accuracy,无头苍蝇似地各种偏方折腾了几个小时。。。这就引出了另一个问题,tensorflow开发中怎么有效地进行调试呢?

from tensorflow import keras
from tensorflow.keras import layers

inputs = keras.Input(shape=(180, 180, 3))
# x = layers.experimental.preprocessing.Rescaling(1./255)(inputs) 
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)
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"])

utils.plot_model(model, 'model-cats-vs-dogs.png',show_shapes=True,show_dtype=True,show_layer_names=True)

3. 数据预处理

注意,以下处理中"class_mode"参数设置为'binary'是与上面模型编译中采用的损失函数为loss="binary_crossentropy"相呼应的。

另外,valid_generator和train_generator基于同一ImageDataGenerator对象利用同一个目录底下的数据中生成。但是它其实也可以基于一个独立的ImageDataGenerator对象利用不同于train_dir目录的数据生成。比如说,可以另外建一个validation子目录(其结构和train子目录相同)用于valid_generator的生成。

# Data generators
from tensorflow.keras.preprocessing.image import ImageDataGenerator
batch_size = 32
train_dir  = new_base_dir / "train"
test_dir   = new_base_dir / "test"
train_datagen = ImageDataGenerator(rescale=1./255,validation_split=0.3)
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=new_base_dir / "train",
    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=new_base_dir / "test",
    target_size=(180, 180),
    color_mode="rgb",
    batch_size=batch_size,
    class_mode='binary',
    shuffle=False,
    seed=42
)
Found 1400 images belonging to 2 classes.
Found 600 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.

4. 模型训练

在稍早一点的版本中是利用fit_generator函数来基于DataGenerator进行训练的,但是在新的版本中fit_generator被deprecated了,fit()函数经过扩充可以支持基于DataGenerator训练了。

callbacks = [
    keras.callbacks.ModelCheckpoint(
        filepath="convnet_from_scratch.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=15,    
    callbacks=callbacks)

显示训练和验证集上的accuracy和loss随着epoch数变化而变化的曲线对比。

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

 

5. 在测试集进行模型性能评估

test_model = keras.models.load_model("convnet_from_scratch.keras")
test_loss, test_acc = test_model.evaluate(test_generator)
print(f"Test accuracy: {test_acc:.3f}")
32/32 [==============================] - 2s 62ms/step - loss: 0.4762 - accuracy: 0.7990
Test accuracy: 0.799

6. 小结

        本文先构建了一个小的卷积神经网络,在猫狗数据集的一个子集上从头开始训练。从结果来看,在训练集上可以达到接近于100%的accuracy,但是在验证集和测试集上只有不到80%。因此很明显存在严重的过拟合问题。此外还可以看出epoches设置为15是多余的,几乎在epoches等于5时就已经达到了验证集的accuracy的峰值。

        接下来将采用两种方法解决这个问题。

Ref:

(1) Francois Chollet: Deep Learning with Python-->此书中文版已出版

Supongo que te gusta

Origin blog.csdn.net/chenxy_bwave/article/details/122260520
Recomendado
Clasificación