FancyKeras-数据的输入(传统)

500 阅读6分钟
原文链接: zhuanlan.zhihu.com
数据输入如流水,流畅就如瀑布倾泻,阻塞就如大学宿舍自来水。

如果你有上过Jeremy Howard和Rachel Tomas的fast.ai课程,课程一开始让你直接把图片扔进一个文件夹里,直接调用里面自定义的“get_batch”、“get_data”函数就能实现数据的输入,是不是特别方便?

今天就来介绍一下用keras实现数据输入的几种方式,因为这个是最最基础的部分,但是很多新手往往会迷失在这一部分,而且如果设置得当,还能加速你的训练过程哦!

首先明确一点,模型是不能直接对图片进行卷积操作的,必须先转化为numpy数组才能输入模型里面去,而且如果数据集的图片尺寸不统一,也有不同的操作细节。

一、单张图片输入

先从最简单的开始,把图片转成numpy array即可,单张图片输入只能用于模型预测的时候。

适用情况:
图片数量:单张
适用场景:模型预测
图片尺寸:统一
import numpy as np
from keras.utils import to_categorical
from keras.preprocessing import image
from keras.applications.resnet50 import ResNet50

file_path = '/image/dogs_001.jpg'
img = image.load_img(file_path, target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)

model = Resnet50(weights='imagenet')
model.predict(x)

解释下步骤,load_img是keras调用了pillow的Image函数,对指定file_path的图片进行提取,然后使用img_to_array把图片转换成为numpy数组,shape为(224, 224, 3),而expand_dims的作用是把shape(224, 224, 3)转换成(1, 224, 224, 3),为什么要这个expand_dims?因为模型本身要求输入尺寸是(None, 224, 224, 3),这个None表示batch,意思是你要输入多少张图片模型是不知道的,所以就用None来表示,而当你输入图片的时候,shape必须跟模型输入的shape保持一致才能输入。

二、多张图片输入

以下代码适用模型预测。多张图片输入思想也很简单,就是把同一个类别的图片文件夹底下所有的图片用for循环load进来,然后加到一个list里面,最后concatenate起来。

适用情况:
图片数量:多张
适用场景:模型预测
图片尺寸:统一
import numpy as np
from keras.preprocessing import image
from keras.applications.resnet50 import ResNet50
import glob

file_path = 'D:/Data/dogs/'
f_names = glob.glob(file_path + '*.jpg')

imgs = []
for i in range(len(f_names)):  # f_names为所有图片地址,list
    img = image.load_img(f_names[i], target_size=(224, 224))  # 读取图片
    arr_img = image.img_to_array(img)  # 图片转换为数组
    arr_img = np.expand_dims(arr_img, axis=0)   # 增加第一个batch维度
    imgs.append(arr_img) # 把图片数组加到一个列表里面
    print("loading no.%s image."%i)
x = np.concatenate([x for x in imgs]) # 把所有图片数组concatenate在一起

print("predicting...")
model = ResNet50(weights='imagenet')
y = model.predict(x)
print("Completed!")

concatenate的作用是把shape为(0, 224, 224, 3)的每张图片tensor,打包成shape为(batch, 224, 224, 3)的tensor,这样就能实现批量预测或批量训练了。

三、生成器输入

很多情况下,你并不能使用以上这些方法来直接输入数据去训练或者预测,原因是你的数据集太大了,没办法把所有的图片都载入到内存当中。那keras的data generator就派上用场了,当你的模型需要训练数据的时候,generator会自动从cpu生成一批图片,喂到GPU里面让模型进行训练,依次循环,直到训练结束。

适用情况:
图片数量:大批量
适用场景:模型预测、训练
图片尺寸:可不统一
from keras.preprocessing import ImageDataGenerator
from keras.applications.resnet50 import ResNet50

trn_path = '/image/trn/'
val_path = '/image/val/'

# 这里先定义一个生成器
generator = ImageDataGenerator() # 这里假设不对原图做任何操作

# 生成器从指定路径中生成图片
trn_data = generator.flow_from_directory(trn_path, batch_size=32,
                                         target_size=(224, 224))
val_data = generator.flow_from_directory(val_path, batch_size=32,
                                         target_size=(224, 224))

# 定义模型
model = ResNet50(weights='imagenet')
model.compile(optimizers='adam', loss='catagorical_crosscentropy', 
              metrics=['accuracy'])

# 训练模型
model.fit_generator(
        train_generator, # 训练集生成器
        steps_per_epoch=2000, # 每一轮训练生成两千个batch
        epochs=50, # 一共训练50轮
        validation_data=validation_generator, # 验证集生成器
        validation_steps=800 # 验证集训练次数)

使用生成器的步骤是,先设计一下这个生成器的“功能”(ImageDataGenerator()),你希望这个生成器能够对你的原图做何种操作,比如旋转、放缩、平移、变色等等,我在这里默认就不作任何操作,有兴趣的可以看我另一篇详解生成器功能的文章:图片数据集太少?看我七十二变,Keras Image Data Augmentation 各参数详解

而“flow_from_directory”的功能是根据你希望的功能,制作一个生成器,这个生成器将会把你的数据从某个路径(trn_path、val_path)生成出来,每次生成32张图片(batch_size),而且生成的都是大小为(224, 224)的图片。

接下来当然是定义模型啦,最后就是把这些生成器都放进"fit_generator"这个机器里面,隆隆隆隆机器就开始训练啦!

慢着!

keras有个很特别的要求,就是上面那个文件夹路径,比如我在"/image/trn/"这个文件夹下面有"cats"、"dogs"两个文件夹,那么keras的generator就会知道我有两个分类,它会自动帮我生成标签,无需自己定义。假如我把路径定位到"/image/trn/cats/"这个文件夹,里面有1000张猫咪图片,那么keras就会认为我有1000个分类!所以请把路径定位到包含所有类别文件夹的那个路径,keras会根据这个路径下有多少个文件夹就会自动生成多少个标签!

上面的适用范围里面写到“图片尺寸可以不统一”,但是如果图片尺寸不统一的话就不能一批一批生成了,请看第四节。

四、图片尺寸不统一的输入

如果你的模型是不定尺寸输入的,也就是说模型的input_shape是(None, None, None, 3)的话,就不能把不同尺寸的图片打包成一个batch进行输入了,因为这种模型在预测或者训练的时候只能接受一种尺寸,如果batch里面有多个尺寸,模型就会不知道使用哪个尺寸来计算输出的shape。目前tensorflow和keras(tf后端)确实不能变尺寸,其它框架不大清楚。所以只能逐张图片进行输入啦。

1. 最土的方式,for loop大法逐个预测:

import blablabla......偷懒不写了

def read_image(path):
    f_names = glob.glob(path + '*.jpg')
    arr_list = []
    for i in range(len(f_names)):  # f_names为所有图片地址,list
        img = image.load_img(f_names[i])  # 读取图片
        arr_img = image.img_to_array(img)  # 图片转换为数组
        arr_img = np.expand_dims(arr_img, axis=0)   # 增加第一个batch维度
        arr_list.append(arr_img)
    return arr_list

def predict_image(model, img_arr_list):
    preds = []
    for i in range(len(img_arr_list)):
        pred = model.predict(img_arr_list[i], batch_size=1, verbose=0)
        preds.append(pred)
    return preds

## model define blablabla......
model = Resnet50()
path = '/image/test/'
arr_list = read_image(path)
preds = predict_image(model, arr_list)

2.generator的方式,一张张生成过去:

from keras.preprocessing import ImageDataGenerator
from keras.applications.resnet50 import ResNet50

trn_path = '/image/trn/'
val_path = '/image/val/'

# 这里先定义一个生成器
generator = ImageDataGenerator() # 这里假设不对原图做任何操作

# 生成器从指定路径中生成图片
trn_data = generator.flow_from_directory(trn_path, batch_size=1) #batch_size注意是1
val_data = generator.flow_from_directory(val_path, batch_size=1)

# 定义模型
model = ResNet50(weights='imagenet')
model.compile(optimizers='adam', loss='catagorical_crosscentropy', 
              metrics=['accuracy'])

# 训练模型
model.fit_generator(
        train_generator, # 训练集生成器
        steps_per_epoch=2000, # 每一轮训练生成两千个batch
        epochs=50, # 一共训练50轮
        validation_data=validation_generator, # 验证集生成器
        validation_steps=800 # 验证集训练次数)

generator几乎不用修改太多的地方,就把batch_size改成1就好,推荐使用generator。generator还可以设置成多进程多线程的方式来输入数据,能加快训练速度,减少GPU等待时间,具体操作就期待《FancyKeras-数据的输入(花式)》一文吧!


如果您喜欢这个专栏,请关注、分享给您的好友,作者会有更大的动力去写作!