数据输入

深度学习是一种数据驱动的技术,为了兼顾易用性与效率, OneFlow 提供了两种“喂”数据给神经网络的方法:

  • 一种方法,可以直接将 NumPy ndarray 对象作为参数传递给作业函数。也就是说 OneFlow 能够直接使用 NumPy 数据作为输入。
  • 另外一种方法是使用 OneFlow 的 DataLoader 及其相关算子,从文件系统加载特定格式的数据集并做预处理。

直接使用 NumPy 数据的方式简单方便,但仅适合小数据量的情况。因为当数据量过大时,可能在准备 NumPy 数据上遭遇效率瓶颈。因此,这种方式比较适合项目的初始阶段,快速验证和改进算法;

OneFlow 的 DataLoader 内部采用了多线程和数据流水线等技术使得数据加载、数据预处理等效率更高。但是,需要为已经支持的格式准备数据集或为 OneFlow 暂时还不支持的格式开发自己的 DataLoader。因此,推荐在成熟的项目中使用。

使用 Numpy 数据作为输入

运行例子

在 Oneflow 中,可以直接使用 NumPy 类型的数据作为作业函数的输入,下面是一个完整的例子:

  1. # feed_numpy.py
  2. import numpy as np
  3. import oneflow as flow
  4. import oneflow.typing as tp
  5. from typing import Tuple
  6. @flow.global_function(type="predict")
  7. def test_job(
  8. images: tp.Numpy.Placeholder((32, 1, 28, 28), dtype=flow.float),
  9. labels: tp.Numpy.Placeholder((32,), dtype=flow.int32),
  10. ) -> Tuple[tp.Numpy, tp.Numpy]:
  11. # do something with images or labels
  12. return (images, labels)
  13. if __name__ == "__main__":
  14. images_in = np.random.uniform(-10, 10, (32, 1, 28, 28)).astype(np.float32)
  15. labels_in = np.random.randint(-10, 10, (32,)).astype(np.int32)
  16. images, labels = test_job(images_in, labels_in)
  17. print(images.shape, labels.shape)

下载完整代码:feed_numpy.py ,然后用 python 执行即可:

  1. python feed_numpy.py

将得到如下结果

  1. (32, 1, 28, 28) (32,)

代码解读

在上面的代码中,我们定义了一个作业函数 test_job(),其输入为 imageslabels ,并且通过注解(注意形参后面是“:”,而不是“=”。)指定了数据的形状与数据类型。

因此,例子中按照作业函数对形状和数据类型的要求随机生成了 NumPy数据:images_inlabels_in

  1. images_in = np.random.uniform(-10, 10, (32, 1, 28, 28)).astype(np.float32)
  2. labels_in = np.random.randint(-10, 10, (32, )).astype(np.int32)

并在调用作业函数时,直接将 NumPy 数据 images_inlabels_in 作为参数传递:

  1. images, labels = test_job(images_in, labels_in)

代码中的 oneflow.typing.Numpy.Placeholder 是 NumPy ndarray 对象的占位符,OneFlow 中还有多种占位符,可以表示更复杂的 NumPy 数据形式。具体可以参考作业函数的定义与调用

使用 DataLoader 及相关算子

oneflow.data 模块下,有用于加载数据集的 DataLoader 算子以及相关的数据预处理算子。DataLoader 一般以 data.xxx_reader 的形式命名,如目前已有的 data.ofrecord_readerdata.coco_reader,分别支持 OneFlow 原生的 OFRecord 格式的文件和 COCO 数据集。

此外,在该模块下,还包含有其它数据预处理算子,用于处理 DataLoader 加载后的数据。如下文代码使用的 data.OFRecordImageDecoderRandomCrop 用于图片随机裁剪,data.OFRecordRawDecoder 用于图片解码。具体使用方法可以查阅 API 文档

运行例子

以下的例子,读取 OFRecord 数据格式文件,处理的是 ImageNet 数据集中的图片。完整代码可以点此下载:of_data_pipeline.py

这个脚本,需要一个 OFRecord 数据集,你可以根据这篇文章自己制作一个。

或者下载我们已经准备好的 part-00000 数据文件,它包含了64张图片。并且,将脚本中的 path/to/ImageNet/ofrecord 替换为 part-00000 文件 所在的目录,然后运行脚本。

以下是使用我们预先准备的数据集运行脚本的例子:

  1. wget https://oneflow-public.oss-cn-beijing.aliyuncs.com/online_document/docs/basics_topics/part-00000
  2. sed -i "s:path/to/ImageNet/ofrecord:./:" of_data_pipeline.py
  3. python3 of_data_pipeline.py

将得到下面的输出:

  1. (64, 3, 224, 224) (64,)

代码解读

使用 OneFlow DataLoader 一般为两个阶段: 数据加载数据预处理

脚本中 flow.data.ofrecord_reader 负责从文件系统中加载数据到内存。

  1. ofrecord = flow.data.ofrecord_reader(
  2. "path/to/ImageNet/ofrecord",
  3. batch_size=batch_size,
  4. data_part_num=1,
  5. part_name_suffix_length=5,
  6. random_shuffle=True,
  7. shuffle_after_epoch=True,
  8. )

需要指定 OFRecord 格式文件所在的目录,和一些其他参数,请参考 data.ofrecord_reader

DataLoader 的返回值,如果是简单的基本数据类型,那么可以直接作为下游的算子的输入,否则,需要继续调用数据预处理算子,进行预处理。

比如,在以上脚本中:

  1. image = flow.data.OFRecordImageDecoderRandomCrop(
  2. ofrecord, "encoded", color_space=color_space
  3. )
  4. label = flow.data.OFRecordRawDecoder(
  5. ofrecord, "class/label", shape=(), dtype=flow.int32
  6. )
  7. rsz = flow.image.Resize(
  8. image, resize_x=224, resize_y=224, color_space=color_space
  9. )
  10. rng = flow.random.CoinFlip(batch_size=batch_size)
  11. normal = flow.image.CropMirrorNormalize(
  12. rsz,
  13. mirror_blob=rng,
  14. color_space=color_space,
  15. mean=[123.68, 116.779, 103.939],
  16. std=[58.393, 57.12, 57.375],
  17. output_dtype=flow.float,
  18. )

OFRecordImageDecoderRandomCrop 负责图片解码并随机做了裁剪,OFRecordRawDecoder 负责从 ofrecord 对象中直接解码出标签, image.Resize 把裁剪后的图片调整成224x224的大小, CropMirrorNormalize 把图片进行了正则化。

支持更多格式的 DataLoader

OneFlow 提供了一些 DataLoader 和预处理的算子,详细请参考 oneflow.data。未来会不断丰富和优化这些算子,用户也可以参考 这篇文章 自定义 DataLoader 满足特定的需求。