Keras 中使用 generator 解决训练中内存泄漏的问题

问题

Keras 初心者(我)不熟悉 GPU 训练中的内存控制方法,近日在训练 VGG16 模型的时候总是出现 ResourceExhaustedError 的错误。一开始百思不得其解,因为服务器上的 GPU 的显存大概有 12 GB,正常应该是够用的。

如果看报错信息,很可能会误以为是卷积层参数问题:

tensorflow.python.framework.errors_impl.ResourceExhaustedError: OOM when allocating tensor with shape[1024,32,224,224] and type float on /job:localhost/replica:0/task:0/device:GPU:0 by allocator GPU_0_bfc
     [[Node: conv1_1/convolution = Conv2D[T=DT_FLOAT, _class=["loc:@gradients/conv1_1/convolution_grad/Conv2DBackpropFilter"], data_format="NCHW", dilations=[1, 1, 1, 1], padding="SAME", strides=[1, 1, 1, 1], use_cudnn_on_gpu=true, _device="/job:localhost/replica:0/task:0/device:GPU:0"](gradients/conv1_1/convolution_grad/Conv2DBackpropFilter-0-TransposeNHWCToNCHW-LayoutOptimizer, conv1_1/kernel/read)]]

Current usage from device: /job:localhost/replica:0/task:0/device:GPU:0, allocator: GPU_0_bfc
  588.00MiB from gradients/conv1_1/convolution_grad/Conv2DBackpropFilter-0-TransposeNHWCToNCHW-LayoutOptimizer
  12.25MiB from mul_168
  12.25MiB from add_109
  12.25MiB from mul_164
  576.0KiB from mul_108
  576.0KiB from add_69
  576.0KiB from mul_104
  576.0KiB from mul_120
  576.0KiB from add_77
  576.0KiB from mul_116
  576.0KiB from mul_132
  576.0KiB from add_85
  576.0KiB from mul_128
  Remaining 85 nodes with 6.21MiB

  ······
  ······

But 实际上,由于模型的训练需要用到大批量的训练数据。如果使用 model.fit 进行训练,内存中的训练数据会全部送入 GPU 显存中,占满显存空间。
解决方法就是使用 model.fit_generator ,将训练数据分批送入 GPU 中的训练进程。

注意:对于 12GB 显存,每批数据的大小即 batch_size 最好设置为 128 ~ 256,否则太大了还是会报错。

代码示例

训练代码:

# data_loader 是自己写的一个的类,里面包含 model.fit_generator 函数需要的 generator
dl = data_loader()

history = model.fit_generator(dl.data_loader(input_shape=input_shape),    # 输入训练集生成器
  steps_per_epoch=messageConfig['train_batch_steps'],
  validation_steps=1, verbose=1, epochs=50, callbacks=[checkpoint],
  validation_data=dl.data_loader(input_shape=input_shape, option='val'))# 输入验证集生成器

Generator:

注意,生成器的输出格式必须为 (input, output) 或者 ({'input_1': input_array_1, 'input_2': input_arrary_2}, {'output': output_array})


class data_loader:
    ......

    def data_loader(self, input_shape=None, option='train', data_format='cannels_last', *args):

        if input_shape:
            self.input_shape = input_shape

        H, W, C = self.input_shape[0], self.input_shape[1], self.input_shape[2]
        erste, zweite, dritte = H, W, C
        if data_format == 'channels_first':
            erste, zweite, dritte = C, H, W

        if option == 'train':
            if args:
                self.batch_size, self.val_size = args[0], args[1]
            return self.generate_train_batch_index(erste, zweite, dritte)

        elif option == 'test':

            return self.generate_val_batch_index(erste, zweite, dritte)

    def generate_train_batch_index(self, erste, zweite, dritte):
        while True:
            for i in range(self.train_batch_num):
                # 返回一个 batch_size 大小的 (train_batch_image, train_batch_labels)
                data = self.get_next_train_batch(i, erste, zweite, dritte)    
                yield data

经过这样一番折腾,终于可以继续和模型愉快地玩耍了。

参考

Tensorflow Deep MNIST: Resource exhausted: OOM when allocating tensor with shape[10000,32,28,28]

A concrete example for using data generator for large datasets such as ImageNet #1627