转换数据集为MindRecord

Ascend GPU CPU 数据准备

在线运行下载Notebook下载样例代码查看源文件

概述

用户可以将非标准的数据集和常用的数据集转换为MindSpore数据格式,即MindRecord,从而方便地加载到MindSpore中进行训练。同时,MindSpore在部分场景做了性能优化,使用MindRecord数据格式可以获得更好的性能体验。

MindSpore数据格式具备的特征如下:

  1. 实现多变的用户数据统一存储、访问,训练数据读取更加简便。

  2. 数据聚合存储,高效读取,且方便管理、移动。

  3. 高效的数据编解码操作,对用户透明、无感知。

  4. 可以灵活控制分区的大小,实现分布式训练。

MindSpore数据格式的目标是归一化用户的数据集,并进一步通过MindDataset(详细使用方法参考API)实现数据的读取,并用于训练过程。

data-conversion-concept

基本概念

一个MindRecord文件由数据文件和索引文件组成,且数据文件及索引文件暂不支持重命名操作:

  • 数据文件

    包含文件头、标量数据页、块数据页,用于存储用户归一化后的训练数据,且单个MindRecord文件建议小于20G,用户可将大数据集进行分片存储为多个MindRecord文件。

  • 索引文件

    包含基于标量数据(如图像Label、图像文件名等)生成的索引信息,用于方便的检索、统计数据集信息。

mindrecord

数据文件主要由以下几个关键部分组成:

  • 文件头

    文件头主要用来存储文件头大小、标量数据页大小、块数据页大小、Schema信息、索引字段、统计信息、文件分区信息、标量数据与块数据对应关系等,是MindRecord文件的元信息。

  • 标量数据页

    标量数据页主要用来存储整型、字符串、浮点型数据,如图像的Label、图像的文件名、图像的长宽等信息,即适合用标量来存储的信息会保存在这里。

  • 块数据页

    块数据页主要用来存储二进制串、Numpy数组等数据,如二进制图像文件本身、文本转换成的字典等。

将数据集转换为MindRecord

下面本教程将简单演示如何将图片数据及其标注转换为MindRecord格式。更多MindSpore数据格式转换说明,可参见编程指南中MindSpore数据格式转换章节。

示例一:展示如何将数据按照定义的数据集结构转换为MindRecord数据文件。

  1. 导入文件写入工具类FileWriter

[1]:
from mindspore.mindrecord import FileWriter
  1. 定义数据集结构文件Schema。

[2]:
cv_schema_json = {"file_name": {"type": "string"}, "label": {"type": "int32"}, "data": {"type": "bytes"}}

Schema文件主要包含字段名name、字段数据类型type和字段各维度维数shape

  • 字段名:字段的引用名称,可以包含字母、数字和下划线。

  • 字段数据类型:包含int32、int64、float32、float64、string、bytes。

  • 字段维数:一维数组用[-1]表示,更高维度可表示为[m, n, …],其中m、n为各维度维数。

如果字段有属性shape,则用户传入write_raw_data接口的数据必须为numpy.ndarray类型,对应数据类型必须为int32、int64、float32、float64。

  1. 按照用户定义的Schema格式,准备需要写入的数据列表,此处传入的是图片数据的二进制流。

[3]:
data = [{"file_name": "1.jpg", "label": 0, "data": b"\x10c\xb3w\xa8\xee$o&<q\x8c\x8e(\xa2\x90\x90\x96\xbc\xb1\x1e\xd4QER\x13?\xff\xd9"},
        {"file_name": "2.jpg", "label": 56, "data": b"\xe6\xda\xd1\xae\x07\xb8>\xd4\x00\xf8\x129\x15\xd9\xf2q\xc0\xa2\x91YFUO\x1dsE1\x1ep"},
        {"file_name": "3.jpg", "label": 99, "data": b"\xaf\xafU<\xb8|6\xbd}\xc1\x99[\xeaj+\x8f\x84\xd3\xcc\xa0,i\xbb\xb9-\xcdz\xecp{T\xb1\xdb"}]
  1. 添加索引字段可以加速数据读取,该步骤非必选。

[4]:
indexes = ["file_name", "label"]
  1. 创建FileWriter对象,传入文件名及分片数量,然后添加Schema文件及索引,调用write_raw_data接口写入数据,最后调用commit接口生成本地数据文件。

[5]:
writer = FileWriter(file_name="test.mindrecord", shard_num=4)
writer.add_schema(cv_schema_json, "test_schema")
writer.add_index(indexes)
writer.write_raw_data(data)
writer.commit()
[5]:
MSRStatus.SUCCESS

该示例会生成test.mindrecord0test.mindrecord0.dbtest.mindrecord1test.mindrecord1.dbtest.mindrecord2test.mindrecord2.dbtest.mindrecord3test.mindrecord3.db共8个文件,称为MindRecord数据集。test.mindrecord0test.mindrecord0.db称为1个MindRecord文件,其中test.mindrecord0为数据文件,test.mindrecord0.db为索引文件。

接口说明:

  • FileWriter:如果参数shard_num>1,那么在生成MindRecord文件时,会将原始数据集保存至shard_num个MindRecord文件中,每个MindRecord文件会保存相邻MindRecord文件的元数据信息。然后在使用MindDataset接口读取MindRecord数据集时,通过MindDataset(dataset_files="./test.mindrecord0")可以读取全部shard_num个MindRecord文件;通过MindDataset(dataset_files=["./test.mindrecord0"])只会读取test.mindrecord0文件中的数据。

  • write_raw_data:将数据写入到内存之中。

  • commit:将最终内存中的数据写入到磁盘。

  1. 如果需要在现有数据格式文件中增加新数据,可以调用open_for_append接口打开已存在的数据文件,继续调用write_raw_data接口写入新数据,最后调用commit接口生成本地数据文件。

[6]:
writer = FileWriter.open_for_append("test.mindrecord0")
writer.write_raw_data(data)
writer.commit()
[6]:
MSRStatus.SUCCESS

示例二:将jpg格式的图片,按照示例一的方法,将其转换成MindRecord数据集。

下载需要处理的图片数据transform.jpg作为待处理的原始数据。

创建文件夹目录./datasets/convert_dataset_to_mindrecord/data_to_mindrecord/用于存放本次体验中所有的转换数据集。

创建文件夹目录./datasets/convert_dataset_to_mindrecord/images/用于存放下载下来的图片数据。

以下示例代码将数据集下载并解压到指定位置。

[ ]:
import os
import requests
import tarfile
import zipfile

requests.packages.urllib3.disable_warnings()

def download_dataset(url, target_path):
    """download and decompress dataset"""
    if not os.path.exists(target_path):
        os.makedirs(target_path)
    download_file = url.split("/")[-1]
    if not os.path.exists(download_file):
        res = requests.get(url, stream=True, verify=False)
        if download_file.split(".")[-1] not in ["tgz", "zip", "tar", "gz"]:
            download_file = os.path.join(target_path, download_file)
        with open(download_file, "wb") as f:
            for chunk in res.iter_content(chunk_size=512):
                if chunk:
                    f.write(chunk)
    if download_file.endswith("zip"):
        z = zipfile.ZipFile(download_file, "r")
        z.extractall(path=target_path)
        z.close()
    if download_file.endswith(".tar.gz") or download_file.endswith(".tar") or download_file.endswith(".tgz"):
        t = tarfile.open(download_file)
        names = t.getnames()
        for name in names:
            t.extract(name, target_path)
        t.close()
    print("The {} file is downloaded and saved in the path {} after processing".format(os.path.basename(url), target_path))

download_dataset("https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/transform.jpg", "./datasets/convert_dataset_to_mindrecord/images")
if not os.path.exists("./datasets/convert_dataset_to_mindrecord/data_to_mindrecord/"):
    os.makedirs("./datasets/convert_dataset_to_mindrecord/data_to_mindrecord/")

执行以下代码,将下载的transform.jpg转换为MindRecord数据集。

[8]:
# step 1 import class FileWriter
import os
from mindspore.mindrecord import FileWriter

# clean up old run files before in Linux
data_path = './datasets/convert_dataset_to_mindrecord/data_to_mindrecord/'
os.system('rm -f {}test.*'.format(data_path))

# import FileWriter class ready to write data
data_record_path = './datasets/convert_dataset_to_mindrecord/data_to_mindrecord/test.mindrecord'
writer = FileWriter(file_name=data_record_path, shard_num=4)

# define the data type
data_schema = {"file_name": {"type": "string"}, "label": {"type": "int32"}, "data": {"type": "bytes"}}
writer.add_schema(data_schema, "test_schema")

# prepeare the data contents
file_name = "./datasets/convert_dataset_to_mindrecord/images/transform.jpg"
with open(file_name, "rb") as f:
    bytes_data = f.read()
data = [{"file_name": "transform.jpg", "label": 1, "data": bytes_data}]

# add index field
indexes = ["file_name", "label"]
writer.add_index(indexes)

# save data to the files
writer.write_raw_data(data)
writer.commit()
[8]:
MSRStatus.SUCCESS

该示例会生成8个文件,成为MindRecord数据集。test.mindrecord0test.mindrecord0.db称为1个MindRecord文件,其中test.mindrecord0为数据文件,test.mindrecord0.db为索引文件,生成的文件如下所示:

./datasets/convert_dataset_to_mindrecord/data_to_mindrecord/
├── test.mindrecord0
├── test.mindrecord0.db
├── test.mindrecord1
├── test.mindrecord1.db
├── test.mindrecord2
├── test.mindrecord2.db
├── test.mindrecord3
└── test.mindrecord3.db

读取MindRecord数据集

下面将简单演示如何通过MindDataset读取MindRecord数据集。

  1. 导入读取类MindDataset

[10]:
import mindspore.dataset as ds
  1. 首先使用MindDataset读取MindRecord数据集,然后对数据创建了字典迭代器,并通过迭代器读取了一条数据记录。

[11]:
file_name = './datasets/convert_dataset_to_mindrecord/data_to_mindrecord/test.mindrecord0'
# create MindDataset for reading data
define_data_set = ds.MindDataset(dataset_files=file_name)
# create a dictionary iterator and read a data record through the iterator
count = 0
for item in define_data_set.create_dict_iterator(output_numpy=True):
    print("sample: {}".format(item))
    count += 1
print("Got {} samples".format(count))
sample: {'data': array([255, 216, 255, ..., 159, 255, 217], dtype=uint8), 'file_name': array(b'transform.jpg', dtype='|S13'), 'label': array(1, dtype=int32)}
Got 1 samples