这篇教程使用图像增强来训练小数据集写得很实用,希望能帮到您。
要用非常少的数据来训练一个图像分类的模型是一种现实中经常会遇到的情况,如果你过去曾经进行过影像视觉的相关处理,这样的情况你势必会经常遇到。
训练用样本很"少"可以意味着从几百个到几万个图像不等(视不同的应用与场景)。作为一个示范的案例,我们将集中在将图像分类为"狗"或"猫",数据集中包含4000张猫和狗照片(2000只猫,2000只狗)。我们将使用2000张图片进行训练,1000张图片用于验证,最后1000张用于测试。
我们将回顾一个解决这种问题的基本策略:从零开始,我们提供了少量数据来训练一个新的模型。我们将首先在我们的2000个训练样本上简单地训练一个小型卷积网络(convnets)来作为未来优化调整的基准,在这个过程中没有任何正规化(regularization)的手法或配置。
这样的模型将使我们的分类准确率达到71%左右。在这个阶段,我们的主要问题将是过拟合(overfitting)。然后,我们将介绍数据填充(data augmentation),这是一种用于减轻电脑视觉验算过度拟合(overfitting)的强大武器。通过利用数据填充(data augmentation),我们将改进网络,并提升准确率达到82%。
在另一篇文章中,我们将探索另外两种将深度学习应用到小数据集的基本技巧:使用预训练的网络模型来进行特征提取(这将使我们达到90%至93%的准确率),调整一个预先训练的网络模型(这将使我们达到95%的最终准确率)。总而言之,以下三个策略将构成你未来的工具箱,用于解决计算机视觉运算应用到小数据集的问题上:
- 从头开始训练一个小型模型
- 使用预先训练的模型进行特征提取
- 微调预先训练的模型
# 运行的环境
import platform
import tensorflow
import keras
print("Platform: {}".format(platform.platform()))
print("Tensorflow version: {}".format(tensorflow.__version__))
print("Keras version: {}".format(keras.__version__))
Platform: Linux-4.15.0-43-generic-x86_64-with-debian-buster-sid
Tensorflow version: 1.12.0
Keras version: 2.2.4
Using TensorFlow backend.
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
from IPython.display import Image
资料集说明
我们将使用的数据集(猫与狗的图片集)没有被keras包装发布,因此需要自行下载。你可以从以下链接下载原始数据集: https://blog.csdn.net/qq_20073741/article/details/81233326
图片是中等解析度的彩色JPEG图。该原始数据集包含25000张狗和猫的图像(每个类别12500个),大小为543MB(压缩)。下载和解压缩后,我们将创建一个包含三个子集的新数据集:一组包含每个类的1000个样本的训练集,每组500个样本的验证集,最后一个包含每个类的500个样本的测试集。
数据集准备
- 从百度云上下载图像数据集training.zip
- 在同一目录下新建子目录"data"
- 把从百度云下载的图像数据集复制到"data"目录
- 将training.zip解压缩
最后你的目录结构看起来像这样:
- xx.ipynb
- data/
- train/
- cat.0.jpg
- cat.1.jpg
- ...
- cat.12499.jpg
import os
# 根目录路径
root_dir = os.getcwd()
# 存放图像数据集的目录
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 next 1000 cat images to train_cats_dir complete!")
Copy next 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!')
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!")
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!")
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!')
Copy next 500 dog images to validation_dogs_dir complete!
# C複製下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!')
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)))
total training cat images: 1000
total training dog images: 1000
total validation cat images: 500
total validation dog images: 500
total test cat images: 500
total test dog images: 500
所以我们确实有2000个训练图像,然后是1000个验证图像,1000个测试图像。在每个图像分割(split)中,每个分类都有相同数量的样本:这是一个平衡的二元分类问题,这意味着分类准确度将是合适的度量标准。
资料预处理(Data Preprocessing)
我们都知道,数据应该先被格式化成适当的预处理浮点张量,然后才能输入到我们的神经网络中。目前,我们的图像在目录中是JPEG的格式,所以进入我们网络的预处理步骤大概是:
- 读入图像
- 将JPEG内容解码为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)
# 直接从目录读取图像数据
train_generator = train_datagen.flow_from_directory(
# 训练图像的目录
train_dir,
# 所有图像大小会被转换成150x150
target_size=(150,150),
# 每次产生20个图像的批次
batch_size = 20,
# 由于这是一个二元分类问题,y的label值也会被转换成二元的标签
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.
我们来看看这些图像张量生成器(generator)的输出:它产生150x150 RGB图像(形状"(20,150,150,3)")和二进制标签(形状"(20,)")的批次张量。20是每个批次中的样品数(批次大小)。请注意,产生器可以无限制地产生这些批次:因为它只是持续地循环遍历目标文件夹中存在的图像。因此,我们需要在某些时候break迭代循环。
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方法。 因为数据是可以无休止地持续生成,所以图像张量产生器需要知道在一个训练循环(epoch)要从图像张量产生器中抽取多少个图像。这是steps_per_epoch参数的作用:在从生成器中跑过steps_per_epoch批次之后,即在运行steps_per_epoch梯度下降步骤之后,训练过程将转到下一个循环(epoch)。在我们的情况下,批次是20个样本,所以它需要100次,直到我们的模型读进了2000个目标样本。 当使用fit_generator时,可以传递一个validation_data参数,就像fit方法一样。重要的是,这个参数被允许作为数据生成器本身,但它也可以是一个Numpy数组的元祖。如果你将生成器传递为validation_data,那么这个生成器有望不断生成一批验证数据,因此你还应该指定validation_steps参数,该参数告诉进程从验证生成器中抽取多少批次以进行评估。
网络模型(Model)
我们的卷积网络(convnets)将是一组交替的Conv2D(具有relu激活)和MaxPooling2D层。我们从大小150x150(有点任意选择)的输入开始,我们最终得到了尺寸为7x7的Flatten层之前的特征图。
注意特征图的深度在网络中逐渐增加(从32到128),而特征图的大小正在减少(从148x148到7x7)。这是一个你将在几乎所有的卷积网络(convnets)结构中会看到的模式。
由于我们正在处理二元分类问题,所以我们用一个神经元(一个大小为1的密集层(Dense))和一个sigmoid激活函数来结束网络。该神经元将会被用来查看图像归属于那一类或另一类的概率 使用小数据集时可能会出现的问题,如何最有效地克服这些问题 神经网络在小样本数据集上非常容易过拟合 |