大家好,我是刘明,明志科技创始人,华为昇思MindSpore布道师。
技术上主攻前端开发、鸿蒙开发和AI算法研究。
努力为大家带来持续的技术分享,如果你也喜欢我的文章,就点个关注吧
数据是整个深度学习中最重要的一环,因为数据的好坏决定了最终结果的上限,模型的好坏只是去无限逼近这个上限,所以高质量的数据输入,会在整个深度神经网络中起到积极作用,数据在整个数据处理和数据增强的过程像经过pipeline管道的水一样,源源不断地流向训练系统,如图所示:
MindSpore Dataset为用户提供了数据加载以及数据增强的功能,在数据的整个pipeline过程中,其中的每一步骤如果都能够进行合理的运用,那么数据的性能会得到很大的优化和提升。
本次体验将基于CIFAR-10数据集来为大家展示如何在数据加载、数据处理和数据增强的过程中进行性能的优化。
此外,操作系统的存储、架构和计算资源也会一定程度上影响数据处理的性能。
下载数据集
运行以下命令来获取数据集:
下载CIFAR-10二进制格式数据集,并将数据集文件解压到./datasets/目录下,数据加载的时候使用该数据集。
from download import download
import os
import shutil
url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz"
path = download(url, "./datasets", kind="tar.gz", replace=True) # 下载CIFAR-10数据集
test_path = "./datasets/cifar-10-batches-bin/test"
train_path = "./datasets/cifar-10-batches-bin/train"
os.makedirs(test_path, exist_ok=True)
os.makedirs(train_path, exist_ok=True)
if not os.path.exists(os.path.join(test_path, "test_batch.bin")):
shutil.move("./datasets/cifar-10-batches-bin/test_batch.bin", test_path)
[shutil.move("./datasets/cifar-10-batches-bin/"+i, train_path) for i in os.listdir("./datasets/cifar-10-batches-bin/") if os.path.isfile("./datasets/cifar-10-batches-bin/"+i) and not i.endswith(".html") and not os.path.exists(os.path.join(train_path, i))]
解压后的数据集文件的目录结构如下:
./datasets/cifar-10-batches-bin
├── readme.html
├── test
│ └── test_batch.bin
└── train
├── batches.meta.txt
├── data_batch_1.bin
├── data_batch_2.bin
├── data_batch_3.bin
├── data_batch_4.bin
└── data_batch_5.bin
数据加载性能优化
indSpore支持加载计算机视觉、自然语言处理等领域的常用数据集、特定格式的数据集以及用户自定义的数据集。不同数据集加载接口的底层实现方式不同,性能也存在着差异,如下所示:
常用数据集 | 标准格式(MindRecord等) | 用户自定义 | |
---|---|---|---|
底层实现 | C++ | C++ | Python |
性能 | 高 | 高 | 中 |
可参考下图选择适合当前场景的数据集加载接口:
数据加载性能优化建议如下:
-
对于已经提供加载接口的常用数据集,优先使用MindSpore提供的数据集加载接口进行加载,可以获得较好的加载性能,具体内容请参考框架提供的数据集加载接口,如果性能仍无法满足需求,则可采取多线程并发方案,即:将数据集接口的参数num_parallel_workers(默认值:8)增大来取得更好的性能。
-
不支持的数据集格式,推荐先将数据集转换为MindRecord数据格式后再使用MindDataset类进行加载(详细使用方法参考API),具体内容请参考将数据集转换为MindSpore数据格式,如果性能仍无法满足需求,则可采取多线程并发方案,即:将数据集接口的参数num_parallel_workers(默认值:8)增大来取得更好的性能。
-
不支持的数据集格式,算法快速验证场景,优选用户自定义GeneratorDataset类实现(详细使用方法参考API),如果性能仍无法满足需求,则可采取多进程/多线程并发方案,即:
-
增大数据集接口的参数num_parallel_workers(默认值:1)来提升并发度;
-
将数据集接口的参数python_multiprocessing设置为True(默认值)/False来启动多进程模式/多线程模式,多进程模式适用于cpu计算密集型任务,多线程适用于IO密集型任务;
注意:如果配置 python_multiprocessing=True(默认值:True)和 num_parallel_workers>1(默认值:1)表示启动了多进程方式进行数据load加速,此时随着数据集迭代,子进程的内存占用会逐渐增加,主要是因为自定义数据集的子进程以 Copy-On-Write 的方式获取主进程中的成员变量。举例:如果自定义数据集 init 函数中包含大量成员变量数据(例如:在数据集构建时加载了一个非常大的文件名列表)并且使用了多进程方式,那这可能会导致产生OOM的问题(总内存的预估使用量是:(子进程数量 + 1) * 父进程的内存大小)。最简单的解决方法是成员变量用非引用数据类型 (如:Pandas、Numpy或PyArrow对象)替换Python对象(如:list / dict / int / float / string等),或者加载更少的元数据以减小成员变量,或者配置 python_multiprocessing=False 使用多线程方式。
- 如果有Using shared memory queue, but rowsize is larger than allocated memory …日志提示,那么将数据集接口的参数max_rowsize(默认值:6M)按日志提示进行增大来提升进程间数据传递的效率。
基于以上的数据加载性能优化建议,本次体验分别使用框架提供的数据集加载操作Cifar10Dataset类(详细使用方法参考API)、数据转换后使用MindDataset类、使用GeneratorDataset类进行数据加载,代码演示如下:
- 使用数据集加载操作Cifar10Dataset类加载CIFAR-10数据集,这里使用的是CIFAR-10二进制格式的数据集,加载数据时采取多线程优化方案,开启了4个线程并发完成任务,最后对数据创建了字典迭代器,并通过迭代器读取了一条数据记录。
import mindspore.dataset as ds
cifar10_path = "./datasets/cifar-10-batches-bin/train"
# create Cifar10Dataset for reading data
cifar10_dataset = ds.Cifar10Dataset(cifar10_path, num_parallel_workers=4)
# create a dictionary iterator and read a data record through the iterator
print(next(cifar10_dataset.create_dict_iterator()))
- 使用Cifar10ToMR这个类将CIFAR-10数据集转换为MindSpore数据格式,这里使用的是CIFAR-10 python文件格式的数据集,然后使用MindDataset类加载MindSpore数据格式数据集,加载数据采取多线程优化方案,开启了4个线程并发完成任务,最后对数据创建了字典迭代器,并通过迭代器读取了一条数据记录。
from mindspore.mindrecord import Cifar10ToMR
trans_path = "./transform/"
if not os.path.exists(trans_path):
os.mkdir(trans_path)
os.system("rm -f {}cifar10*".format(trans_path))
# download CIFAR-10 python
py_url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-python.tar.gz"
download(py_url, "./datasets", kind="tar.gz", replace=True)
cifar10_path = './datasets/cifar-10-batches-py'
cifar10_mindrecord_path = './transform/cifar10.record'
cifar10_transformer = Cifar10ToMR(cifar10_path, cifar10_mindrecord_path)
# execute transformation from CIFAR-10 to MindRecord
cifar10_transformer.transform(['label'])
# create MindDataset for reading data
cifar10_mind_dataset = ds.MindDataset(dataset_files=cifar10_mindrecord_path, num_parallel_workers=4)
# create a dictionary iterator and read a data record through the iterator
print(next(cifar10_mind_dataset.create_dict_iterator()))
- 使用GeneratorDataset类加载自定义数据集,并且采取多进程优化方案,开启了4个进程并发完成任务,最后对数据创建了字典迭代器,并通过迭代器读取了一条数据记录。
import numpy as np
def generator_func(num):
for i in range(num):
yield (np.array([i]),)
# create GeneratorDataset for reading data
dataset = ds.GeneratorDataset(source=generator_func(5), column_names=["data"], num_parallel_workers=4)
# create a dictionary iterator and read a data record through the iterator
print(next(dataset.create_dict_iterator()))