Keras生成器和序列的区别

Posted

技术标签:

【中文标题】Keras生成器和序列的区别【英文标题】:Keras difference between generator and sequence 【发布时间】:2019-10-20 23:43:23 【问题描述】:

我正在使用深度 CNN+LSTM 网络对一维信号数据集进行分类。我正在使用由tensorflow 1.12.0 支持的keras 2.2.4。由于我的数据集很大且资源有限,因此我在训练阶段使用生成器将数据加载到内存中。首先,我尝试了这个生成器:

def data_generator(batch_size, preproc, type, x, y):
    num_examples = len(x)
    examples = zip(x, y)
    examples = sorted(examples, key = lambda x: x[0].shape[0])
    end = num_examples - batch_size + 1
    batches = [examples[i:i + batch_size] for i in range(0, end, batch_size)]

    random.shuffle(batches)
    while True:
        for batch in batches:
            x, y = zip(*batch)
            yield preproc.process(x, y)

使用上述方法,我可以一次启动最多 30 个样本的小批量训练。但是,这种方法并不能保证网络在每个 epoch 的每个样本上只训练一次。考虑到 Keras 网站上的这条评论:

Sequence 是一种更安全的多处理方式。这种结构 保证网络只会在每个样本上训练一次 生成器不是这样的纪元。

我尝试了另一种使用以下类加载数据的方法:

class Data_Gen(Sequence):

def __init__(self, batch_size, preproc, type, x_set, y_set):
    self.x, self.y = np.array(x_set), np.array(y_set)
    self.batch_size = batch_size
    self.indices = np.arange(self.x.shape[0])
    np.random.shuffle(self.indices)
    self.type = type
    self.preproc = preproc

def __len__(self):
    # print(self.type + ' - len : ' + str(int(np.ceil(self.x.shape[0] / self.batch_size))))
    return int(np.ceil(self.x.shape[0] / self.batch_size))

def __getitem__(self, idx):
    inds = self.indices[idx * self.batch_size:(idx + 1) * self.batch_size]
    batch_x = self.x[inds]
    batch_y = self.y[inds]
    return self.preproc.process(batch_x, batch_y)

def on_epoch_end(self):
    np.random.shuffle(self.indices)

我可以确认,使用这种方法,网络在每个 epoch 的每个样本上训练一次,但这次当我在 mini-batch 中放入超过 7 个样本时,出现内存不足错误:

OP_REQUIRES 在 random_op.cc 失败:202:资源耗尽:OOM 时 用形状分配张量......

我可以确认我使用相同的模型架构、配置和机器来执行此测试。我想知道为什么这两种加载数据的方式会有所不同??

如有需要,请随时询问更多详细信息。

提前致谢。

已编辑:

这是我用来拟合模型的代码:

reduce_lr = keras.callbacks.ReduceLROnPlateau(
            factor=0.1,
            patience=2,
            min_lr=params["learning_rate"])

        checkpointer = keras.callbacks.ModelCheckpoint(
            filepath=str(get_filename_for_saving(save_dir)),
            save_best_only=False)

        batch_size = params.get("batch_size", 32)

        path = './logs/run-0'.format(datetime.now().strftime("%b %d %Y %H:%M:%S"))
        tensorboard = keras.callbacks.TensorBoard(log_dir=path, histogram_freq=0,
                                                  write_graph=True, write_images=False)
        if index == 0:
            print(model.summary())
            print("Model memory needed for batchsize 0 : 1 Gb".format(batch_size, get_model_memory_usage(batch_size, model)))

        if params.get("generator", False):
            train_gen = load.data_generator(batch_size, preproc, 'Train', *train)
            dev_gen = load.data_generator(batch_size, preproc, 'Dev', *dev)
            valid_metrics = Metrics(dev_gen, len(dev[0]) // batch_size, batch_size)
            model.fit_generator(
                train_gen,
                steps_per_epoch=len(train[0]) / batch_size + 1 if len(train[0]) % batch_size != 0 else len(train[0]) // batch_size,
                epochs=MAX_EPOCHS,
                validation_data=dev_gen,
                validation_steps=len(dev[0]) / batch_size + 1  if len(dev[0]) % batch_size != 0 else len(dev[0]) // batch_size,
                callbacks=[valid_metrics, MyCallback(), checkpointer, reduce_lr, tensorboard])

            # train_gen = load.Data_Gen(batch_size, preproc, 'Train', *train)
            # dev_gen = load.Data_Gen(batch_size, preproc, 'Dev', *dev)
            # model.fit_generator(
        #     train_gen,
        #     epochs=MAX_EPOCHS,
        #     validation_data=dev_gen,
        #     callbacks=[valid_metrics, MyCallback(), checkpointer, reduce_lr, tensorboard])

【问题讨论】:

你试过用CPU吗? keras.io/utils "我们建议在 CPU 设备范围内执行此操作,以便模型的权重托管在 CPU 内存上。否则它们可能最终托管在 GPU 上,这会使权重共享复杂化。" 还没有@Luke。我真的不明白你的提议?您能否进一步澄清您的观点? @Maystro 尝试将模型的权重存储在 CPU 上,在此处查看示例 1 keras.io/utils 我不完全确定我理解这里的问题——你是说当你有一个独立的批次时你可以做 30 个批次,但是使用生成器并打开多处理会导致问题?如果是这样,那可能是失败的,因为 Keras 同时在内存中保存了多个批次。 您是使用 CPU 还是 GPU 进行训练?您使用的是单个 GPU 还是多个 GPU?我想我之前遇到过类似的问题 【参考方案1】:

这些方法大致相同。子类化是正确的 Sequence 当您的数据集不适合内存时。但你不应该 在任何类的方法中运行任何预处理,因为这将 每个 epoch 重新执行一次,浪费大量计算资源。

洗牌可能比洗牌更容易 指数。像这样:

随机导入随机播放

class DataGen(Sequence):
    def __init__(self, batch_size, preproc, type, x_set, y_set):
        self.samples = list(zip(x, y))
        self.batch_size = batch_size
        shuffle(self.samples)
        self.type = type
        self.preproc = preproc

    def __len__(self):
        return int(np.ceil(len(self.samples) / self.batch_size))

    def __getitem__(self, i):
        batch = self.samples[i * self.batch_size:(i + 1) * self.batch_size]
        return self.preproc.process(*zip(batch))

    def on_epoch_end(self):
        shuffle(self.samples)

我认为如果没有,就无法说出为什么内存不足 更多地了解您的数据。我的猜测是你的preproc 函数做错了什么。你可以通过运行来调试它:

for e in DataGen(batch_size, preproc, *train):
    print(e)
for e in DataGen(batch_size, preproc, *dev):
    print(e)

您很可能会耗尽内存。

【讨论】:

以上是关于Keras生成器和序列的区别的主要内容,如果未能解决你的问题,请参考以下文章

AttributeError:“SequenceGenerator”对象没有属性“形状”自定义序列生成器 Keras 2.2.4

keras.utils.Sequence:FileSequence生成文件序列流

如何在 tf.data.Dataset 对象上使用序列/生成器将部分数据放入内存?

Keras深度学习实战(40)——音频生成

GAN(生成对抗网络)之keras实践

在 keras 中拟合生成器和数据增强