From 1a92bbad358db9f5adcafaa384b6141e484f1081 Mon Sep 17 00:00:00 2001 From: Zeng Jinle <32832641+sneaxiy@users.noreply.github.com> Date: Wed, 12 Jun 2019 22:52:50 +0800 Subject: [PATCH] polish data reader API for release/1.5 (#891) --- .../howto/prepare_data/index_cn.rst | 67 +++-- .../howto/prepare_data/use_py_reader.rst | 247 ++++++++++-------- 2 files changed, 181 insertions(+), 133 deletions(-) diff --git a/doc/fluid/user_guides/howto/prepare_data/index_cn.rst b/doc/fluid/user_guides/howto/prepare_data/index_cn.rst index f88ede433..ff349ad1c 100644 --- a/doc/fluid/user_guides/howto/prepare_data/index_cn.rst +++ b/doc/fluid/user_guides/howto/prepare_data/index_cn.rst @@ -4,7 +4,7 @@ 准备数据 ######## -使用PaddlePaddle Fluid准备数据分为两个步骤: +使用PaddlePaddle Fluid准备数据分为三个步骤: Step1: 自定义Reader生成训练/预测数据 ################################### @@ -16,32 +16,55 @@ Batch级的Reader每次返回一个Batch的数据,Sample级的Reader每次返 如果您的数据是Sample级的数据,我们提供了一个可以数据预处理和组建batch的工具::code:`Python Reader` 。 -Step2: 将数据送入网络进行训练/预测 +Step2: 在网络配置中定义数据层变量 ################################### +用户需使用 :code:`fluid.layers.data` 在网络中定义数据层变量。定义数据层变量时需指明数据层的名称name、数据类型dtype和维度shape。例如: -Fluid提供两种方式,分别是同步Feed方式或异步py_reader接口方式,具体介绍如下: +.. code-block:: python + + import paddle.fluid as fluid + + image = fluid.layers.data(name='image', dtype='float32', shape=[28, 28]) + label = fluid.layers.data(name='label', dtype='int64', shape=[1]) + + +需要注意的是,此处的shape是单个样本的维度,PaddlePaddle Fluid会在shape第0维位置添加-1,表示batch_size的维度,即此例中image.shape为[-1, 28, 28], +label.shape为[-1, 1]。 + +若用户不希望框架在第0维位置添加-1,则可通过append_batch_size=False参数控制,即: + +.. code-block:: python + + import paddle.fluid as fluid + + image = fluid.layers.data(name='image', dtype='float32', shape=[28, 28], append_batch_size=False) + label = fluid.layers.data(name='label', dtype='int64', shape=[1], append_batch_size=False) + +此时,image.shape为[28, 28],label.shape为[1]。 + +Step3: 将数据送入网络进行训练/预测 +################################### + +Fluid提供两种方式,分别是异步PyReader接口方式或同步Feed方式,具体介绍如下: + +- 异步PyReader接口方式 + +用户需要先使用 :code:`fluid.io.PyReader` 定义PyReader对象,然后通过PyReader对象的decorate方法设置数据源。 +使用PyReader接口时,数据传入与模型训练/预测过程是异步进行的,效率较高,推荐使用。 - 同步Feed方式 -用户需使用 :code:`fluid.layers.data` -配置数据输入层,并在 :code:`fluid.Executor` 或 :code:`fluid.ParallelExecutor` +用户自行构造输入数据,并在 :code:`fluid.Executor` 或 :code:`fluid.ParallelExecutor` 中使用 :code:`executor.run(feed=...)` 传入训练数据。数据准备和模型训练/预测的过程是同步进行的, 效率较低。 -- 异步py_reader接口方式 - -用户需要先使用 :code:`fluid.layers.py_reader` 配置数据输入层,然后使用 -:code:`py_reader` 的 :code:`decorate_paddle_reader` 或 :code:`decorate_tensor_provider` -方法配置数据源,再通过 :code:`fluid.layers.read_file` 读取数据。数据传入与模型训练/预测过程是异步进行的, -效率较高。 - 这两种准备数据方法的比较如下: ======== ================================= ===================================== -对比项 同步Feed方式 异步py_reader接口方式 +对比项 同步Feed方式 异步PyReader接口方式 ======== ================================= ===================================== -API接口 :code:`executor.run(feed=...)` :code:`fluid.layers.py_reader` +API接口 :code:`executor.run(feed=...)` :code:`fluid.io.PyReader` 数据格式 Numpy Array或LoDTensor Numpy Array或LoDTensor 数据增强 Python端使用其他库完成 Python端使用其他库完成 速度 慢 快 @@ -51,7 +74,7 @@ API接口 :code:`executor.run(feed=...)` :code:`fluid.layers.py_reader Reader数据类型对使用方式的影响 ############################### -根据Reader数据类型的不同,上述Step1和Step2的具体操作将有所不同,具体介绍如下: +根据Reader数据类型的不同,上述步骤的具体操作将有所不同,具体介绍如下: 读取Sample级Reader数据 +++++++++++++++++++++ @@ -71,12 +94,12 @@ Step1. 组建数据 Step2. 送入数据 ================================= -若使用同步Feed方式送入数据,请使用DataFeeder接口将Reader数据转换为LoDTensor格式后送入网络,具体请参见 :ref:`cn_api_fluid_DataFeeder` - -若使用异步py_reader接口方式送入数据,请调用 :code:`decorate_paddle_reader` 接口完成,具体请参见: +若使用异步PyReader接口方式送入数据,请调用 :code:`decorate_sample_generator` 或 :code:`decorate_sample_list_generator` 接口完成,具体请参见: - :ref:`user_guides_use_py_reader` +若使用同步Feed方式送入数据,请使用DataFeeder接口将Reader数据转换为LoDTensor格式后送入网络,具体请参见 :ref:`cn_api_fluid_DataFeeder` + 读取Batch级Reader数据 +++++++++++++++++++++++ @@ -88,19 +111,19 @@ Step1. 组建数据 Step2. 送入数据 ================================= -若使用同步Feed方式送入数据,具体请参见: +若使用异步PyReader接口方式送入数据,请调用PyReader的 :code:`decorate_batch_generator` 接口完成,具体方式请参见: .. toctree:: :maxdepth: 1 - feeding_data.rst + use_py_reader.rst -若使用异步py_reader接口方式送入数据,请调用py_reader的 :code:`decorate_tensor_provider` 接口完成,具体方式请参见: +若使用同步Feed方式送入数据,具体请参见: .. toctree:: :maxdepth: 1 - use_py_reader.rst + feeding_data.rst diff --git a/doc/fluid/user_guides/howto/prepare_data/use_py_reader.rst b/doc/fluid/user_guides/howto/prepare_data/use_py_reader.rst index 19e5fb5a0..1c12352a2 100644 --- a/doc/fluid/user_guides/howto/prepare_data/use_py_reader.rst +++ b/doc/fluid/user_guides/howto/prepare_data/use_py_reader.rst @@ -15,33 +15,22 @@ import paddle.fluid as fluid - py_reader = fluid.layers.py_reader(capacity=64, - shapes=[(-1,784), (-1,1)], - dtypes=['float32', 'int64'], - name='py_reader', - use_double_buffer=True) + image = fluid.layers.data(name='image', dtype='float32', shape=[784]) + label = fluid.layers.data(name='label', dtype='int64', shape=[1]) -其中,capacity为PyReader对象的缓存区大小;shapes为batch各参量(如图像分类任务中的image和label)的尺寸;dtypes为batch各参量的数据类型;name为PyReader对象的名称;use_double_buffer默认为True,表示使用 :code:`double_buffer_reader` ,建议开启,可提升数据读取速度。 + ITERABLE = True -需要注意的是:如果您要创建多个不同PyReader对象(例如训练和预测阶段需创建两个不同的PyReader),则需要必须给不同的PyReader对象指定不同的name。这是因为PaddlePaddle采用不同的变量名区分不同的变量,而且 `Program.clone()` (参见 :ref:`cn_api_fluid_Program_clone` )不能实现PyReader对象的复制。 + py_reader = fluid.io.PyReader(feed_list=[image, label], capacity=64, use_double_buffer=True, iterable=ITERABLE) -.. code-block:: python - - import paddle.fluid as fluid - - train_py_reader = fluid.layers.py_reader(capacity=64, - shapes=[(-1,784), (-1,1)], - dtypes=['float32', 'int64'], - name='train', - use_double_buffer=True) +其中, - test_py_reader = fluid.layers.py_reader(capacity=64, - shapes=[(-1,3,224,224), (-1,1)], - dtypes=['float32', 'int64'], - name='test', - use_double_buffer=True) +- feed_list为需要输入的数据层变量列表; +- capacity为PyReader对象的缓存区大小; +- use_double_buffer默认为True,表示使用 :code:`double_buffer_reader` 。建议开启,可提升数据读取速度; +- iterable默认为True,表示该PyReader对象是可For-Range迭代的。当iterable=True时,PyReader与Program解耦,定义PyReader对象不会改变Program;当iterable=False时,PyReader会在Program中插入数据读取相关的op。 -在使用PyReader时,如果需要共享训练阶段和测试阶段的模型参数,您可以通过 :code:`fluid.unique_name.guard()` 的方式来实现。 +需要注意的是:`Program.clone()` (参见 :ref:`cn_api_fluid_Program_clone` )不能实现PyReader对象的复制。如果您要创建多个不同PyReader对象(例如训练和预测阶段需创建两个不同的PyReader),则需重定义两个PyReader对象。 +若需要共享训练阶段和测试阶段的模型参数,您可以通过 :code:`fluid.unique_name.guard()` 的方式来实现。 注:Paddle采用变量名区分不同变量,且变量名是根据 :code:`unique_name` 模块中的计数器自动生成的,每生成一个变量名计数值加1。 :code:`fluid.unique_name.guard()` 的作用是重置 :code:`unique_name` 模块中的计数器,保证多次调用 :code:`fluid.unique_name.guard()` 配置网络时对应变量的变量名相同,从而实现参数共享。 下面是一个使用PyReader配置训练阶段和测试阶段网络的例子: @@ -52,19 +41,11 @@ import paddle.fluid as fluid import paddle.dataset.mnist as mnist - def network(is_train): - # Create py_reader object and give different names - # when is_train = True and is_train = False - reader = fluid.layers.py_reader( - capacity=10, - shapes=((-1, 784), (-1, 1)), - dtypes=('float32', 'int64'), - name="train_reader" if is_train else "test_reader", - use_double_buffer=True) - - # Use read_file() method to read out the data from py_reader - img, label = fluid.layers.read_file(reader) - ... + def network(): + image = fluid.layers.data(name='image', dtype='float32', shape=[784]) + label = fluid.layers.data(name='label', dtype='int64', shape=[1]) + reader = fluid.io.PyReader(feed_list=[image, label], capacity=64) + # Here, we omitted the definition of loss of the model return loss , reader @@ -75,7 +56,7 @@ with fluid.program_guard(train_prog, train_startup): # Use fluid.unique_name.guard() to share parameters with test network with fluid.unique_name.guard(): - train_loss, train_reader = network(True) + train_loss, train_reader = network() adam = fluid.optimizer.Adam(learning_rate=0.01) adam.minimize(train_loss) @@ -85,66 +66,99 @@ with fluid.program_guard(test_prog, test_startup): # Use fluid.unique_name.guard() to share parameters with train network with fluid.unique_name.guard(): - test_loss, test_reader = network(False) + test_loss, test_reader = network() 设置PyReader对象的数据源 ################################ -PyReader对象通过 :code:`decorate_paddle_reader()` 或 :code:`decorate_tensor_provider()` 方法设置其数据源。 :code:`decorate_paddle_reader()` 和 :code:`decorate_tensor_provider()` 均接收Python生成器 :code:`generator` 作为参数, :code:`generator` 内部每次通过yield的方式生成一个batch的数据。 +PyReader对象通过 :code:`decorate_sample_generator()` , :code:`decorate_sample_list_generator` 和 :code:`decorate_batch_generator()` 方法设置其数据源。 +这三个方法均接收Python生成器 :code:`generator` 作为参数,其区别在于: + +- :code:`decorate_sample_generator()` 要求 :code:`generator` 返回的数据格式为[img_1, label_1],其中img_1和label_1为单个样本的Numpy Array类型数据。 -:code:`decorate_paddle_reader()` 和 :code:`decorate_tensor_provider()` 方法的区别在于: +- :code:`decorate_sample_list_generator()` 要求 :code:`generator` 返回的数据格式为[(img_1, label_1), (img_2, label_2), ..., (img_n, label_n)],其中img_i和label_i均为每个样本的Numpy Array类型数据,n为batch size。 -- :code:`decorate_paddle_reader()` 要求 :code:`generator` 返回的数据格式为[(img_1, label_1), (img_2, label_2), ..., (img_n, label_n)],其中img_i和label_i均为每个样本的Numpy Array类型数据,n为batch size。而 :code:`decorate_tensor_provider()` 要求 :code:`generator` 返回的数据的数据格式为[batched_imgs, batched_labels],其中batched_imgs和batched_labels为batch级的Numpy Array或LoDTensor类型数据。 +- :code:`decorate_batch_generator()` 要求 :code:`generator` 返回的数据的数据格式为[batched_imgs, batched_labels],其中batched_imgs和batched_labels为batch级的Numpy Array或LoDTensor类型数据。 -- :code:`decorate_tensor_provider()` 要求 :code:`generator` 返回的数据类型、尺寸必须与配置py_reader时指定的dtypes、shapes参数相同,而 :code:`decorate_paddle_reader()` 不要求数据类型和尺寸的严格一致,其内部会完成数据类型和尺寸的转换。 +当PyReader的iterable=True(默认)时,必须给这三个方法传 :code:`places` 参数, +指定将读取的数据转换为CPU Tensor还是GPU Tensor。当PyReader的iterable=False时,不需传places参数。 -具体方式为: +例如,假设我们有两个reader,其中fake_sample_reader每次返回一个sample的数据,fake_batch_reader每次返回一个batch的数据。 .. code-block:: python - import paddle.batch import paddle.fluid as fluid import numpy as np - BATCH_SIZE = 32 + # sample级reader + def fake_sample_reader(): + for _ in range(100): + sample_image = np.random.random(size=(784, )).astype('float32') + sample_label = np.random.random_integers(size=(1, ), low=0, high=9).astype('int64') + yield sample_image, sample_label + + + # batch级reader + def fake_batch_reader(): + batch_size = 32 + for _ in range(100): + batch_image = np.random.random(size=(batch_size, 784)).astype('float32') + batch_label = np.random.random_integers(size=(batch_size, 1), low=0, high=9).astype('int64') + yield batch_image, batch_label - # Case 1: Use decorate_paddle_reader() method to set the data source of py_reader - # The generator yields Numpy-typed batched data - def fake_random_numpy_reader(): - image = np.random.random(size=(784, )) - label = np.random.random_integers(size=(1, ), low=0, high=9) - yield image, label + image1 = fluid.layers.data(name='image1', dtype='float32', shape=[784]) + label1 = fluid.layers.data(name='label1', dtype='int64', shape=[1]) - py_reader1 = fluid.layers.py_reader( - capacity=10, - shapes=((-1, 784), (-1, 1)), - dtypes=('float32', 'int64'), - name='py_reader1', - use_double_buffer=True) + image2 = fluid.layers.data(name='image2', dtype='float32', shape=[784]) + label2 = fluid.layers.data(name='label2', dtype='int64', shape=[1]) - py_reader1.decorate_paddle_reader(paddle.batch(fake_random_numpy_reader, batch_size=BATCH_SIZE)) + image3 = fluid.layers.data(name='image3', dtype='float32', shape=[784]) + label3 = fluid.layers.data(name='label3', dtype='int64', shape=[1]) +对应的PyReader设置如下: - # Case 2: Use decorate_tensor_provider() method to set the data source of py_reader - # The generator yields Tensor-typed batched data - def fake_random_tensor_provider(): - image = np.random.random(size=(BATCH_SIZE, 784)).astype('float32') - label = np.random.random_integers(size=(BATCH_SIZE, 1), low=0, high=9).astype('int64') - yield image_tensor, label_tensor +.. code-block:: python - py_reader2 = fluid.layers.py_reader( - capacity=10, - shapes=((-1, 784), (-1, 1)), - dtypes=('float32', 'int64'), - name='py_reader2', - use_double_buffer=True) + import paddle + import paddle.fluid as fluid - py_reader2.decorate_tensor_provider(fake_random_tensor_provider) + USE_CUDA = True + USE_DATA_PARALLEL = True + + if ITERABLE: + # 若PyReader可迭代,则必须设置places参数 + if USE_DATA_PARALLEL: + # 若进行多GPU卡训练,则取所有的CUDAPlace + # 若进行多CPU核训练,则取多个CPUPlace,本例中取了8个CPUPlace + places = fluid.cuda_places() if USE_CUDA else fluid.cpu_places(8) + else: + # 若进行单GPU卡训练,则取单个CUDAPlace,本例中0代表0号GPU卡 + # 若进行单CPU核训练,则取单个CPUPlace,本例中1代表1个CPUPlace + places = fluid.cuda_places(0) if USE_CUDA else fluid.cpu_places(1) + else: + # 若PyReader不可迭代,则不需要设置places参数 + places = None + + # 使用sample级的reader作为PyReader的数据源 + py_reader1 = fluid.io.PyReader(feed_list=[image1, label1], capacity=10, iterable=ITERABLE) + py_reader1.decorate_sample_generator(fake_sample_reader, batch_size=32, places=places) + + # 使用sample级的reader + paddle.batch设置PyReader的数据源 + py_reader2 = fluid.io.PyReader(feed_list=[image2, label2], capacity=10, iterable=ITERABLE) + sample_list_reader = paddle.batch(fake_sample_reader, batch_size=32) + sample_list_reader = paddle.reader.shuffle(sample_list_reader) # 还可以进行适当的shuffle + py_reader2.decorate_sample_list_generator(sample_list_reader, places=places) + + # 使用batch级的reader作为PyReader的数据源 + py_reader3 = fluid.io.PyReader(feed_list=[image3, label3], capacity=10, iterable=ITERABLE) + py_reader3.decorate_batch_generator(fake_batch_reader, places=places) 使用PyReader进行模型训练和测试 ################################ -使用PyReader进行模型训练和测试的例程如下: +使用PyReader进行模型训练和测试的例程如下。 + +- 第一步,我们需组建训练网络和预测网络,并定义相应的PyReader对象,设置好PyReader对象的数据源。 .. code-block:: python @@ -153,46 +167,45 @@ PyReader对象通过 :code:`decorate_paddle_reader()` 或 :code:`decorate_tensor import paddle.dataset.mnist as mnist import six - def network(is_train): - # Create py_reader object and give different names - # when is_train = True and is_train = False - reader = fluid.layers.py_reader( - capacity=10, - shapes=((-1, 784), (-1, 1)), - dtypes=('float32', 'int64'), - name="train_reader" if is_train else "test_reader", - use_double_buffer=True) - img, label = fluid.layers.read_file(reader) - ... + ITERABLE = True + + def network(): + # 创建数据层对象 + image = fluid.layers.data(name='image', dtype='float32', shape=[784]) + label = fluid.layers.data(name='label', dtype='int64', shape=[1]) + + # 创建PyReader对象 + reader = fluid.io.PyReader(feed_list=[image, label], capacity=64, iterable=ITERABLE) + # Here, we omitted the definition of loss of the model return loss , reader - # Create main program and startup program for training + # 创建训练的main_program和startup_program train_prog = fluid.Program() train_startup = fluid.Program() - # Define train network + # 定义训练网络 with fluid.program_guard(train_prog, train_startup): - # Use fluid.unique_name.guard() to share parameters with test network + # fluid.unique_name.guard() to share parameters with test network with fluid.unique_name.guard(): - train_loss, train_reader = network(True) + train_loss, train_reader = network() adam = fluid.optimizer.Adam(learning_rate=0.01) adam.minimize(train_loss) - # Create main program and startup program for testing + # 创建预测的main_program和startup_program test_prog = fluid.Program() test_startup = fluid.Program() - # Define test network + # 定义预测网络 with fluid.program_guard(test_prog, test_startup): # Use fluid.unique_name.guard() to share parameters with train network with fluid.unique_name.guard(): - test_loss, test_reader = network(False) + test_loss, test_reader = network() place = fluid.CUDAPlace(0) exe = fluid.Executor(place) - # Run startup program + # 运行startup_program进行初始化 exe.run(train_startup) exe.run(test_startup) @@ -200,33 +213,45 @@ PyReader对象通过 :code:`decorate_paddle_reader()` 或 :code:`decorate_tensor train_prog = fluid.CompiledProgram(train_prog).with_data_parallel(loss_name=train_loss.name) test_prog = fluid.CompiledProgram(test_prog).with_data_parallel(share_vars_from=train_prog) - # Set the data source of py_reader using decorate_paddle_reader() method - train_reader.decorate_paddle_reader( - paddle.reader.shuffle(paddle.batch(mnist.train(), 512), buf_size=8192)) + # 设置PyReader的数据源 + places = fluid.cuda_places() if ITERABLE else None + + train_reader.decorate_sample_list_generator( + paddle.reader.shuffle(paddle.batch(mnist.train(), 512), buf_size=1024), places=places) + + test_reader.decorate_sample_list_generator(paddle.batch(mnist.test(), 512), places=places) + +- 第二步:根据PyReader对象是否iterable,选用不同的方式运行网络。 + +若iterable=True,则PyReader对象是一个Python的生成器,可直接for-range迭代。for-range返回的结果通过exe.run的feed参数传入执行器。 + +.. code-block:: python - test_reader.decorate_paddle_reader(paddle.batch(mnist.test(), 512)) + def run_iterable(program, exe, loss, py_reader): + for data in py_reader(): + loss_value = exe.run(program=program, feed=data, fetch_list=[loss]) + print('loss is {}'.format(loss_value)) for epoch_id in six.moves.range(10): - train_reader.start() - try: - while True: - loss = exe.run(program=train_prog, fetch_list=[train_loss]) - print 'train_loss', loss - except fluid.core.EOFException: - print 'End of epoch', epoch_id - train_reader.reset() + run_iterable(train_prog, exe, loss, train_reader) + run_iterable(test_prog, exe, loss, test_reader) + +若iterable=False,则需在每个epoch开始前,调用 :code:`start()` 方法启动PyReader对象;并在每个epoch结束时,exe.run会抛出 :code:`fluid.core.EOFException` 异常,在捕获异常后调用 :code:`reset()` 方法重置PyReader对象的状态, +以便启动下一轮的epoch。iterable=False时无需给exe.run传入feed参数。具体方式为: - test_reader.start() +.. code-block:: python + + def run_non_iterable(program, exe, loss, py_reader): + py_reader.start() try: while True: - loss = exe.run(program=test_prog, fetch_list=[test_loss]) - print 'test loss', loss + loss_value = exe.run(program=program, fetch_list=[loss]) + print('loss is {}'.format(loss_value)) except fluid.core.EOFException: - print 'End of testing' - test_reader.reset() + print('End of epoch') + py_reader.reset() -具体步骤为: - -1. 在每个epoch开始前,调用 :code:`start()` 方法启动PyReader对象; + for epoch_id in six.moves.range(10): + run_non_iterable(train_prog, exe, loss, train_reader) + run_non_iterable(test_prog, exe, loss, test_reader) -2. 在每个epoch结束时, :code:`read_file` 抛出 :code:`fluid.core.EOFException` 异常,在捕获异常后调用 :code:`reset()` 方法重置PyReader对象的状态,以便启动下一轮的epoch。 -- GitLab