Keras数据增强

本文重点讲述了ImageDataGenerator类的应用与测试;在理解该类的原理后编写俩种生成器作为示例方便未来适应多种场景下的数据增强。

ImageDataGenerator参数初始化

参考Keras中文手册其初始化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ImageDataGenerator是 数据扩增类;下面代码即是类的初始化
keras.preprocessing.image.ImageDataGenerator(featurewise_center=False,#布尔值,使输入数据集去中心化(均值为0), 按feature执行
samplewise_center=False,#布尔值,使输入数据的每个样本均值为0
featurewise_std_normalization=False,#布尔值,将输入除以数据集的标准差以完成标准化, 按feature执行
samplewise_std_normalization=False,#布尔值,将输入的每个样本除以其自身的标准差
zca_whitening=False,#布尔值,对输入数据施加ZCA白化
zca_epsilon=1e-6,# ZCA使用的eposilon,默认1e-6
rotation_range=0,#整数,数据提升时图片随机转动的角度
width_shift_range=0,#浮点数,图片宽度的某个比例,数据提升时图片水平偏移的幅度
height_shift_range=0,#浮点数,图片高度的某个比例,数据提升时图片竖直偏移的幅度
shear_range=0,#浮点数,剪切强度(逆时针方向的剪切变换角度)
zoom_range=0,#浮点数或形如[lower,upper]的列表,随机缩放的幅度,若为浮点数,则相当于[lower,upper] = [1 - zoom_range, 1+zoom_range]
channel_shift_range=0,#浮点数,随机通道偏移的幅度
fill_mode="nearest",#‘constant’,‘nearest’,‘reflect’或‘wrap’之一,当进行变换时超出边界的点将根据本参数给定的方法进行处理
cval=0.,#浮点数或整数,当fill_mode=constant时,指定要向超出边界的点填充的值
horizontal_flip=False,#布尔值,进行随机水平翻转
vertical_filp=False,#布尔值,进行随机竖直翻转
rescale=None,#重放缩因子,默认为None. 如果为None或0则不进行放缩,否则会将该数值乘到数据上(在应用其他变换之前)
preprocessing_function=None,#将被应用于每个输入的函数。该函数将在任何其他修改之前运行。该函数接受一个参数,为一张图片(秩为3的numpy array),并且输出一个具有相同shape的numpy array
data_format=K.image_data_format())#字符串,“channel_first”或“channel_last”之一,代表图像的通道维的位置。

图像仿射变换对应的矩阵操作

数据扩增示例

  1. 下载猫狗大战数据集

  2. 划分数据集参考我的博客python小工具

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #建立data文件夹,如下树状结构(不要包含其他文件夹)
    data/
    train/
    dog/
    cat/
    val/
    dog/
    cat/
    import os
    import shutil
    dog_iamge_list = os.listdir('./data/Dog/')
    cat_iamge_list = os.listdir('./data/Cat/')
    dog_nums = len(dog_iamge_list)#狗狗图片数目
    cat_nums = len(cat_iamge_list)#...
    train_path = './data/train/'
    val_path = './data/val/'

    for idx,img in enumerate(dog_iamge_list):
    if idx < 1000:
    shutil.move('./data/Dog/'+img,'./data/'+'train/dog/') #移动文件到文件夹
    else:#可以稍微设置下验证集的数量,不然训练测试速度比较慢
    shutil.move('./data/Dog/'+img,'./data/'+'val/dog/')
    for idx,img in enumerate(cat_iamge_list):
    if idx < 1000:
    shutil.move('./data/Cat//'+img,'./data/'+'train/cat/')
    else:
    shutil.move('./data/Cat//'+img,'./data/'+'val/cat/')
  3. 测试数据增强效果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #测试数据增强效果
    from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img
    datagen = ImageDataGenerator(
    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')

    img = load_img('./data/train/cat/1.jpg') # 导入数据
    x = img_to_array(img) # 转成Numpy array格式 (3,150,150)
    x = x.reshape((1,) + x.shape) # Reshape为 (1, 3, 150, 150)

    # datagen.flow() 生成器 参考我前面的博客,用for分批读取
    i = 0
    for batch in datagen.flow(x, batch_size=1,save_to_dir='./test_gen_img', save_prefix='cat', save_format='jpeg'):
    i += 1
    if i > 17:
    break
    原图
    原图
    生成图像
    生成图像

使用ImageDataGenerator进行实战

1
2
#build model
见前面教程:本例是二分利之一输出层的激活函数选择sigmoid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#构建生成器
batch_size = 16
train_datagen = ImageDataGenerator(
rescale=1./255,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True)
#测试集只需要和训练机一样rescale
test_datagen = ImageDataGenerator(rescale=1./255)
#train或者val的每个子目录表示一个类
#训练集生成器
train_generator = train_datagen.flow_from_directory(
'./data/train', # 训练集路径
target_size=(150, 150), # 图片尺寸,自动resize
batch_size=batch_size,
class_mode='binary') # 标签模式
#验证集生成器
validation_generator = test_datagen.flow_from_directory(
'./data/val',
target_size=(150, 150),
batch_size=batch_size,
class_mode='binary')
1
2
3
4
5
6
7
8
#训练模型
#win10可能会遇到不能识别的图片,直接删除
model.fit_generator(
train_generator,
steps_per_epoch=2000 // batch_size,
epochs=50,
validation_data=validation_generator,
validation_steps=800 // batch_size)

编写自定义生成器

总结一下就是继承keras.utils.Sequence基类,重写__getitem__函数,[示例](https://github.com/yu4u/noise2noise)如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from keras.utils import Sequence
class NoisyImageGenerator(Sequence):#继承Sequence基类
def __init__(self, image_dir, source_noise_model, target_noise_model, batch_size=32, image_size=64):
self.image_paths = list(Path(image_dir).glob("*.jpg"))
self.source_noise_model = source_noise_model
self.target_noise_model = target_noise_model
self.image_num = len(self.image_paths)
self.batch_size = batch_size
self.image_size = image_size

def __len__(self):#重写len返回函数
return self.image_num // self.batch_size

def __getitem__(self, idx):#重写getitem,返回数据批
batch_size = self.batch_size
image_size = self.image_size
x = np.zeros((batch_size, image_size, image_size, 3), dtype=np.uint8)
y = np.zeros((batch_size, image_size, image_size, 3), dtype=np.uint8)
sample_id = 0
while True:
image_path = random.choice(self.image_paths)
image = cv2.imread(str(image_path))
h, w, _ = image.shape

if h >= image_size and w >= image_size:
h, w, _ = image.shape
i = np.random.randint(h - image_size + 1)
j = np.random.randint(w - image_size + 1)
clean_patch = image[i:i + image_size, j:j + image_size]
x[sample_id] = self.source_noise_model(clean_patch)
y[sample_id] = self.target_noise_model(x[sample_id])
# y[sample_id] = self.target_noise_model(clean_patch)
sample_id += 1
if sample_id == batch_size:
return x, y


class ValGenerator(Sequence):#
def __init__(self, image_dir, val_noise_model):#在初始化函数里面处理完所有数据再在__getitem__返回,加载数据效率较低
image_paths = list(Path(image_dir).glob("*.*"))
self.image_num = len(image_paths)
self.data = []

for image_path in image_paths:
y = cv2.imread(str(image_path))
h, w, _ = y.shape
y = y[:(h // 16) * 16, :(w // 16) * 16] # for stride (maximum 16)
x = val_noise_model(y)
self.data.append([np.expand_dims(x, axis=0), np.expand_dims(y, axis=0)])

def __len__(self):
return self.image_num

def __getitem__(self, idx):
return self.data[idx]

Reference