MXNet:数据读写

转载:https://www.cnblogs.com/hellcat/p/9094806.html

1、Gluon数据加载

在gluon接口中,通过Dataset和DataLoader来对数据集进行循环遍历,并返回batch大小的数据,其中Dataset对象用于数据的收集、加载和变换,而DataLoader对象用于返回batch大小的数据。

1.相关模块
mxnet.gluon.data : 数据加载API
mxnet.gluon.data.vision : 专门用于计算机视觉的数据集API和处理工具2. 2.Dataset介绍

Dataset对象用于收集数据、加载和变换数据,其中 数据加载是能够通过给定的下标获取对应的样本,数据变换可以对数据进行各种数据增广操作

原理
所有Dataset类中,都有以下四个方法:

getitem(idx): 数据加载,用于返回第idx个样本
len(): 用于返回数据集的样本的数量
transform(fn, lazy = True): 数据变换,用于返回对每个样本利用fn函数进行数据变换(增广)后的Dataset
transform_first(fn, lazy = True): 数据变换,用于返回对每个样本的特征利用fn函数进行数据变换(增广)后的Dataset,而不对label进行数据增广
用法
ArrayDataset

import mxnet as mx
## 定义
mx.random.seed(42) # 固定随机数种子,以便能够复现
X = mx.random.uniform(shape = (10, 3))
y = mx.random.uniform(shape = (10, 1))
dataset = mx.gluon.data.ArrayDataset(X, y) # ArrayDataset不需要从硬盘上加载数据
## 使用
dataset[5]  # 将返回第6个样本的特征和标签,(特征,标签)

ImageFolderDataset

import mxnet as mx
## 定义
dataset= gdata.vision.ImageFolderDataset("样本集的根路径", flag=1)  #样本路径文件目录名称不支持中文
## 使用
dataset[5]  # 将返回第6个样本的特征和标签,(特征,标签)
data/:
	car/:  t1.jpg, t2.jpg, t3.jpg
	dog/:  d1.jpg, d2.jpg, d3.jpg
	...

ImageRecordDataset

import mxnet as mx
## 定义
file = '/xxx/train.rec'
 #不需要指定idx文件路径,会从路径中自动拼接处idx的路径,例如此处为/xxx/train.idx
dataset= gdata.vision.ImageRecordDataset(file) 
## 使用
dataset[5]  # 将返回第6个样本的特征和标签,(特征,标签)

API中的所有Dataset

mxnet.gluon.data.Dataset: 抽象的数据集类

mxnet.gluon.data.ArrayDataset: 组合多个Dataset的数据集类

mxnet.gluon.data.RecordFileDataset: .rec文件的数据集类

mxnet.gluon.data.vison.MNIST: MNIST数据集的Dataset

mxnet.gluon.data.vison.FashionMNIST: FashionMNIST数据集的Dataset

mxnet.gluon.data.vison.CIFAR10: CIFAR10数据集的Dataset

mxnet.gluon.data.vison.CIFAR100: CIFAR100数据集的Dataset

mxnet.gluon.data.vison.ImageRecordDataset: 含有图片的.rec文件的Dataset

mxnet.gluon.data.vison.ImageFolderDataset: 存储图片在文件夹结构的Dataset

说明: mxnet和numpy的array可以直接作为Dataset
3. DataLoader介绍
加载Dataset,迭代时返回batch大小的样本可以方便的并行地加载数据
使用示例如下:

from multiprocessing import cpu_count
CPU_COUNT = cpu_count()#指定CPU参数,如果是gpu版本的mxnet会发生意外,不要用就好了
data_loader = mx.gluon.data.DataLoader(dataset, batch_size = 5, num_workers = CPU_COUNT)
for X, y in data_loader:
    print X.shape, y.shape

4.transforms模块介绍
在gloun的data接口中,有可以使用的数据增广的模块 (mxnet.gluon.data.vision.tranforms)。在transforms模块中定义了很多数据变换的layer(为Block的子类),变换layer的输入为样本,输出为变换后的样本。
用法示例

from mxnet.gluon import data as gdata
train_ds = gdata.vision.ImageFolderDataset("样本集的根路径", flag=1)
print train_ds[0] #变换之前的数据
## 数据变换定义
transform_train = gdata.vision.transforms.Compose([  # Compose将这些变换按照顺序连接起来
        # 将图片放大成高和宽各为 40 像素的正方形。
        gdata.vision.transforms.Resize(40),
        # 随机对高和宽各为 40 像素的正方形图片裁剪出面积为原图片面积 0.64 到 1 倍之间的小正方形,再放缩为高和宽各为 32 像素的正方形。
        gdata.vision.transforms.RandomResizedCrop(32, scale=(0.64, 1.0),
                                                  ratio=(1.0, 1.0)),
        # 随机左右翻转图片。
        gdata.vision.transforms.RandomFlipLeftRight(),
        # 将图片像素值按比例缩小到 0 和 1 之间,并将数据格式从“高 * 宽 * 通道”改为“通道 * 高 * 宽”。
        gdata.vision.transforms.ToTensor(),
        # 对图片的每个通道做标准化。
        gdata.vision.transforms.Normalize([0.4914, 0.4822, 0.4465],
                                          [0.2023, 0.1994, 0.2010])
    ])

train_ds_transformed = train_ds.transform_first(train_ds )
print  train_ds_transformed[0] #变换之后的数据

重要的变换

Cast: 变换数据类型
ToTensor: 将图像数组由“高 * 宽 * 通道”改为 “通道 * 高 * 宽”
Normalize: 对图片(shape为通道 * 高 * 宽)每个通道上的每个像素按照均值和方差标准化
RandomResizedCrop: 首先按照一定的比例随机裁剪图像,然后再对图像变换高和宽
Resize: 将图像变换高和宽
RandomFlipLeftRight: 随机左右翻转

from mxnet.gluon import data as gdata
import multiprocessing
import os

def get_cifar10(root_dir,  batch_size, num_workers =  1):
    train_ds = gdata.vision.ImageFolderDataset(os.path.join(root_dir, 'train'), flag=1)
    valid_ds = gdata.vision.ImageFolderDataset(os.path.join(root_dir, 'valid'), flag=1)
    train_valid_ds = gdata.vision.ImageFolderDataset(os.path.join(root_dir, 'train_valid'), flag=1)
    test_ds = gdata.vision.ImageFolderDataset(os.path.join(root_dir, 'test'), flag=1)

    transform_train = gdata.vision.transforms.Compose([
        # 将图片放大成高和宽各为 40 像素的正方形。
        gdata.vision.transforms.Resize(40),
        # 随机对高和宽各为 40 像素的正方形图片裁剪出面积为原图片面积 0.64 到 1 倍之间的小正方
        # 形,再放缩为高和宽各为 32 像素的正方形。
        gdata.vision.transforms.RandomResizedCrop(32, scale=(0.64, 1.0),
                                                  ratio=(1.0, 1.0)),
        # 随机左右翻转图片。
        gdata.vision.transforms.RandomFlipLeftRight(),
        # 将图片像素值按比例缩小到 0 和 1 之间,并将数据格式从“高 * 宽 * 通道”改为
        # “通道 * 高 * 宽”。
        gdata.vision.transforms.ToTensor(),
        # 对图片的每个通道做标准化。
        gdata.vision.transforms.Normalize([0.4914, 0.4822, 0.4465],
                                          [0.2023, 0.1994, 0.2010])
    ])

    # 测试时,无需对图像做标准化以外的增强数据处理。
    transform_test = gdata.vision.transforms.Compose([
        gdata.vision.transforms.ToTensor(),
        gdata.vision.transforms.Normalize([0.4914, 0.4822, 0.4465],
                                          [0.2023, 0.1994, 0.2010])
    ])
    train_ds = train_ds.transform_first(transform_train)
    valid_ds = valid_ds.transform_first(transform_test)
    train_valid_ds = train_valid_ds.transform_first(transform_train)
    test_ds = test_ds.transform_first(transform_test)
    train_data = gdata.DataLoader(train_ds, batch_size, shuffle=True, last_batch='keep',num_workers = num_workers)
    valid_data = gdata.DataLoader(valid_ds, batch_size, shuffle=False, last_batch='keep', num_workers = num_workers)
    train_valid_data = gdata.DataLoader(train_valid_ds, batch_size, shuffle=True, last_batch='keep', num_workers=num_workers)
    test_data = gdata.DataLoader(test_ds, batch_size, shuffle=False, last_batch='keep', num_workers=num_workers)
    return train_data, valid_data, train_valid_data, test_data


if __name__ == '__main__':
    batch_size = 256
    root_dir =  '/home/face/common/samples/cifar-10/train_valid_test'

    train_data, valid_data, train_valid_data, test_data = get_cifar10(root_dir, batch_size)

    for batch in train_data:
        data, label = batch
        print data.shape, label

2、MXNet.io:数据读取迭代器

从内存中读取数据
当数据存储在内存中,由NDArray 或numpy ndarray 支持时,我们可以使用NDArrayIter 读取数据

import mxnet as mx
import numpy as np
 
data = np.random.rand(100,3)  # 100个数据每个数据3特征
label = np.random.randint(0, 10, (100,))  # 100个标签
data_iter = mx.io.NDArrayIter(data=data, label=label, batch_size=30)
for batch in data_iter:
    print([batch.data, batch.label, batch.pad],'\n')

从CSV文件中读取数据
MXNet提供 CSVIter 从CSV文件中读取数据,用法如下:

#lets save `data` into a csv file first and try reading it back
np.savetxt('data.csv', data, delimiter=',')
# data_shape对应不上会报错
data_iter = mx.io.CSVIter(data_csv='data.csv', data_shape=(3,), batch_size=30) 
 
for batch in data_iter:
    print([batch.data, batch.pad])

以上两种方法中的pad属性为int,表示有多少条数据是补充的(最后一个batch数据不够时的策略)。

3、MXNet.recordio:二进制rec文件交互

RecordIO是MXNet用于数据IO的文件格式,文件后缀为.rec。

它紧凑地打包数据,以便从Hadoop HDFS和AWS S3等分布式文件系统进行高效的读写。 MXNet提供MXRecordIO 和MXIndexedRecordIO,用于数据的顺序访问和随机访问。

注意,.rec文件写入的必须是整数或者二进制数据。

mx.recordio.MXRecordIO:顺序式rec文件
首先,我们来看一下如何使用MXRecordIO 顺序读写的例子,

record = mx.recordio.MXRecordIO('tmp.rec', 'w')
for i in range(5):
    record.write(b'record_%d'%i)
record.close()

mx.recordio.MXIndexedRecordIO:随机索引式rec文件
MXIndexedRecordIO 支持随机或索引访问数据。 我们将创建一个索引记录文件和一个相应的索引文件,如下所示:

record = mx.recordio.MXIndexedRecordIO('tmp.idx', 'tmp.rec', 'w')
for i in range(5):
    record.write_idx(i, b'record_%d'%i)
record.close()

现在,我们可以使用键值访问各个记录:

record = mx.recordio.MXIndexedRecordIO('tmp.idx', 'tmp.rec', 'r')
record.read_idx(3)

还可以列出文件中的所有键:

record.keys

数据文件打包为规整的二进制结构
.rec文件中的每个记录都可以包含任意的二进制数据,然而,大多数深度学习任务需要以标签/数据格式作为输入。

mx.recordio 包为此类操作提供了一些实用功能,即:pack,unpack,pack_img 和unpack_img。

二进制数据的装包(mx.recordio.pack)与拆包(mx.recordio.unpack)
pack 和unpack 用于存储浮点数(或1维浮点数组)标签和二进制数据。

数据与头文件一起打包。

# pack
data = b'data'
label1 = 1.0
header1 = mx.recordio.IRHeader(flag=0, label=label1, id=1, id2=0)
s1 = mx.recordio.pack(header1, data)
 
label2 = [1.0, 2.0, 3.0]
header2 = mx.recordio.IRHeader(flag=3, label=label2, id=2, id2=0)
s2 = mx.recordio.pack(header2, data)
 
# unpack
print(mx.recordio.unpack(s1))
print(mx.recordio.unpack(s2))

mx.recordio.pack返回的s1、s2为二进制字节流,而mx.recordio.unpack则返回tuple。

s = mx.recordio.unpack(s1)
isinstance(s[0], tuple)

图像数据的装包与拆包
由于图片数据在DL中尤为常用,所以单独给图片数组设计出接口,这个接口可以接收numpy数组,自动将之转化为二进制数据存入文件,解压时逆向操作。
MXNet提供pack_img 和unpack_img 来打包/解压图像数据,pack_img 打包的记录可以由mx.io.ImageRecordIter 加载。

data = np.ones((3,3,1), dtype=np.uint8)
label = 1.0
header = mx.recordio.IRHeader(flag=0, label=label, id=0, id2=0)
s = mx.recordio.pack_img(header, data, quality=100, img_fmt='.jpg')
 
# unpack_img
print(mx.recordio.unpack_img(s))

4、自定义迭代器

当内置的迭代器不符合应用需求时,可以创建自己的自定义数据迭代器。
MXNet中的迭代器应该:
实现Python2中的next() 或者Python3中的__ next() __,返回DataBatch 或者到数据流的末尾时抛出StopIteration 异常。
实现reset() 方法,从头开始读取数据
具有一个provide_data 属性,包括存储了数据的名称,形状,类型和布局信息的DataDesc 对象的列表
具有一个provide_label 属性,包括存储了标签的名称,形状,类型和布局信息的DataDesc 对象的列表
当创建一个新的迭代器时,你既可以从头开始定义一个迭代器,也可以使用一个现有的迭代器。例如,在图像字幕应用中,输入样本是图像,而标签是句子。 因此,我们可以通过以下方式创建一个新的迭代器:
通过使用ImageRecordIter 创建一个image_iter,它提供多线程的预取和增强。
通过使用NDArrayIter 或 rnn 包中提供的bucketing 迭代器创建caption_iter 。
next() 返回image_iter.next() 和caption_iter.next() 的组合结果

class SimpleIter(mx.io.DataIter):
    def __init__(self,
                 # data_shaps:包含batch维的数据,data_gen:函数,接收参数data_shapes
                 data_names, data_shapes, data_gen,
                 # label_shaps:包含batch维的标签,label_gen:函数,接收参数label_shapes
                 label_names, label_shapes, label_gen, 
                 num_batches=10):
        """
        实际上这个class着重修改__init__和next即可,包证next的return是一个batch的数据
        n = 32
        data_iter = SimpleIter(['data'], [(n, 100)],
                               [lambda s: np.random.uniform(-1, 1, s)],    # 从[-1,1)随机抽取 s个样本
                               ['softmax_label'], [(n,)],
                               [lambda s: np.random.randint(0, num_classes, s)])   #从[ 0 ,10)中随机生成s个整数标签
        """
        self._provide_data = zip(data_names, data_shapes)
        self._provide_label = zip(label_names, label_shapes)
        self.num_batches = num_batches
        self.data_gen = data_gen
        self.label_gen = label_gen
        self.cur_batch = 0
 
    def __iter__(self):
        return self
 
    def reset(self):
        self.cur_batch = 0
 
    def __next__(self):
        return self.next()
 
    @property
    def provide_data(self):
        return self._provide_data
 
    @property
    def provide_label(self):
        return self._provide_label
 
    def next(self):
        if self.cur_batch < self.num_batches:
            self.cur_batch += 1
            data = [mx.nd.array(g(d[1]))
                    for d,g in zip(self._provide_data, self.data_gen)]
            label = [mx.nd.array(g(d[1]))
                     for d,g in zip(self._provide_label, self.label_gen)]
            return mx.io.DataBatch(data, label)
        else:
            raise StopIteration

简单定义一个网络,

import mxnet as mx
num_classes = 10
net = mx.sym.Variable('data')
net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=64)
net = mx.sym.Activation(data=net, name='relu1', act_type="relu")
net = mx.sym.FullyConnected(data=net, name='fc2', num_hidden=num_classes)
net = mx.sym.SoftmaxOutput(data=net, name='softmax')
print(net.list_arguments())
print(net.list_outputs())

训练示意,

import logging
logging.basicConfig(level=logging.INFO)
 
n = 32
data_iter = SimpleIter(['data'], [(n, 100)],
                  [lambda s: np.random.uniform(-1, 1, s)],
                  ['softmax_label'], [(n,)],
                  [lambda s: np.random.randint(0, num_classes, s)])
 
mod = mx.mod.Module(symbol=net)
mod.fit(data_iter, num_epoch=5)

你可能感兴趣的:(深度学习)