在深度学习实践中,当训练数据量少时,可能会出现过拟合问题。根据Goodfellow等人的观点,我们对学习算法的任何修改的目的都是为了减小泛化误差,而不是训练误差。
我们已经在sb[后续补充]中提到了不同类型的正则化手段来防止模型的过拟合,然而,这些都是针对参数的正则化形式,往往要求我们修改loss函数。事实上,还有其他方式防止模型过拟合,比如:
Dropout是通过修改网络本身结构以达到正则化效果的技术。Dropout是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃。 注意是暂时 ,对于随机梯度下降来说,由于是随机丢弃,故而每一个mini-batch都在训练不同的网络。
在这节,我们讨论另外一种防止过拟合方法,叫做数据增强(data augmentation),该方法主要对训练数据集进行一定的变换,改变图片的外观,然后将得到的‘新’图片进行模型训练。使用数据增强技术可以得到更多的数据。
备注 :预测时候是不是也可以利用data augmentation,结果使用投票方法得到呢?
数据增强主要是运用各种技术生成新的训练样本,比如随机旋转或平移等,虽然,‘新’的样本在一定程度上改变了外观,但是样本的标签保持不变。使用数据增强技术可以增加模型训练的样本数据,一般而言,当模型不断地学习新的数据时,模型的泛化性会得到一定的提高。但是有一点需要注意,大多数情况下,数据增强会降低模型训练的准确度,而会提高模型测试的准确度,因此,我们一般不参考数据增强下的训练数据集的评估结果,而是参考测试集上的评估结果。
在计算机视觉中,使用数据增强是非常合理的,由于图像数据的特殊性,可以通过简单的几何变换从原始图像中获额外的训练数据,而且不改变图像的标签,常见的变换有:
最直观的明白数据增强的效果的最佳方法就是可视化,可以很直观地看到图像的外观变化。为了实现这个可视化,我们基于keras构建一个数据增强的python脚本,名为augmentation_demo.py,并写入以下代码:
1 |
#encoding:utf-8 |
其中ImageDataGenerator是keras中数据增强类,包含了各种变换方法。
接下来,我们解析命令行参数:
1 |
# 构造参数解析和解析参数 |
其中每个参数详细如下:
接下来,加载输入图像,将其转换为keras支持的array形式数组,并对图像增加一个额外维度。
1 |
# 加载图像,并转化为numpy的array |
初始化ImageDataGenerator:
1 |
aug = ImageDataGenerator( |
ImageDataGenerator类有很多参数,这里无法一一列举。想了解详细的参数意义,请参阅官方的 Keras文档 . 相反,我们列出几个最有可能实际中用到的:
备注 :无论使用哪种变换方法,需要注意我们想要的是增加数据集,只改变图像表面的外观,而不改变原始的图像语义信息。
一旦ImageDataGenerato初始化后,我们就可以产生新的训练数据集:
1 |
print("[INFO] generating images...") |
首先,初始化构造图像数据增强的生成器,并传入参数——输入图像,batch_size设置为1(因为我们这里只增加一个图像),输出的路径等。然后,对imageGen生成器进行遍历,imageGen每次被请求时都会自动生成一个新的训练样本。当新增10张图时,就停止。
执行下列命令,产生数据增强结果:
1 |
$ python augmentation_demo.py --image jemma.png --output output |
当脚本执行完之后,你可以看到:
1 |
ls output/ |
数据增强技术虽然有可能降低模型训练时的准确度,但是,数据增强可以一定程度上降低过拟合,确保我们的模型可以更好地推广到新的输入样本。更重要的是,当我们只有少量数据集时——往往是无法进行深度学习实践的,那么可以利用数据增强生成额外的训练数据,从而减少训练深度学习网络需要手工标记的数据。
在本节的第一部分中,我们将讨论Flowers-17数据集,这是一个非常小的数据集(对计算机视觉任务的而言),以及数据增强如何帮助我们生成额外的训练样本。主要做2个实验:
在Flowers-17上训练MiniVGGNet,不使用数据增强。
在flower -17上使用数据增强技术训练MIniVGGNet。
Flowers-17 数据集
Flowers-17数据集是一个细粒度分类数据,我们的任务是识别17种不同的花。图像数据集非常小,每个类只有80张图像,总共1360张图片。在计算机视觉任务中,应用深度学习算法一般要求每个类别差不多有1000 - 5000个数据,因此,Flowers-17数据集是严重不满足。
我们称Flowers-17为细粒度分类任务,因为所有类别都非常相似(主要花的物种)。事实上,我们可以把这些类别都看作子类别。虽然类别之间是不同的,但是存在相同的结构,比如花瓣,雄蕊、雌蕊等。
细粒度分类任务对于深度学习实践者来说是最具挑战性的,因为这意味着我们的机器学习模型需要学习极端的细粒度特征来区分非常相似的各个类。考虑到我们有限的train数据,这种细粒度的分类任务变得更加困难。
Flowers-17数据可以从 地址 下载。
在此之前,我们处理图像时,一般是将它们调整为固定大小,忽略了纵横比。对于一般的数据集,这样做是可以接受的。但是,对于更具挑战性的数据集,我们将图像大小调整到一个固定的大小时,需要保持长宽比。比如图2.4:
左图是输入的原始图像,我们调整图像的大小为256×256(中间),忽略纵横比,从中间图可以看到图像发生了一定的扭曲。再对比右图,考虑图像的纵横比下,我们首先沿着较短的部分调整大小,比如这里宽度调整为256,然后沿高度裁剪图像,使其高度为256。虽然我们在剪辑过程中丢弃了部分图像,但我们也保留了部分图像原始长宽比。保持一致的纵横比可以使卷积神经网络学习到更有辨别力,一致性的特征。这是我们处理更高级的数据集比如ImageNet用到的一种常见的技术。为了了解预处理,让我们对pyimagesearch项目结构增加一个AspectAwarePreprocessor类:
1 |
--- pyimagesearch |
在preprocessing子模块下,新建一个文件, 名为aspectawarepreprocessor.py ,并写入以下代码;
1 |
#encoding:utf-8 |
就像在SimplePreprocessor中一样,函数需要两个参数(目标输出图像的宽度和高度)以及调整图像所使用的插值方法。定义预处理函数如下:
1 |
def preprocess(self,image): |
预处理函数传入的是需要处理的图像。预处理主要包含两步:
代码如下:
1 |
if w < h: |
我们需要重新抓取宽度和高度,并使用delta裁剪图像的中心:
1 |
(h,w) = image.shape[:2] |
在裁剪过程中(由于舍入误差),目标图像尺寸可能增加或者减小一个像素,因此,我们调用cv2模块调整大小以确保我们的输出图像满足一定的宽度和高度。然后将预处理后的图像返回给调用函数。
数据预处理完之后,接下来主要构建模型以及对模型进行训练和评估。
Flowers-17:不使用数据增强
首先,我们不对Flower-17数据进行数据增强,直接对原始数据训练MiniVGGNe模型,新建一个脚本文件,名为minivggnet_flowers17.py,并写入以下代码:
1 |
#encoding:utf-8 |
说明 : 由于从官方下载的Flower-17数据是没有标签的,因此,我增加了一个模块ImageMove,主要是将图片进行分类,对应到每一个标签文件夹中,并使用0-16代表类别。
定义命令行参数,这里只需要传入图片的数据路径
1 |
ap = argparse.ArgumentParser() |
这里新增一个分类图片模块,使用0-16代表不同的类别
1 |
print('[INFO] moving image to label folder.....') |
数据目录结构:
1 |
原始目录 |
处理完图片目录,接下来从数据中提取数据标签
1 |
print("[INFO] loading images...") |
加载数据集,并对数据进行标准化处理
1 |
# 预处理模块 |
将数据集分成train数据和test数据,并对标签one-hot编码化
1 |
(trainX, testX, trainY, testY) = train_test_split(data, labels,test_size=0.25, random_state=42) |
初始化模型,并进行训练
1 |
# 初始化模型和优化器 |
在数据预处理部分,我们将图片大小调整为64x64,因此,MiniVGGNet网络主要输入shape为64x64x3(像素宽,像素高,通道数)的数据,类别个数是len(classNames),在本例中,它等于17。
使用SGD对模型进行训练,训练次数为100次,并对训练过程进行可视化。
1 |
# 评估模型性能 |
完成上述代码之后,执行下面命令,将得到MiniVGGNet模型训练的结果
1 |
python minivggnet_flowers17.py --dataset yourpath/flowers17 |
1 |
precision recall f1-score support |
此外,从图2.5中可以看到训练精度在不到20次的迭代中已经超过了95%,在最后一次迭代中获得了100%的准确性——很明显地发生了过拟合。由于缺乏大量的训练数据,MiniVGGNet对训练数据的样本学到了过于细微的特征,无法推广到测试数据。为了避免过拟合,我们可以应用正则化技术——在本章的上下文中,我们的正则化方法主要是数据增强。在实践中,您还将包括其他形式的正则化(权值衰减、Dropout等),以进一步减少过拟合的影响。
Flowers-17: Data Augmentation
这一部分,我们将与上节相同的训练过程,唯一不同的是对原始数据进行了数据增强。新建一个脚本,名为minivggnet_flowers17_data_aug.py,并写入以下代码:
1 |
#encoding:utf-8 |
与minivggnet_flowers17.py一样,不同的是第10行新增了数据增强模块。
接下来同样进行数据预处理
1 |
ap = argparse.ArgumentParser() |
数据预处理完之后,在模型训练之前,我们对train数据进行数据增强处理。
1 |
aug = ImageDataGenerator(rotation_range=30, width_shift_range=0.1,height_shift_range=0.1, shear_range=0.2, zoom_range=0.2,horizontal_flip=True, fill_mode="nearest") |
主要的变换有:
通常而言,这些调整的参数值需要根据你的具体数据进行设置,一般而言,旋转幅度控制在[0,30]之间,水平和垂直平移控制在[0.1,0.2](缩放也是一样的),如果水平翻转没有改变图片的语义信息,标签没有发生变化,则应该也使用水平翻转。
接下来。对模型进行初始化
1 |
print("[INFO] compiling model...") |
由于我们使用了数据增强处理,因此,训练模型部分,需要简单进行调整
1 |
# 训练模型 |
需要注意的是,这里使用的是.fit_generator而不是.fit,第一个参数为aug.flow,生成经过数据增强或标准化后的batch数据。flow输入的是对应的训练数据和标签。
steps_per_epoch的含义是一个epoch分成多少个batch_size, 每个epoch以经过模型的样本数达到samples_per_epoch时,记一个epoch结束。如果说训练样本树N=1000,steps_per_epoch = 10,那么相当于一个batch_size=100。
接下来,开始训练模型,并对结果进行可视化。
1 |
# 评估网络 |
注意 :上面我们只对train数据进行增强处理,而对于test数据不做任何处理。
完成上述代码之后,执行下面命令,得到结果
1 |
python minivggnet_flowers17_data_aug.py --dataset yourpath/flowers17 |
1 |
precision recall f1-score support |