如何在Keras中使用数据生成器(data generators)的详细示例

动机

您是否曾经不得不加载一个非常消耗内存的数据集,以至于希望魔术能够无缝地解决这一问题?大型数据集正日益成为我们生活的一部分,因为我们能够利用数量不断增长的数据。

我们必须谨记,在某些情况下,即使是最先进的配置也没有足够的内存空间来像以前那样处理数据。这就是为什么我们需要找到其他有效地完成该任务的方法的原因。在此博客文章中,我们将向您展示如何实时在多个内核上生成数据集并将其立即馈送到您的深度学习模型。

本教程中使用的框架是Python的高级软件包Keras提供的框架,可以在TensorFlow或Theano的GPU安装之上使用。

讲解

以前的情况

在阅读本文之前,您的Keras脚本可能看起来像这样:

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)

本文全部涉及更改一次加载整个数据集的方式。确实,此任务可能会引起问题,因为所有训练样本可能无法同时放入内存中。

为了做到这一点,让我们深入研究如何构建适合此情况的数据生成器的逐步方法。顺便说一句,以下代码是用于您自己的项目的良好框架;您可以复制/粘贴以下代码,并相应地填充空白。

小提示

在开始之前,我们先看一些组织技巧,这些技巧在处理大型数据集时特别有用。

ID使用Python字符串来标识给定的数据集样本。跟踪样本及其标签的一种好方法是采用以下框架:

创建一个字典partition收集索引数据:

  • partition[‘train’]:train ID列表
  • partition[‘validation’]:validation ID列表
    创建一个字典labels,其中每个ID数据集的相关标签由给出labels[ID]

例如,假定我们的训练集包含id-1id-2id-3与相应的标签012,验证集合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}

此外,出于模块化的考虑,我们将在单独的文件中编写Keras代码和自定义类,以便您的文件夹看起来像

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()

我们将有关数据的相关信息作为参数,例如维度大小(例如,长度为32的卷dim=(32,32,32)),通道数,类数,批处理大小,或决定是否要在生成时改组数据。我们还存储重要信息,例如标签以及我们希望在每次通过时生成的ID列表。

在此,该方法on_epoch_end在每个时期的开始和结束时都会触发一次。如果shuffle参数设置为True,则每次通过时我们都会获得新的探索顺序(否则,只需保持线性探索方案即可)。

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)

调整将示例 fed to 分类器的顺序很有帮助,这样,各个时期之间的批处理看起来就不会相似。这样做最终将使我们的模型更强大。

生成过程最核心的另一种方法是完成最关键的工作:生成一批数据。调用负责此任务的私有方法__data_generation,并将目标批处理的ID列表作为参数。

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)

在数据生成期间,此代码从其对应的文件中读取每个示例的NumPy数组ID.npy。由于我们的代码是多核友好的,因此请注意,您可以执行更复杂的操作(例如,从源文件进行计算),而不必担心数据生成成为训练过程中的瓶颈。

另外,请注意,我们使用Keras keras.utils.to_categorical函数将存储的数字标签转换y[0 0 1 0 0 0]适合分类的二进制形式(例如,在6类问题中,第三个标签对应于)。

现在是我们将所有这些组件组合在一起的部分。每个调用都请求一个介于0和批次总数之间的批次索引,其中在__len__方法中指定了后者。

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

通常的做法是将此值设置为
⌊ #  samples batch size ⌋ \biggl\lfloor\frac{\#\textrm{ samples}}{\textrm{batch size}}\biggr\rfloor batch size# samples
这样该模型每个时期最多可以看到一次训练样本。

现在,当调用与给定索引相对应的批处理时,生成器将执行__getitem__生成该方法的方法。

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

下面显示了与我们在本节中描述的步骤相对应的完整代码。

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)

Keras脚本

现在,我们必须相应地修改Keras脚本,以便它接受我们刚刚创建的生成器。

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)

正如你所看到的,我们从所谓modelfit_generator方法,而不是fit,我们刚刚给我们的训练发生器的参数之一。Keras负责其余的工作!

请注意,我们的实现允许使用的multiprocessing参数fit_generator,其中指定的线程数是workers并行生成批处理的线程数。足够多的工作人员可以确保有效地管理CPU计算,瓶颈确实是神经网络在GPU上进行的向前和向后操作(而不是数据生成)。

可运行实例

import numpy as np
import keras
from keras.layers import Dense


class DataGenerator(keras.utils.Sequence):
    'Generates data for Keras'

    def __init__(self, train_data, test_data, batch_size=32, n_channels=1, shuffle=True):
        'Initialization'
        self.train_data = train_data
        self.test_data = test_data
        self.batch_size = batch_size
        self.n_channels = n_channels
        self.shuffle = shuffle
        self.on_epoch_end()

    def __len__(self):
        'Denotes the number of batches per epoch'
        return int(np.floor(len(self.train_data) / 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]

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

        X = self.train_data[indexes]
        y = self.test_data[indexes]

        return X, y

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


def gen_model():

    model = keras.models.Sequential()

    model.add(Dense(units=1, use_bias=False, input_shape=(1,)))  # 仅有的1个权重在这里

    return model


if __name__ == '__main__':

    # 数据比较简单,用 CPU 即可
    import os
    os.environ["CUDA_VISIBLE_DEVICES"] = "-1"

    # Parameters
    params = {'batch_size': 10,
              'n_channels': 1,
              'shuffle': False}

    # Datasets
    # partition =  # IDs
    # labels =  # Labels
    x = {}
    x['train'] = np.arange(100, dtype='int32')
    x['validation'] = np.arange(100, 120, dtype='int32')
    y = {}
    y['train'] = -x['train']
    y['test'] = -x['train']


    # Generators
    training_generator = DataGenerator(x['train'], y['train'], **params)
    # validation_generator = DataGenerator(x['validation'], y['test'], **params)

    # Design model
    model = gen_model()
    model.compile(loss='mse', optimizer='adam')

    # model.fit(x['train'], y['train'], epochs=1000, batch_size=10, verbose=2)

    # Train model on dataset
    model.fit_generator(generator=training_generator,
                        # validation_data=validation_generator,
                        epochs=2000,
                        verbose=2,
                        workers=6,
                        )

    print(model.layers[0].get_weights())

结论

就是这个!您现在可以使用以下命令运行Keras脚本

python3 keras_script.py

和你会看到,在训练阶段期间,数据并行地由CPU生成,然后直接喂入到GPU

你可以找到这个战略上的一个具体的例子应用的完整例子GitHub上,其中的代码数据的生成以及对Keras脚本可用。


ref:
姊妹文章:Keras数据生成器以及如何使用它们

A detailed example of how to use data generators with Keras

Artificial Intelligence cheatsheets

Machine Learning cheatsheets

Ddeep Learning cheatsheets

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
在机器学习生成器是一种模型,它可以生成新的数据,这些数据通常与训练数据相似但并不完全相同。生成器通常用于生成图像、音频或文本等数据。 在 Keras ,可以使用 Sequential 或 Functional API 构建生成器模型。下面是一个使用 Sequential API 构建简单生成器示例: ```python from keras.models import Sequential from keras.layers import Dense, Dropout, LeakyReLU, BatchNormalization, Reshape, UpSampling2D generator = Sequential() # 添加一个全连接层,用于将输入的噪声向量映射到一个间向量 generator.add(Dense(256, input_shape=(100,))) # 添加 LeakyReLU 激活函数和批量归一化层 generator.add(LeakyReLU(alpha=0.2)) generator.add(BatchNormalization()) # 添加一个全连接层,将间向量映射到一个更大的向量 generator.add(Dense(512)) generator.add(LeakyReLU(alpha=0.2)) generator.add(BatchNormalization()) # 添加一个全连接层,将更大的向量映射到一个更大的向量 generator.add(Dense(1024)) generator.add(LeakyReLU(alpha=0.2)) generator.add(BatchNormalization()) # 添加一个全连接层,将更大的向量映射到一个输出向量 generator.add(Dense(28*28*1, activation='tanh')) # 将输出向量重塑为图像形状 generator.add(Reshape((28, 28, 1))) # 添加上采样层,将图像大小增加两倍 generator.add(UpSampling2D()) # 添加更多的卷积层和激活函数来生成更复杂的图像 generator.add(Conv2D(64, kernel_size=3, padding="same")) generator.add(LeakyReLU(alpha=0.2)) generator.add(BatchNormalization()) generator.add(Conv2D(32, kernel_size=3, padding="same")) generator.add(LeakyReLU(alpha=0.2)) generator.add(BatchNormalization()) generator.add(Conv2D(1, kernel_size=3, padding="same", activation='tanh')) generator.summary() ``` 这个生成器模型使用了一个输入向量(通常是服从高斯分布的噪声向量),并将其映射到一个输出图像。生成器模型包含了多个全连接层、激活函数、批量归一化层和上采样层,用于将间向量转换为更大的向量和最终的输出图像。 要使用这个生成器,可以通过向其输入随机噪声向量并调用其 predict 方法来生成新的图像。例如: ```python import numpy as np # 生成一批随机噪声向量 noise = np.random.normal(0, 1, (1, 100)) # 使用生成器生成图像 generated_image = generator.predict(noise) ``` 这将生成一个形状为 (1, 28, 28, 1) 的图像张量。可以使用 matplotlib 等库将其显示出来。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

或许,这就是梦想吧!

如果对你有用,欢迎打赏。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值