前言:前面已经详细介绍了keras整个图像预处理模块的详细流程,参见下面两篇文章:
keras的图像预处理全攻略(二)—— ImageDataGenerator 类
keras的图像预处理全攻略(三)—— ImageDataGenerator 类的辅助类
本文结合实际案例来说说明到底该怎么应用 ImageDataGenerator 类以及需要注意的一些事项
一、图像分类问题中ImageDataGenerator 类的flow方法的应用
二、图像分类问题中ImageDataGenerator 类的flow_from_directory方法应用
三、对于分割数据集使用ImageDataGenerator类
一、图像分类问题中ImageDataGenerator 类的flow方法的应用
我们经常遇见一些分类问题,往往由于数据量较小的原因,导致模型很容易过拟合,所以需要进行数据增强,本文以最简单的mnist手写字为例子来加以说明ImageDataGenerator 类的简单应用。再说明之前,我们学要结合系列文章的第二篇的相关知识点,ImageDataGenerator 类的使用分为“四步走”策略。
1.1 第一步:数据集的划分,得到x_train,y_train,x_test,y_test;
from keras.preprocessing.image import ImageDataGenerator
from keras.datasets import mnist
from keras.datasets import cifar10
from keras.utils import np_utils
import numpy as np
import matplotlib.pyplot as plt
# 第一步:划分数据集。得到x_train,y_train,x_test,y_test;
num_classes = 10
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = np.expand_dims(x_train, axis = 3)
y_train = np_utils.to_categorical(y_train, num_classes)
y_test = np_utils.to_categorical(y_test, num_classes)
print(np.shape(x_train)) #返回(60000, 28, 28, 1)
print(np.shape(x_test)) #返回(10000, 28, 28),因为这里没有对测试数据进行维度扩充,这里不需要使用到测试数据,所以没管它
print(np.shape(y_train)) #返回(60000, 10)
print(np.shape(y_test)) #返回(10000, 10)
print("======================================================")
注意:由于mnist.npz会默认没有的,需要下载,但是国内下载实在是太慢,所以我是已经下载好了并且保存在了用户文件夹之下的.keras/datasets里面了。所以不需要下载的,网上有很多的下载资源,也可以找我要数据,大改10M。
1.2 第二步:构造ImageDataGenerator对象
其中要进行某一些操作是通过在构造函数中的参数指定的,datagen = ImageDataGenerator(......)
#第二步骤:构造ImageDataGenerator类的对象,通过参数指定要进行处理项目
#对数据进行预处理,注意这里不是一次性要将所有的数据进行处理完,而是在后面的代码中进行逐批次处理
datagen = ImageDataGenerator(
featurewise_center=True,
featurewise_std_normalization=True,
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True)
1.3 第三步:对样本数据进行data augmentation处理,通过fit方法
#第三步:对需要处理的数据进行fit
datagen.fit(x_train)
1.4 产生迭代对象,通过flow方法,并且查看每一次迭代对象的信息
#第四步:使用.flow方法构造Iterator,
data_iter = datagen.flow(x_train, y_train, batch_size=8,save_to_dir="save_data") #返回的是一个“生成器对象”
print(type(data_iter)) #返回的是一个NumpyArrayIterator对象
# 通过循环迭代每一次的数据,并进行查看
for x_batch,y_batch in data_iter:
for i in range(8):
plt.subplot(2,4,i+1)
plt.imshow(x_batch[i].reshape(28,28), cmap='gray')
plt.show()
这里是核心点,有几个需要注意的地方:
(1)迭代器的数据格式
我们通过ImageDataGenerator类来对训练数据做处理,目的是产生一个迭代器,然后将每一次迭代产生的数据放进model.fit_generator()里面进行训练,而迭代器的数据格式如下:
- 一个
(inputs, targets)
元组 - 一个
(inputs, targets, sample_weights)
元组。
(2)flow函数save_to_dir参数
这个参数会将没一个批次所进行data augmentation之后的图像保存在指定的文件夹,它的作用是方便对于处理之后的图像进行查看。比如我上面保存到了当前文件之下的save_data文件夹里面。
(3)迭代器可以通过for进行循环,我也可以直接通过next()方法来进行迭代,上面的第四步等价与下面的代码:
while True:
x_batch, y_batch = data_iter.next()
for i in range(8):
plt.subplot(2,4,i+1)
plt.imshow(x_batch[i].reshape(28,28), cmap='gray')
plt.show()
(4)flow是无限迭代的。也就是说每一次迭代8组样本,等一个轮回迭代完之后依然会继续重新迭代,不会自己停止。
记住:flow()、flow_from_dataframe()、flow_from_directory()都是无限迭代的。
但是这个地方一直有一个问题,我还没找到答案,望大神告知!
如果总的样本数samples能够被batch_size整除,那么恰好可以每一次都恰好对batch_size个样本进行处理,这样当所有的样本都处理完之后,又会进入下一次循环迭代,即迭代不会终止;但是如果样本数samples不能够被batch_size整除,那么最后一个batch_size会少几个样本,这样在所有的样本数迭代完成一次之后就不再循环迭代了,也就是所有的样本只迭代一次,这倒是怎么回事,我还不太清楚,我是在实验中尝试得出的结果,网友大神告知,谢谢!
1.5 运行结果展示
由于图片很多,每一次仅仅只处理8张图片,所以每次要查看很多次才能将所有的样本数据完全看完,本文在运行了三次迭代器之后就停止了程序的运行,得到的运行结果,和保存的数据展示如下:
这是三次迭代,每一次迭代产生的8张图片,我自己保存的sav_data文件夹之下的24张图片如下:
二、图像分类问题中ImageDataGenerator 类的flow_from_directory方法应用
为了演示flow_from_dirctory()方法的效果,本文没有使用其它的训练数据集,自己随便找几张图片做实验就可以了。
本试验依然是以分类问题为背景,自己选择了15张约翰逊斯嘉丽的图片作为训练数据集,15张图片分为三类,分别是happy、smile、silent,另外下载了5张图片作为测试集,类别是一样的这三类,文件夹的组织结构如下所示:
├─Scarlett_Johansson_test
│ ├─happy
│ │ 2001.jpg
│ │
│ ├─silent
│ │ 2002.jpg
│ │ 2003.jpg
│ │
│ └─smile
│ 2004.jpg
│ 2005.jpg
│
└─Scarlett_Johansson_train
├─happy
│ 1006.jpg
│ 1007.jpg
│ 1008.jpg
│ 1009.jpg
│ 1010.jpg
│
├─silent
│ 1011.jpg
│ 1012.jpg
│ 1013.jpg
│ 1014.jpg
│ 1015.jpg
│
└─smile
1001.jpg
1002.jpg
1003.jpg
1004.jpg
1005.jpg
即Scarlett_Johansson_data数据文件夹之下分为Scarlett_Johansson_test和Scarlett_Johansson_train两个文件夹,这两个文件夹之下没一个里面都包含smile、happy、silent三个类别的子文件夹,每一个字文件夹之下都保存着相应的样本数据。
下面开始写代码:
2.1 第一步:构建ImageDataGenerator类的对象
from keras.preprocessing.image import ImageDataGenerator
import numpy as np
import matplotlib.pyplot as plt
#第一步:对测试数据和训练数据分别构造一个ImageDataGenerator对象
train_datagen = ImageDataGenerator(
rescale=1./255,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True)
test_datagen = ImageDataGenerator(rescale=1./255)
2.2 第二步:调用flow_from_directory方法
#第二步:分别对训练数据和测试数据使用flow_from_directory方法
train_generator = train_datagen.flow_from_directory(
'Scarlett_Johansson_data/Scarlett_Johansson_train',
target_size=(150, 150), #将图片大小转化为150x150
batch_size=5, #每一次处理6张图片
class_mode='categorical', #对类别进行独热编码
save_to_dir="Scarlett_Johansson_data_augmentation", #将augmentation之后的图片保存
save_prefix="sjl_") #将augmentation之后的图片添加sjl_前缀
validation_generator = test_datagen.flow_from_directory(
'Scarlett_Johansson_data/Scarlett_Johansson_test',
target_size=(150, 150),
batch_size=2,
class_mode='binary')
print('##############################################################')
注意:本例子中一共有三个类别,及三个不同的文件夹smile、happy、silent,程序会自动判别一共有3各类型,这里有一个很重要的参数class_mode,
如果class_mode==“binary”,则输出的类别是0,1,2,来分别表示三个类,
如果是class_mode==“'categorical”,则这三个类别会用独热编码的方式来表示,即【0,0,1】、【0,1,0】、【1,0,0】.
2.3 第三步:迭代训练数据
这里只对训练数据train_generator进行迭代,由于一共只有15组训练样本,所以每一次迭代5组样本,恰好可以除尽,这样可以循环迭代而不终止。本文选择迭代9次,即每个样本共产生3个数据增强的样本,共产生15*3=45组样本。代码如下:
#第三步:迭代查看,这里只查看训练数据集的,测试数据集的就不管了
count=1
for x_batch,y_batch in train_generator:
print(F"------------开始第{count}次迭代-----------------------------")
print(F"------------x_batch、y_batch的形状如下----------------------")
print(np.shape(x_batch),np.shape(y_batch))
print('-------------y_batch打印结果如下-----------------------------')
print(y_batch)
print('============================================================')
#将每次augmentation之后的5幅图像显示出来
for i in range(5):
plt.subplot(1,5,i+1)
plt.imshow(x_batch[i].reshape(150,150,3))
plt.savefig("Scarlett_Johansson_data_iter_time/iter_time_"+str(count)+".png",format="png")
plt.show()
count+=1
程序运行的结果如下:
'''
Using TensorFlow backend.
Found 15 images belonging to 3 classes. #这是调用train_datagen.flow_from_directory()后打印的
Found 5 images belonging to 3 classes. ##这是调用test_datagen.flow_from_directory()后打印的
##############################################################
------------开始第1次迭代-----------------------------
------------x_batch、y_batch的形状如下----------------------
(5, 150, 150, 3) (5, 3)
-------------y_batch打印结果如下-----------------------------
[[0. 0. 1.]
[0. 1. 0.]
[0. 1. 0.]
[0. 0. 1.]
[0. 1. 0.]]
============================================================
.
.
. 中间的省略了,一共迭代了9次
.
.
------------开始第9次迭代-----------------------------
------------x_batch、y_batch的形状如下----------------------
(5, 150, 150, 3) (5, 3)
-------------y_batch打印结果如下-----------------------------
[[0. 1. 0.]
[0. 0. 1.]
[0. 1. 0.]
[1. 0. 0.]
[1. 0. 0.]]
============================================================
'''
2.4 程序运行的结果查看
(1)Scarlett_Johansson_data_iter_time文件夹——即每次迭代后使用plt预览的6张图片的文件夹
(2)Scarlett_Johansson_data_augmentation文件夹——即迭代6次,共产生了9x5=45张增强图片的效果
从上面可以看出,对15张样本数据分别都进行了三次增强,每挨着的3张图片对比可以发现,图像是不一样的,有的经过了翻转即flip操作、有的经过了缩放即zoom操作、有的经过了错切即shear操作,这其实都是来源于ImageDataGenerator中构造函数参数的指定来实现的。
三、对于分割数据集使用ImageDataGenerator类
由于只是做实验,本次是自己在voc2012数据集中只选择了15张样本数据来进行试验,只选择了三种类型,分别是飞机airplane、人person、火车car(因为火车的单词train与训练的train相同,这里就暂时使用car代替一下),文件目录的组织方式同上面的案例是一样的,如下:
├─mask_data
│ └─train
│ ├─airplane
│ │ 2007_000032.png
│ │ 2007_000033.png
│ │ 2007_000256.png
│ │ 2007_001288.png
│ │ 2007_001568.png
│ │
│ ├─car
│ │ 2007_000042.png
│ │ 2007_000123.png
│ │ 2007_000629.png
│ │ 2007_000636.png
│ │ 2007_004951.png
│ │
│ └─person
│ 2007_000129.png
│ 2007_000170.png
│ 2007_000323.png
│ 2007_000346.png
│ 2007_000999.png
│
└─source_data
└─train
├─airplane
│ 2007_000032.jpg
│ 2007_000033.jpg
│ 2007_000256.jpg
│ 2007_001288.jpg
│ 2007_001568.jpg
│
├─car
│ 2007_000042.jpg
│ 2007_000123.jpg
│ 2007_000629.jpg
│ 2007_000636.jpg
│ 2007_004951.jpg
│
└─person
2007_000129.jpg
2007_000170.jpg
2007_000323.jpg
2007_000346.jpg
2007_000999.jpg
注意:为什么针对分割模型这里单独拿出来,主要是因为分割模型的特殊性,它不仅有原始图像,还有与原始图像相对应的的分割标签图像,这里称之为mask图形,我对原始图形使用了某一种变换,必须给相对应的mask图形使用相同的变换进行变换,这才能保证训练数据与mask之间的一一对应关系不变。具体的实现如下:
3.1 第一步:构造ImageDataGenerator类的对象,
由于是需要对image和mask构造相同的变换,所以这里通过一个字典来构造该类的参数,如下:
from keras.preprocessing.image import ImageDataGenerator
import numpy as np
import matplotlib.pyplot as plt
#第一步:对测试数据和训练数据分别构造一个ImageDataGenerator对象
#注意:由于原始训练数据和分割数据是绑定的,有对应关系,这里要创建两个相同参数的实例
data_gen_args = dict(featurewise_center=True,
featurewise_std_normalization=True,
rescale=1./255,
rotation_range=90,
width_shift_range=0.1,
height_shift_range=0.1,
zoom_range=0.2)
image_datagen = ImageDataGenerator(**data_gen_args) #构造训练数据image的对象
mask_datagen = ImageDataGenerator(**data_gen_args) #构造分割数据mask的对象
3.2 第二步:同时对image和mask调用flow_from_directory方法
这个地方需要注意的是,由于flow_from_directory方法会从训练数据中随机选择一个批次的图片进行变换,为了保持原始图像与分割图像mask之间的一一对应关系,需要指定一个随机数种子seed。(备注:需要懂得到底什么是伪随机数,什么时随机数种子哦?)
另外,由于这个地方只考虑如何将训练的image和分割的mask进行统一的变换,类别就先不做讨论了,所以设置
class_mode=None.
另外这里没有设置save_to_dir参数,就不保存变换之后的原始图像image和变换之后的mask图像了,后面预览一下即可
#第二步:分别对训练数据和测试数据使用flow_from_directory方法,由于伪随机性的特质,这里需要
#为原始数据和mask标注数据提供相同的种子
seed = 1
image_generator = image_datagen.flow_from_directory(
'segment_data/source_data/train',
batch_size=5,
target_size=(150, 150),
class_mode=None, #这个地方将class_mode设置为None,即不返回类别标签
seed=seed)
mask_generator = mask_datagen.flow_from_directory(
'segment_data/mask_data/train',
batch_size=5,
target_size=(150, 150),
class_mode=None, #这个地方将class_mode设置为None,即不返回类别标签
seed=seed)
print('##############################################################')
3.3 第二步:同时对image和mask进行迭代
#第三步:迭代查看,这里只查看训练数据集的,测试数据集的就不管了
# 将生成器组合成一个产生图像和蒙版(mask)的生成器
train_generator = zip(image_generator, mask_generator)
count=1
#犹豫期前面没有返回类别标签,这里迭代器只返回原始图像数据和mask对应的数据
for x_batch_image,x_batch_mask in train_generator:
print(F"------------开始第{count}次迭代-----------------------------")
print(F"------------x_batch_image、x_batch_mask的形状如下----------------------")
print(np.shape(x_batch_image),np.shape(x_batch_mask))
print(x_batch_image[0])
print('============================================================')
#将每次augmentation之后的5幅图像显示出来
for i in range(5):
plt.subplot(2,5,i+1)
plt.imshow(x_batch_image[i]) #由于本来就是(150,150,3)的格式,所以不需要再reshape了
plt.subplot(2,5,5+i+1)
plt.imshow(x_batch_mask[i])
#plt.savefig("Scarlett_Johansson_data_iter_time/iter_time_"+str(count)+".png",format="png")
plt.show()
count+=1
这里进行五次迭代,程序运行的结果如下:
‘'''
Using TensorFlow backend.
Found 15 images belonging to 3 classes.
Found 15 images belonging to 3 classes.
##############################################################
D:\ProgramData\Anaconda3\envs\tensorflow1.10.0\lib\site-packages\keras_preprocessing\image\image_data_generator.py:699: UserWarning: This ImageDataGenerator specifies `featurewise_center`, but it hasn't been fit on any training data. Fit it first by calling `.fit(numpy_data)`.
warnings.warn('This ImageDataGenerator specifies '
D:\ProgramData\Anaconda3\envs\tensorflow1.10.0\lib\site-packages\keras_preprocessing\image\image_data_generator.py:707: UserWarning: This ImageDataGenerator specifies `featurewise_std_normalization`, but it hasn't been fit on any training data. Fit it first by calling `.fit(numpy_data)`.
warnings.warn('This ImageDataGenerator specifies '
------------开始第1次迭代-----------------------------
------------x_batch_image、x_batch_mask的形状如下----------------------
(5, 150, 150, 3) (5, 150, 150, 3)
============================================================
------------开始第2次迭代-----------------------------
------------x_batch_image、x_batch_mask的形状如下----------------------
(5, 150, 150, 3) (5, 150, 150, 3)
============================================================
------------开始第3次迭代-----------------------------
------------x_batch_image、x_batch_mask的形状如下----------------------
(5, 150, 150, 3) (5, 150, 150, 3)
============================================================
------------开始第4次迭代-----------------------------
------------x_batch_image、x_batch_mask的形状如下----------------------
(5, 150, 150, 3) (5, 150, 150, 3)
============================================================
------------开始第5次迭代-----------------------------
------------x_batch_image、x_batch_mask的形状如下----------------------
(5, 150, 150, 3) (5, 150, 150, 3)
============================================================
'''
注意:中间有一段警告信息,说由于我在构造ImageDataGenerator类的对象的时候,使用了
featurewise_center=True,
featurewise_std_normalization=True,
这两个参数,这就需要从这两个参数的意义说起了,可以参考我前面的系列文章(二),这里不再赘述,简单就是说这里需要使用到样本数据的统计信息,但是因为我这里没有原先的样本数据,所以就没有用,使用了上面的两个参数之后,第一步需要先使用fit()函数在样本数据上进行“拟合”(引号,明白意思就行),根据统计信息进行计算,所以这里会进行警告。
3.4 变化结果展示
从上面的结果可以看出,原始图像image和分割标注图像mask的变化是同步进行的,他们的对应关系没有被打乱。