数据准备、载入及加速

System Message: WARNING/2 (/home/work/paddledoc/FluidDoc/doc/fluid/advanced_guide/data_preparing/imperative_mode/dataloader_using.rst, line 3)

Title overline too short.

#################
数据准备、载入及加速
#################

在命令式编程模式(动态图)下,使用PaddlePaddle Fluid准备数据相比声明式编程模式(静态图)较为简洁,总体分为三个步骤:

Step 1: 自定义Reader生成训练/预测数据

System Message: WARNING/2 (/home/work/paddledoc/FluidDoc/doc/fluid/advanced_guide/data_preparing/imperative_mode/dataloader_using.rst, line 10)

Title underline too short.

Step 1: 自定义Reader生成训练/预测数据
###################################

首先,自定义一个生成器类型的Reader数据源,通过yield的方式顺序输出训练/预测数据,其生成的数据类型可以为Numpy Array或Tensor。

根据Reader返回的数据形式的不同,Fluid支持配置三种不同的数据生成器,以满足不同的用户需求,下面举例说明。

以常见的图像类模型输入需求为例,一个Sample的输入通常是 (image, label),即(处理图像,真实标签),对此Fluid支持的三种不同Reader分别为:

  1. Sample(样本)级的Reader:每次生成的数据为 (image, label)

  2. Sample List 级的Reader:每次生成的数据为 [(image, label), (image, label), (image, label), …]

  3. Batch 级的Reader:每次生成的数据为 ([BATCH_SIZE, image], [BATCH_SIZE, label])

如果您的数据是Sample形式的数据,但是想要在外部以Batch为单位组件数据,并对整个Batch的数据进行预处理,我们提供了相关的工具,详细内容请参见: 数据预处理工具 ,此处仅通过简单的例子说明如下:

import paddle

mnist_train = paddle.dataset.mnist.train()
mnist_train_batch_reader = paddle.batch(mnist_train, 128) # 128 为 batch size

在上面例子中,mnist_train生成的数据是Sample为单位的,经过 paddle.batch 处理后,会以一个Batch(包含128个Sample)为单位生成数据。

Step 2. 创建DataLoader并设置自定义Reader

System Message: WARNING/2 (/home/work/paddledoc/FluidDoc/doc/fluid/advanced_guide/data_preparing/imperative_mode/dataloader_using.rst, line 34)

Title underline too short.

Step 2. 创建DataLoader并设置自定义Reader
######################################

在命令式编程模式(动态图)下,我们推荐使用DataLoader进行数据载入,DataLoader默认使用线程进行异步加速,使用也更加简便直观。

  1. 创建DataLoader

命令式编程模式(动态图)下,创建DataLoader对象的方式为:

import paddle.fluid as fluid

data_loader = fluid.io.DataLoader.from_generator(capacity=10)

其中,

  • capacity为DataLoader对象的缓存区大小,单位为batch数量。

该值根据实际需求设定即可,一般对于单卡训练的场景,设置5到20即可满足需求,特别是对于一个Batch数据较大的情况,不宜设置过大,可能会消耗过多内存资源,反而降低效率。 如果模型消耗训练或预测数据较慢的话,缓存队列设置过大也没有收益,但对于模型训练速度非常快,数据载入是训练速度瓶颈的情况,适当扩大队列能够提高训练速度。

对于其他参数,我们推荐使用默认设置,无需再额外配置,具体可参见官方文档 cn_api_fluid_io_DataLoader

  1. 设置DataLoader对象的数据源

上文中讲到,DataLoader支持配置三种不同的自定义Reader,配置这三种Reader的方法分别为: set_sample_generator()set_sample_list_generatorset_batch_generator() 。 这三个方法均接收Python生成器 generator 作为参数,其区别在于:

  • set_sample_generator() 要求 generator 返回的数据格式为(image_1, label_1),其中image1和label_1为单个样本的Numpy Array类型数据。

  • set_sample_list_generator() 要求 generator 返回的数据格式为[(image_1, label_1), (image_2, label_2), …, (image_n, label_n)],其中image_i和label_i均为每个样本的Numpy Array类型数据,n为batch size。

  • set_batch_generator() 要求 generator 返回的数据的数据格式为([BATCH_SIZE, image], [BATCH_SIZE, label]),其中[BATCH_SIZE, image]和[BATCH_SIZE, label]为batch级的Numpy Array或Tensor类型数据。

此处我们构建三个不同的示例生成器,对应上述三个接口:

import numpy as np

BATCH_NUM = 10
BATCH_SIZE = 16
MNIST_IMAGE_SIZE = 784
MNIST_LABLE_SIZE = 1

# 伪数据生成函数,服务于下述三种不同的生成器
def get_random_images_and_labels(image_shape, label_shape):
    image = np.random.random(size=image_shape).astype('float32')
    label = np.random.random(size=label_shape).astype('int64')
    return image, label

# 每次生成一个Sample,使用set_sample_generator配置数据源
def sample_generator_creator():
    def __reader__():
        for _ in range(BATCH_NUM * BATCH_SIZE):
            image, label = get_random_images_and_labels([MNIST_IMAGE_SIZE], [MNIST_LABLE_SIZE])
            yield image, label

    return __reader__

# 每次生成一个Sample List,使用set_sample_list_generator配置数据源
def sample_list_generator_creator():
    def __reader__():
        for _ in range(BATCH_NUM):
            sample_list = []
            for _ in range(BATCH_SIZE):
                image, label = get_random_images_and_labels([MNIST_IMAGE_SIZE], [MNIST_LABLE_SIZE])
                sample_list.append([image, label])

            yield sample_list

    return __reader__

# 每次生成一个Batch,使用set_batch_generator配置数据源
def batch_generator_creator():
    def __reader__():
        for _ in range(BATCH_NUM):
            batch_image, batch_label = get_random_images_and_labels([BATCH_SIZE, MNIST_LABLE_SIZE], [BATCH_SIZE, MNIST_LABLE_SIZE])
            yield batch_image, batch_label

    return __reader__

然后,可以根据需求为DataLoader配置不同的数据源,此处完整的创建DataLoader及相应配置为:

import paddle.fluid as fluid

BATCH_SIZE = 16

place = fluid.CPUPlace() # 或者 fluid.CUDAPlace(0)
fluid.enable_imperative(place)

# 使用sample数据生成器作为DataLoader的数据源
data_loader1 = fluid.io.DataLoader.from_generator(capacity=10)
data_loader1.set_sample_generator(sample_generator_creator(), batch_size=BATCH_SIZE, places=place)

# 使用sample list数据生成器作为DataLoader的数据源
data_loader2 = fluid.io.DataLoader.from_generator(capacity=10)
data_loader2.set_sample_list_generator(sample_list_generator_creator(), places=place)

# 使用batch数据生成器作为DataLoader的数据源
data_loader3 = fluid.io.DataLoader.from_generator(capacity=10)
data_loader3.set_batch_generator(batch_generator_creator(), places=place)

此处有两点值得注意:

  1. DataLoader的使用需要在命令式编程模式(动态图)下,即提前通过 fluid.enable_imperative() 进入命令式编程模式(动态图)。

  2. 命令式编程模式(动态图)下DataLoader配置数据源,需要在 set_XXX_generator 时执行place,一般为当前执行的place。(该点后续可能会优化,在这里默认使用动态图当前place,而无需用户指定)

Step 3. 使用DataLoader进行模型训练和预测

System Message: WARNING/2 (/home/work/paddledoc/FluidDoc/doc/fluid/advanced_guide/data_preparing/imperative_mode/dataloader_using.rst, line 145)

Title underline too short.

Step 3. 使用DataLoader进行模型训练和预测
####################################

下面我们通过一个完整的例子来说明命令式编程模式(动态图)下DataLoader在训练/预测时的使用:

  1. 构建命令式编程模式(动态图)模型

此处我们构建一个简单的命令式编程模式(动态图)网络。

  1. 创建网络执行对象,配置DataLoader,进行训练或预测

import paddle.fluid as fluid

EPOCH_NUM = 4
BATCH_SIZE = 16
MNIST_IMAGE_SIZE = 784
MNIST_LABLE_SIZE = 1

# 1. 构建命令式编程模式(动态图)网络
class MyLayer(fluid.dygraph.Layer):
    def __init__(self):
        super(MyLayer, self).__init__()
        self.linear = fluid.dygraph.nn.Linear(MNIST_LABLE_SIZE, 10)

    def forward(self, inputs, label=None):
        x = self.linear(inputs)
        if label is not None:
            loss = fluid.layers.cross_entropy(x, label)
            avg_loss = fluid.layers.mean(loss)
            return x, avg_loss
        else:
            return x

# 2. 创建网络执行对象,配置DataLoader,进行训练或预测
place = fluid.CPUPlace() # 或者 fluid.CUDAPlace(0)
fluid.enable_imperative(place)

# 创建执行的网络对象
my_layer = MyLayer()

# 添加优化器
adam = fluid.optimizer.AdamOptimizer(
    learning_rate=0.001, parameter_list=my_layer.parameters())

# 配置DataLoader
train_loader = fluid.io.DataLoader.from_generator(capacity=10)
train_loader.set_sample_list_generator(sample_list_generator_creator(), places=place)

# 执行训练/预测
for _ in range(EPOCH_NUM):
    for data in train_loader():
        # 拆解载入数据
        image, label = data

        # 执行前向
        x, avg_loss = my_layer(image, label)

        # 执行反向
        avg_loss.backward()

        # 梯度更新
        adam.minimize(avg_loss)
        mnist.clear_gradients()

异步数据读取加速

System Message: WARNING/2 (/home/work/paddledoc/FluidDoc/doc/fluid/advanced_guide/data_preparing/imperative_mode/dataloader_using.rst, line 211)

Title underline too short.

异步数据读取加速
##############

在命令式编程模式(动态图)下,DataLoader默认使用子线程进行异步数据读取加速,但由于python GIL(全局解释器锁)的限制,在数据载入开销比较大的场景下,仅使用线程进行加速的效果并不能满足训练速度需求,因此我们提供了使用子进程加速的方式,进一步提升数据读取的效率。

配置使用子进程加速,仅需要在DataLoader创建时设置 use_multiprocess=True 即可,此参数默认为False,例如

import paddle.fluid as fluid

data_loader = fluid.io.DataLoader.from_generator(capacity=10, use_multiprocess=True)

其他使用方式均与前文中的示例一致。

关于配置此选项带来的加速效果,此处列出一些测试数据供参考。表中数据为单个Epoch的训练耗时,单位为秒(s),模型名后括号内为模型训练所使用的Batch Size。

模型 (Batch Size)

DataLoader默认模式耗时 (s)

DataLoader+子进程模式耗时 (s)

加速比例

Mnist (64)

10.16

6.44

+57.8%

ResNet (32)

83.56

53.75

+55.5%

Ptb (20)

108.56

108.27

+0.2%

MobileNet V1 (256)

5041.77

3249.51

+55.2%

从表中可以看出,在图像类训练任务这种数据载入开销较大的情况下,配置子进程加速的效果是比较明显的,但在一个Batch数据很小的训练任务中,没有明显提升,在这种情况下,数据载入的开销不会是训练速度的瓶颈,使用默认模式即可。

Note

命令式编程模式(动态图)下DataLoader多进程方式采用共享内存机制实现Tensor的进程间传输,使用时需要保证机器上或Docker共享内存空间足够大,需要大于 DataLoader.capacity * Batch Size * 单个Sample的数据大小。在物理机或者虚拟机上训练一般不会有问题,但在docker中可能出现共享内存不足的情况,因为docker中/dev/shm目录空间不够大(默认64M),若内存空间不足,建议配置较大的共享内存空间,或者仍使用单进程。