格式转换
MindSpore中可以把用于训练网络模型的数据集,转换为MindSpore特定的格式数据(MindSpore Record格式),从而更加方便地保存和加载数据。其目标是归一化用户的数据集,并进一步通过MindDataset
接口实现数据的读取,并用于训练过程。
此外,MindSpore还针对部分数据场景进行了性能优化,使用MindSpore Record数据格式可以减少磁盘IO、网络IO开销,从而获得更好的使用体验。
MindSpore Record数据格式具备的特征如下:
实现数据统一存储、访问,使得训练时数据读取更加简便。
数据聚合存储、高效读取,使得训练时数据方便管理和移动。
高效的数据编解码操作,使得用户可以对数据操作无感知。
可以灵活控制数据切分的分区大小,实现分布式数据处理。
Record文件结构
如下图所示,MindSpore Record文件由数据文件和索引文件组成。
其中数据文件包含文件头、标量数据页、块数据页,用于存储用户归一化后的训练数据,且单个MindSpore Record文件建议小于20G,用户可将大数据集进行分片存储为多个MindSpore Record文件。
而索引文件则包含基于标量数据(如图像Label、图像文件名等)生成的索引信息,用于方便的检索、统计数据集信息。
数据文件中的文件头、标量数据页、块数据页的具体用途如下所示:
文件头:是MindSpore Record文件的元信息,主要用来存储文件头大小、标量数据页大小、块数据页大小、Schema信息、索引字段、统计信息、文件分区信息、标量数据与块数据对应关系等。
标量数据页:主要用来存储整型、字符串、浮点型数据,如图像的Label、图像的文件名、图像的长宽等信息,即适合用标量来存储的信息会保存在这里。
块数据页:主要用来存储二进制串、NumPy数组等数据,如二进制图像文件本身、文本转换成的字典等。
值得注意的是,数据文件和索引文件均暂不支持重命名操作。
转换成Record格式
下面主要介绍如何将CV类数据和NLP类数据转换为MindSpore Record文件格式,并通过MindDataset
接口,实现MindSpore Record文件的读取。
转换CV类数据集
本示例主要以包含100条记录的CV数据集并将其转换为MindSpore Record格式为例子,介绍如何将CV类数据集转换成MindSpore Record文件格式,并使用MindDataset
接口读取。
首先,需要创建100张图片的数据集并对齐进行保存,其样本包含file_name
(字符串)、label
(整型)、 data
(二进制)三个字段,然后使用MindDataset
接口读取该MindSpore Record文件。
生成100张图像,并转换成MindSpore Record文件格式。
[1]:
from PIL import Image
from io import BytesIO
import mindspore.mindrecord as record
# 输出的MindSpore Record文件完整路径
MINDRECORD_FILE = "test.mindrecord"
# 定义包含的字段
cv_schema = {"file_name": {"type": "string"},
"label": {"type": "int32"},
"data": {"type": "bytes"}}
# 声明MindSpore Record文件格式
writer = record.FileWriter(file_name=MINDRECORD_FILE, shard_num=1, overwrite=True)
writer.add_schema(cv_schema, "it is a cv dataset")
writer.add_index(["file_name", "label"])
# 创建数据集
data = []
for i in range(100):
sample = {}
white_io = BytesIO()
Image.new('RGB', ((i+1)*10, (i+1)*10), (255, 255, 255)).save(white_io, 'JPEG')
image_bytes = white_io.getvalue()
sample['file_name'] = str(i+1) + ".jpg"
sample['label'] = i+1
sample['data'] = white_io.getvalue()
data.append(sample)
if i % 10 == 0:
writer.write_raw_data(data)
data = []
if data:
writer.write_raw_data(data)
writer.commit()
[1]:
MSRStatus.SUCCESS
从上面的打印结果MSRStatus.SUCCESS
可以看出,数据集转换成功。在本篇后续的例子中如果数据集转换成功均可看到此打印结果。
通过
MindDataset
接口读取MindSpore Record文件格式。
[2]:
import mindspore.dataset as ds
import mindspore.dataset.vision as vision
# 读取MindSpore Record文件格式
data_set = ds.MindDataset(dataset_files=MINDRECORD_FILE)
decode_op = vision.Decode()
data_set = data_set.map(operations=decode_op, input_columns=["data"], num_parallel_workers=2)
# 样本计数
print("Got {} samples".format(data_set.get_dataset_size()))
Got 100 samples
转换NLP类数据集
本示例首先创建一个包含100条记录的MindSpore Record文件格式,其样本包含八个字段,均为整型数组,然后使用MindDataset
接口读取该MindSpore Record文件。
为了方便展示,此处略去了将文本转换成字典序的预处理过程。
生成100条文本数据,并转换成MindSpore Record文件格式。
[3]:
import numpy as np
import mindspore.mindrecord as record
# 输出的MindSpore Record文件完整路径
MINDRECORD_FILE = "test.mindrecord"
# 定义样本数据包含的字段
nlp_schema = {"source_sos_ids": {"type": "int64", "shape": [-1]},
"source_sos_mask": {"type": "int64", "shape": [-1]},
"source_eos_ids": {"type": "int64", "shape": [-1]},
"source_eos_mask": {"type": "int64", "shape": [-1]},
"target_sos_ids": {"type": "int64", "shape": [-1]},
"target_sos_mask": {"type": "int64", "shape": [-1]},
"target_eos_ids": {"type": "int64", "shape": [-1]},
"target_eos_mask": {"type": "int64", "shape": [-1]}}
# 声明MindSpore Record文件格式
writer = record.FileWriter(file_name=MINDRECORD_FILE, shard_num=1, overwrite=True)
writer.add_schema(nlp_schema, "Preprocessed nlp dataset.")
# 创建虚拟数据集
data = []
for i in range(100):
sample = {"source_sos_ids": np.array([i, i + 1, i + 2, i + 3, i + 4], dtype=np.int64),
"source_sos_mask": np.array([i * 1, i * 2, i * 3, i * 4, i * 5, i * 6, i * 7], dtype=np.int64),
"source_eos_ids": np.array([i + 5, i + 6, i + 7, i + 8, i + 9, i + 10], dtype=np.int64),
"source_eos_mask": np.array([19, 20, 21, 22, 23, 24, 25, 26, 27], dtype=np.int64),
"target_sos_ids": np.array([28, 29, 30, 31, 32], dtype=np.int64),
"target_sos_mask": np.array([33, 34, 35, 36, 37, 38], dtype=np.int64),
"target_eos_ids": np.array([39, 40, 41, 42, 43, 44, 45, 46, 47], dtype=np.int64),
"target_eos_mask": np.array([48, 49, 50, 51], dtype=np.int64)}
data.append(sample)
if i % 10 == 0:
writer.write_raw_data(data)
data = []
if data:
writer.write_raw_data(data)
writer.commit()
[3]:
MSRStatus.SUCCESS
通过
MindDataset
接口读取MindSpore Record格式文件。
[4]:
import mindspore.dataset as ds
# 读取MindSpore Record文件格式
data_set = ds.MindDataset(dataset_files=MINDRECORD_FILE, shuffle=False)
# 样本计数
print("Got {} samples".format(data_set.get_dataset_size()))
# 打印部分数据
count = 0
for item in data_set.create_dict_iterator():
print("source_sos_ids:", item["source_sos_ids"])
count += 1
if count == 10:
break
Got 100 samples
source_sos_ids: [0 1 2 3 4]
source_sos_ids: [1 2 3 4 5]
source_sos_ids: [2 3 4 5 6]
source_sos_ids: [3 4 5 6 7]
source_sos_ids: [4 5 6 7 8]
source_sos_ids: [5 6 7 8 9]
source_sos_ids: [ 6 7 8 9 10]
source_sos_ids: [ 7 8 9 10 11]
source_sos_ids: [ 8 9 10 11 12]
source_sos_ids: [ 9 10 11 12 13]
Dataset转存MindRecord
MindSpore提供常用数据集加载和转换的接口,能够将常用的数据集转换为MindSpore Record文件格式。
更多数据集转换的详细说明参考API文档。
转存CIFAR-10数据集
通过调用Dataset.save
接口,将CIFAR-10原始数据转换为MindSpore Record。
下载CIFAR-10数据集并解压到指定目录,并使用
Cifar10Dataset
加载。
[5]:
from download import download
from mindspore.dataset import Cifar10Dataset
url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz"
path = download(url, "./", kind="tar.gz", replace=True)
dataset = Cifar10Dataset("./cifar-10-batches-bin/") # 加载数据
Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz (162.2 MB)
file_sizes: 100%|████████████████████████████| 170M/170M [00:26<00:00, 6.35MB/s]
Extracting tar.gz file...
Successfully downloaded / unzipped to ./
调用
Dataset.save
接口,将CIFAR-10数据集转换为MindSpore Record文件格式。
[6]:
dataset.save("cifar10.mindrecord")
通过
MindDataset
接口读取MindSpore Record文件格式。
[7]:
import os
from mindspore.dataset import MindDataset
# 读取MindSpore Record文件格式
data_set = MindDataset(dataset_files="cifar10.mindrecord")
# 样本计数
print("Got {} samples".format(data_set.get_dataset_size()))
if os.path.exists("cifar10.mindrecord") and os.path.exists("cifar10.mindrecord.db"):
os.remove("cifar10.mindrecord")
os.remove("cifar10.mindrecord.db")
Got 60000 samples