Keras中导入数据的方法,包括快速导入大规模的自制数据集

在对Keras框架的学习中,一个很大的难点就是数据的导入,尤其是当数据不能一次放入内存的时候,应该如何导入的问题。在Keras的官网,没有章节特意讲这个内容,而专门去找资料,也很难找到相关的内容。绝大多数的教程都是直接使用的Keras自带的数据集。为了处理大量数据的情况,我还特意研究了Python的多线程。后来我还知道了导入数据的时候的随机性的重要性等各种问题。这篇文章算是一个总结。

详细内容参见链接:https://www.jianshu.com/p/0fbe5b5d0ab8
来源:简书

方便查看搬运至此(含个人解读):
一、Keras里面集成的数据集读取:
如果看过我前面的文章Keras入门与LeNet的实现,应该知道Keras里面有很多经典的数据集。当我们研究自己的模型的时候,只需要拿出其中与我们要研究的问题类似的数据集进行试验就行了。比如之前提到的手写数字识别的经典数据集mnist。就只需要一行代码:

(X_train, y_train), (X_test, y_test) = mnist.load_data()

就可以成功导入了。在Keras官网上面有各个常用数据库的导入方法,这使得使用这些经典的数据库特别简单。
但是我们使用Keras是不完全是为了研究自己的model,还可能是为了解决实际问题。这个时候,我们就要创造自己的数据集,并且把数据集运用到自己的模型之中。

二、自制数据集的读取
创造数据集是一件比较难的事情。尤其是要创造大量的、靠谱的数据集。通常来说,很多数据集只能通过大公司去收集,而不能自己创造。但是凡事总有例外。有的时候我们还是能够自己创造数据集的。例如我们的OCR。我们只需要把字符进行变形就可以生成我们的数据集了。(当然,如果要考虑手写的顺序等问题,这就不够了。)关于生成字符识别数据集,请参考我之前的文章。
1、一次性读入到内存(适合小数据集)
接着,我来说明我查到的第一种运用自己的数据集的方法。这种方法需要一次性地把数据放入内存,因此数据量不能过大

原理也很简单。考虑到Keras的输入数据是numpy、float类型,因此我们只需要把图片读入,然后转成numpy就行了。

首先,我们的目录结构是这样的:

./words
  ./0
    0.png
    1.png
    2.png
    ...
   /1
     0.png
     1.png
     2.png
      ...
   /2
   ...

先写一个读图片的函数,我们用这个函数把Image类型转成numpy类型。

def read_image(imageName):
    im = Image.open(imageName).convert('L')
    data = np.array(im)
    return data

我们创造一个images的列表和labels的列表,用来存图片和对应的结果。接着,我们把图片和它对应的结果读入:

# 读取在words里面有几个文件夹
text = os.listdir('./words')

# 把文件夹里面的图片和其对应的文件夹的名字也就是对应的字
for textPath in text:
    for fn in os.listdir(os.path.join('words', textPath)):
        if fn.endswith('.png'):
            fd = os.path.join('./words', textPath, fn)
            images.append(read_image(fd))
            labels.append(textPath)

接着我们把刚刚得到的images和labels也变成numpy类型。当然,labels首先要变成int类型。

X = np.array(images)
y = np.array(list(map(int, labels)))

最后,我们按三七分把这些分为训练集和测试集。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=30)

数据的导入工作就完成了。但是显然,这样做的话我们必须一次性把所有数据读入内存。当我们的数据量特别大的时候,这肯定是行不通的。就算数据量不大,这样也会浪费很多时间在IO上面。我们的希望的是,在训练的时候拿数据,一份一份地训练。
2、批量读取数据(个人不推荐)
这是一个很难解决的问题,在网上很难找到对应的教程专门提到这一点。大神们都默认这是一个很简单的问题。但是对于新手来说,这却是一个很难的问题。我甚至考虑过用多线程去写。但是甚至读入的数据的顺序,都会影响最后的结果。如果我们一批一批地训练,就容易使得最后的结果偏向我们最后导入的数据,从而过拟合。最后我在查阅了Keras的官方文档,并且查看了很多相关的内容之后,找到了解决方法。

原来,Keras的训练不仅仅有fit,还有fit_generator,也就是一个一个训练。***fit_generator***的API如下:

fit_generator(self, generator, steps_per_epoch, epochs=1, verbose=1, callbacks=None, validation_data=None, validation_steps=None, class_weight=None, max_q_size=10, workers=1, pickle_safe=False, initial_epoch=0)

文档是这样写的:

函数的参数:

generator:生成器函数,生成器的输出应该为:
一个形如(inputs,targets)的tuple
一个形如(inputs, targets,sample_weight)的tuple。所有的返回值都应该包含相同数目的样本。生成器将无限在数据集上循环。每个epoch以经过模型的样本数达到samples_per_epoch时,记一个epoch结束
steps_per_epoch:整数,当生成器返回steps_per_epoch次数据时计一个epoch结束,执行下一个epoch
epochs:整数,数据迭代的轮数
verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,2为每个epoch输出一行记录
validation_data:具有以下三种形式之一
生成验证集的生成器
一个形如(inputs,targets)的tuple
一个形如(inputs,targets,sample_weights)的tuple
validation_steps: 当validation_data为生成器时,本参数指定验证集的生成器返回次数
class_weight:规定类别权重的字典,将类别映射为权重,常用于处理样本不均衡问题。
sample_weight:权值的numpy array,用于在训练时调整损失函数(仅用于训练)。可以传递一个1D的与样本等长的向量用于对样本进行11的加权,或者在面对时序数据时,传递一个的形式为(samples,sequence_length)的矩阵来为每个时间步上的样本赋不同的权。这种情况下请确定在编译模型时添加了sample_weight_mode='temporal'。
workers:最大进程数
max_q_size:生成器队列的最大容量
pickle_safe: 若为真,则使用基于进程的线程。由于该实现依赖多进程,不能传递non picklable(无法被pickle序列化)的参数到生成器中,因为无法轻易将它们传入子进程中。
initial_epoch: 从该参数指定的epoch开始训练,在继续之前的训练时有用。

这个里面,只有前面六个是比较重要的,其他的默认就行了。甚至我们只需要前面三个就行了。steps_per_epoch和epochs都很好理解。这个generator,也就是生成器函数,才是问题的关键。

接下来就非常简单了。在keras系列︱**利用fit_generator最小化显存占用比率/数据Batch化**这篇博客里面已经讲得很清楚了。

里面有个demo一般的生成器函数:

def generate_batch_data_random(x, y, batch_size):
    """逐步提取batch数据到显存,降低对显存的占用"""
    ylen = len(y)
    loopcount = ylen // batch_size
    while (True):
        i = randint(0,loopcount)
        yield x[i * batch_size:(i + 1) * batch_size], y[i * batch_size:(i + 1) * batch_size]

这里应该注意的有两点,第一点就是数据必须是要打乱的,没有规律的。第二点就是最后用的是yield。这也是Python的一个高级特性了。简单地说,这就是一个return。但是你每调用一次,它就返回一次,而不像其他函数一样,return了就出去了。这样就成为了一个生成器。具体可以看廖雪峰的Python教程

剩下的就很简单了。我们甚至可以直接在这个生成器函数里面写图片生成的算法。当然,考虑到IO操作肯定比直接生成要快,直接生成肯定是不可取的。
3、更快的导入数据方法(推荐)
当然,这不是最好的导入数据的方法。Keras还有更快的方法。在介绍这个方法之前,我需要先介绍Keras的图像预处理的方法。

为了防止图像的过拟合,Keras里面自带了图片生成器ImageDataGenerator用来对图像进行一些简单的操作,例如平移,旋转,缩放等等。这样我们就可以在有限的数据集上面生成无限的训练样本。这样可以扩大训练集的大小,防止图像的过拟合。具体的内容可以查看图片生成器ImageDataGenerator的文章

keras.preprocessing.image.ImageDataGenerator(featurewise_center=False,  
                                             samplewise_center=False, 
                                             featurewise_std_normalization=False, 
                                             samplewise_std_normalization=False, 
                                             zca_whitening=False, 
                                             zca_epsilon=1e-06, 
                                             rotation_range=0, 
                                             width_shift_range=0.0, 
                                             height_shift_range=0.0, 
                                             brightness_range=None, 
                                             shear_range=0.0, 
                                             zoom_range=0.0, 
                                             channel_shift_range=0.0, 
                                             fill_mode='nearest', 
                                             cval=0.0, 
                                             horizontal_flip=False, 
                                             vertical_flip=False, 
                                             rescale=None, 
                                             preprocessing_function=None, 
                                             data_format=None, 
                                             validation_split=0.0, 
                                             dtype=None)

通过实时数据增强生成张量图像数据批次。数据将不断循环(按批次)。

参数:


featurewise_center: 布尔值。将输入数据的均值设置为 0,逐特征进行。
samplewise_center: 布尔值。将每个样本的均值设置为 0。
featurewise_std_normalization: Boolean. 布尔值。将输入除以数据标准差,逐特征进行。
samplewise_std_normalization: 布尔值。将每个输入除以其标准差。
zca_epsilon: ZCA 白化的 epsilon 值,默认为 1e-6。
zca_whitening: 布尔值。是否应用 ZCA 白化。
rotation_range: 整数。随机旋转的度数范围。
width_shift_range: 浮点数、一维数组或整数
float: 如果 <1,则是除以总宽度的值,或者如果 >=1,则为像素值。
1-D 数组: 数组中的随机元素。
int: 来自间隔 (-width_shift_range, +width_shift_range) 之间的整数个像素。
width_shift_range=2 时,可能值是整数 [-1, 0, +1],与 width_shift_range=[-1, 0, +1] 相同;而 width_shift_range=1.0 时,可能值是 [-1.0, +1.0) 之间的浮点数。
height_shift_range: 浮点数、一维数组或整数
float: 如果 <1,则是除以总宽度的值,或者如果 >=1,则为像素值。
1-D array-like: 数组中的随机元素。
int: 来自间隔 (-height_shift_range, +height_shift_range) 之间的整数个像素。
height_shift_range=2 时,可能值是整数 [-1, 0, +1],与 height_shift_range=[-1, 0, +1] 相同;而 height_shift_range=1.0 时,可能值是 [-1.0, +1.0) 之间的浮点数。
shear_range: 浮点数。剪切强度(以弧度逆时针方向剪切角度)。
zoom_range: 浮点数 或 [lower, upper]。随机缩放范围。如果是浮点数,[lower, upper] = [1-zoom_range, 1+zoom_range]。
channel_shift_range: 浮点数。随机通道转换的范围。
fill_mode: {"constant", "nearest", "reflect" or "wrap"} 之一。默认为 'nearest'。输入边界以外的点根据给定的模式填充:
'constant': kkkkkkkk|abcd|kkkkkkkk (cval=k)
'nearest': aaaaaaaa|abcd|dddddddd
'reflect': abcddcba|abcd|dcbaabcd
'wrap': abcdabcd|abcd|abcdabcd
cval: 浮点数或整数。用于边界之外的点的值,当 fill_mode = "constant" 时。
horizontal_flip: 布尔值。随机水平翻转。
vertical_flip: 布尔值。随机垂直翻转。
rescale: 重缩放因子。默认为 None。如果是 None 或 0,不进行缩放,否则将数据乘以所提供的值(在应用任何其他转换之前)。
preprocessing_function: 应用于每个输入的函数。这个函数会在任何其他改变之前运行。这个函数需要一个参数:一张图像(秩为 3 的 Numpy 张量),并且应该输出一个同尺寸的 Numpy 张量。
data_format: 图像数据格式,{"channels_first", "channels_last"} 之一。"channels_last" 模式表示图像输入尺寸应该为 (samples, height, width, channels)"channels_first" 模式表示输入尺寸应该为 (samples, channels, height, width)。默认为 在 Keras 配置文件 ~/.keras/keras.json 中的 image_data_format 值。如果你从未设置它,那它就是 "channels_last"。
validation_split: 浮点数。Float. 保留用于验证的图像的比例(严格在0和1之间)。
dtype: 生成数组使用的数据类型。

关键问题不在于这个图片生成,而是这个图片生成器的方法里面提供了一个函数——flow_from_directory(directory)

这个函数的参数如下:

flow_from_directory(directory): 以文件夹路径为参数,生成经过数据提升/归一化后的数据,在一个无限循环中无限产生batch数据

directory: 目标文件夹路径,对于每一个类,该文件夹都要包含一个子文件夹.子文件夹中任何JPG、PNG、BNP、PPM的图片都会被生成器使用.详情请查看[此脚本](https://link.jianshu.com/?t=https://gist.github.com/fchollet/0830affa1f7f19fd47b06d4cf89ed44d)
target_size: 整数tuple,默认为(256, 256). 图像将被resize成该尺寸
color_mode: 颜色模式,"grayscale","rgb"之一,默认为"rgb".代表这些图片是否会被转换为单通道或三通道的图片.
classes: 可选参数,为子文件夹的列表,['dogs','cats']默认为None. 若未提供,则该类别列表将从directory下的子文件夹名称/结构自动推断。每一个子文件夹都会被认为是一个新的类。(类别的顺序将按照字母表顺序映射到标签值)。通过属性class_indices可获得文件夹名与类的序号的对应字典。
class_mode: "categorical", "binary", "sparse"None之一. 默认为"categorical. 该参数决定了返回的标签数组的形式, "categorical"会返回2D的one-hot编码标签,"binary"返回1D的二值标签."sparse"返回1D的整数标签,如果为None则不返回任何标签, 生成器将仅仅生成batch数据, 这种情况在使用model.predict_generator()和model.evaluate_generator()等函数时会用到.
batch_size: batch数据的大小,默认32
shuffle: 是否打乱数据,默认为True
seed: 可选参数,打乱数据和进行变换时的随机数种子
save_to_dir: None或字符串,该参数能让你将提升后的图片保存起来,用以可视化
save_prefix:字符串,保存提升后图片时使用的前缀, 仅当设置了save_to_dir时生效
save_format:"png""jpeg"之一,指定保存图片的数据格式,默认"jpeg"
flollow_links: 是否访问子文件夹中的软链接

这样,我们导入数据就可以直接使用Keras自带的导入数据的方法了,并且附带了图片的处理。

我们的代码可以这样写:

from keras import backend as K
from keras.preprocessing.image import ImageDataGenerator
‘’‘数据导入’‘’
datagen = ImageDataGenerator(data_format = K.image_data_format())
train_generator = datagen.flow_from_directory(  
    '/home/ubuntu/MNIST/train',
    target_size=(120, 120),
    color_mode='rgb',
    batch_size=256)
test_generator = datagen.flow_from_directory(  
    '/home/ubuntu/MNIST/test',
    target_size=(120, 120),
    color_mode='rgb',
    batch_size=256)
    
‘’‘训练’‘’  
path = os.path.join(MODEL_PATH,'MNIST_googlenet_keras.h5')#训练模型的存储地址
model.fit_generator(train_generator, steps_per_epoch=500, epochs=30,workers=4,verbose=1)#训练
model.save(path)#存储模型
scores = model.evaluate_generator(generator=test_generator,workers=4,use_multiprocessing=True,verbose=0)#验证
print('%s: %.2f' % (model.metrics_names[0], scores[0])) # Loss
print('%s: %.2f%%' % (model.metrics_names[1], scores[1] * 100)) # metrics1
print('%s: %.2f%%' % (model.metrics_names[2], scores[2] * 100)) # metrics2

这样我们就导入了数据了。自此Keras的简单使用已经不成问题。

最后,我们需要注意,最后的导入数据的时候,会自动搜索里面的文件夹,但是是按字典序排序的。这很自然。例如你的文件夹是分类问题,文件夹都是猫、狗、鼠这样的汉字,它当然得按字典序排序。但是如果是像我们用0、1、2、3…这样的数字,就容易让人崩溃。因此我们需要注意在生成文件夹的时候,前面补0,即000、001、003、…、999。

  • 7
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值