在Keras中使用数据生成器的详细示例

写在前面

https://stanford.edu/~shervine/blog/keras-how-to-generate-data-on-the-fly

Keras作为一个高层的深度学习API,诞生之初就受到了大量使用者的欢迎,如今已被集成到Tensorflow内,更加速了它的发展。我们可以很方便的使用内置的函数加载数据、搭建网络、训练模型。我们都知道,在加载数据时,有两种方式:

  1. 一下子全部加载进来,然后使用.fit()方法,如下代码所示:
import numpy as np
from keras.models import Sequential

# Load entire dataset
X, y = np.load('some_training_set_with_labels.npy')

# Design model
model = Sequential()
[...] # Your architecture
model.compile()

# Train model on your dataset
model.fit(x=X, y=y)

简洁明了,但是我们往往并没有足够的内存去加载全部的数据。

  1. 使用数据生成器的方法,本质就是Python中的生成器,这样的话我们就可以一批(batch)一批的将数据送入网络中迭代训练。Keras中就有专门的函数:ImageDataGenerator,用法可参照:这里 这个函数的原理也很简单,就是通过并行读取指定文件夹里的图像数据,加以应用各种变换,然后送入到训练的网络中。但是这个函数有时无法满足我们的特定功能,我们也不能过于依赖他,下面将逐步探讨在keras中设计数据生成器的方法和步骤。

符号表示

开始前,我们先浏览一些组织技巧,这将对我们处理大的数据集带来很大帮助。
ID表示数据集里的一个样本,以字符串形式表示。我们使用下面的框架可以很好地追溯样本和对应的标签:

  1. 创建一个名为partition的字典,其中partition[‘train’]partition[‘validation’]分别表示训练集合验证集的ID.
  2. 创建一个名为labels的字典,数据集中的每个ID,其对应的label为labels[ID].

举例来说,如果我们训练集包含id-1,id-2id-3的样本并且对应的标签分别为0,12.验证集中id-4的标签为1。这种情况下,Python变量partitionlabels的内容分别为:

>>> partition
{'train': ['id-1', 'id-2', 'id-3'], 'validation': ['id-4']}
>>> labels
{'id-1': 0, 'id-2': 1, 'id-3': 2, 'id-4': 1}

为了代码的模块化,我们将单独编写代码和自定义的类放在两个不同的文件中,文件的组织结构类似于:

folder/
├── my_classes.py
├── keras_script.py
└── data/

其中data/假定为你存储数据集的文件夹。最后,需要注意的是,本教程中的代码是较为通用和极简,因此你可以轻松地将其调整为自己的数据集。

数据生成器

现在,将详细介绍如何设置Python类DataGenerator,该类将实时把数据投喂到Keras模型中。
首先来编写该类的初始化函数,该类将继承keras.utils.Sequence的属性,以便我们可以利用例如多线程等功能。

def __init__(self, list_IDs, labels, batch_size=32, dim=(32,32,32), n_channels=1,
             n_classes=10, shuffle=True):
    'Initialization'
    self.dim = dim
    self.batch_size = batch_size
    self.labels = labels
    self.list_IDs = list_IDs
    self.n_channels = n_channels
    self.n_classes = n_classes
    self.shuffle = shuffle
    self.on_epoch_end()

初始化函数中,我们将与数据相关的信息导入进来,比如数据的尺寸,batch_size,同时也将存储重要的信息例如labelsIDs的列表.我们也可以在on_epoch_end方法中指定在每个epoch后是否将数据打乱(shuffle):

def on_epoch_end(self):
  'Updates indexes after each epoch'
  self.indexes = np.arange(len(self.list_IDs))
  if self.shuffle == True:
      np.random.shuffle(self.indexes)

在这个代码片中,我们根据self.shuffle的属性决定是否每个epoch之后将之后数据迭代的次序(self.indexes)打乱。在每个epoch后打乱数据的次序会使我们训练出来的模型表现更加稳定。
核心的数据生成方法定义为私有方法__data_generation

def __data_generation(self, list_IDs_temp):
  'Generates data containing batch_size samples' # X : (n_samples, *dim, n_channels)
  # 初始化,给迭代的每一批数据预先分配空间
  X = np.empty((self.batch_size, *self.dim, self.n_channels))
  y = np.empty((self.batch_size), dtype=int)

  # Generate data
  for i, ID in enumerate(list_IDs_temp):
      # 根据数据的ID加载数据,这里的数据是.npy格式
      X[i,] = np.load('data/' + ID + '.npy')

      # Store class
      y[i] = self.labels[ID]
  #返回的label是经过one hot编码之后的数据
  return X, keras.utils.to_categorical(y, num_classes=self.n_classes)

我们还需要在建立每一个batch的索引,该索引的长度在0到数据总数/batch_size之间,定义为私有方法:

def __len__(self):
  'Denotes the number of batches per epoch'
  return int(np.floor(len(self.list_IDs) / self.batch_size))

举例来说,我们数据总的个数为1002,batch_size为10,我们在一个epoch中最多迭代100次,多了就会有重复迭代的数据。因此我们使用了np.floor向下取整函数。
当一个batch的索引确定之后,将使用__getitem __方法产生一个batch的数据:

def __getitem__(self, index):
  'Generate one batch of data'
  # 一个Batch的索引
  indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]

  # 根据索引找文件名
  list_IDs_temp = [self.list_IDs[k] for k in indexes]

  # 找到了文件名后调用__data_generation方法读取数据
  X, y = self.__data_generation(list_IDs_temp)
  #这里可以加入一些扩增的方法,例如旋转平移等
  return X, y

全部的代码如下:

import numpy as np
import keras

class DataGenerator(keras.utils.Sequence):
    'Generates data for Keras'
    def __init__(self, list_IDs, labels, batch_size=32, dim=(32,32,32), n_channels=1,
                 n_classes=10, shuffle=True):
        'Initialization'
        self.dim = dim
        self.batch_size = batch_size
        self.labels = labels
        self.list_IDs = list_IDs
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.shuffle = shuffle
        self.on_epoch_end()

    def __len__(self):
        'Denotes the number of batches per epoch'
        return int(np.floor(len(self.list_IDs) / self.batch_size))

    def __getitem__(self, index):
        'Generate one batch of data'
        # Generate indexes of the batch
        indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]

        # Find list of IDs
        list_IDs_temp = [self.list_IDs[k] for k in indexes]

        # Generate data
        X, y = self.__data_generation(list_IDs_temp)

        return X, y

    def on_epoch_end(self):
        'Updates indexes after each epoch'
        self.indexes = np.arange(len(self.list_IDs))
        if self.shuffle == True:
            np.random.shuffle(self.indexes)

    def __data_generation(self, list_IDs_temp):
        'Generates data containing batch_size samples' # X : (n_samples, *dim, n_channels)
        # Initialization
        X = np.empty((self.batch_size, *self.dim, self.n_channels))
        y = np.empty((self.batch_size), dtype=int)

        # Generate data
        for i, ID in enumerate(list_IDs_temp):
            # Store sample
            X[i,] = np.load('data/' + ID + '.npy')

            # Store class
            y[i] = self.labels[ID]

        return X, keras.utils.to_categorical(y, num_classes=self.n_classes)

我们接着创建一个脚本使用我们刚刚写好的生成器:

import numpy as np

from keras.models import Sequential
from my_classes import DataGenerator

# Parameters
params = {'dim': (32,32,32),
          'batch_size': 64,
          'n_classes': 6,
          'n_channels': 1,
          'shuffle': True}

# Datasets
partition = # IDs
labels = # Labels

# Generators
training_generator = DataGenerator(partition['train'], labels, **params)
validation_generator = DataGenerator(partition['validation'], labels, **params)

# Design model
model = Sequential()
[...] # Architecture
model.compile()

# Train model on dataset
model.fit_generator(generator=training_generator,
                    validation_data=validation_generator,
                    use_multiprocessing=True,
                    workers=6)

现在我们就可以使用创建好的数据生成器训练模型了。

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值