在本教程中,我们专注于如何将数据提供给训练或推断程序。 MXNet中的大多数训练和推断模块接受数据迭代器,因此简化了此过程,特别是在读取大型数据集时。 这里我们讨论 API 规则和几个提供的迭代器。
完成本教程,我们需要:
$ pip install opencv-python requests matplotlib jupyter
$ git clone https://github.com/dmlc/mxnet ~/mxnet
$ export MXNET_HOME='~/mxnet'
MXNet中的数据迭代器类似于Python迭代器对象。 在Python中,函数iter 允许通过在诸如Python列表之类的可迭代对象上调用next() 来顺序获取各项参数。 迭代器提供了一个抽象接口,用于遍历各种类型的可迭代集合,而不需要公开有关底层数据源的详细信息。
在MXNet中,数据迭代器将在每次调用next 时作为DataBatch 返回一个批次的数据。一个 DataBatch 通常包含n个训练样本及其标签。这里n是迭代器的batch_size。在数据流结束时,如果没有更多数据可读,迭代器会引发像Python iter 这样的StopIteration 异常。 DataBatch的结构定义见官网API。
可以通过DataBatch 中的provide_data 和provide_label 属性将每个训练样本及其标签上的名称,形状,类型和布局等信息作为DataDesc*数据描述符对象。
MXNet中的所有IO都通过mx.io.DataIter 及其子类来处理。 在本教程中,我们将讨论MXNet提供的一些常用的迭代器。
在深入细节之前,我们先通过导入一些所需的软件包来设置环境:
import mxnet as mx
%matplotlib inline
import os
import subprocess
import numpy as np
import matplotlib.pyplot as plt
import tarfile
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
当数据存储在内存中,由NDArray 或numpy ndarray 支持时,我们可以使用NDArrayIter 读取数据:
import numpy as np
data = np.random.rand(100,3)
label = np.random.randint(0, 10, (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])
MXNet提供 CSVIter 从CSV文件中读取数据,用法如下:
#lets save `data` into a csv file first and try reading it back
np.savetxt('data.csv', data, delimiter=',')
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])
当内置的迭代器不符合应用需求时,可以创建自己的自定义数据迭代器。
MXNet中的迭代器应该:
当创建一个新的迭代器时,你既可以从头开始定义一个迭代器,也可以使用一个现有的迭代器。例如,在图像字幕应用中,输入样本是图像,而标签是句子。 因此,我们可以通过以下方式创建一个新的迭代器:
以下示例展示如何创建一个简单的迭代器:
class SimpleIter(mx.io.DataIter):
def __init__(self, data_names, data_shapes, data_gen,
label_names, label_shapes, label_gen, num_batches=10):
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
我们可以用上面定义的SimpleIter 来训练一个简单的MLP程序:
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())
这里有四个变量是可学习的参数:全连接层fc1 和fc2 的权重和偏置,输入数据的两个变量:用于训练样本的data 和包含相应的标签和softmax_output的softmax_label。
MXNet的Symbol API中的data 变量称为自由变量。 要执行一个符号,它们需要绑定数据。
我们通过MXNet的module API,使用数据迭代器将样本提供给神经网络。
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)
RecordIO是MXNet用于数据IO的文件格式。 它紧凑地打包数据,以便从Hadoop HDFS和AWS S3等分布式文件系统进行高效的读写。 MXNet提供MXRecordIO 和MXIndexedRecordIO,用于数据的顺序访问和随机访问。
首先,我们来看一下如何使用MXRecordIO 顺序读写的例子。 这些文件以.rec扩展名命名。
record = mx.recordio.MXRecordIO('tmp.rec', 'w')
for i in range(5):
record.write('record_%d'%i)
record.close()
我们可以通过r 选项打开文件来读取数据,如下所示:
record = mx.recordio.MXRecordIO('tmp.rec', 'r')
while True:
item = record.read()
if not item:
break
print (item)
record.close()
MXIndexedRecordIO 支持随机或索引访问数据。 我们将创建一个索引记录文件和一个相应的索引文件,如下所示:
record = mx.recordio.MXIndexedRecordIO('tmp.idx', 'tmp.rec', 'w')
for i in range(5):
record.write_idx(i, '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。
pack 和unpack 用于存储浮点数(或1维浮点数组)标签和二进制数据。 数据与头文件一起打包。
# pack
data = '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))
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))
还可以使用MXNet src/tools 文件夹中提供的im2rec.py 实用脚本将原始图像转换为RecordIO 格式。 下面的Image IO部分将展示如何使用脚本转换为RecordIO格式。
在本节中,我们将学习如何在MXNet中预处理和加载图像数据。
在MXNet中加载图像数据有4种方式。
预处理图像的方式有多种,我们列举其中的几种:
下面,我们演示一些由mx.image 包提供的常用的预处理示例。
下载我们可以使用的示例图像。
fname = mx.test_utils.download(url='http://data.mxnet.io/data/test_images.tar.gz', dirname='data', overwrite=False)
tar = tarfile.open(fname)
tar.extractall(path='./data')
tar.close()
加载原始图像
mx.image.imdecode 可以加载图像。imdecode 提供了与OpenCV 类似的界面。
注意:你仍然需要安装OpenCV 来使用mx.image.imdecode(而不是CV2 Python库)。
img = mx.image.imdecode(open('data/test_images/ILSVRC2012_val_00000001.JPEG', 'rb').read())
plt.imshow(img.asnumpy()); plt.show()
图像转换
# resize to w x h
tmp = mx.image.imresize(img, 100, 70)
plt.imshow(tmp.asnumpy()); plt.show()
# crop a random w x h region from image
tmp, coord = mx.image.random_crop(img, (150, 200))
print(coord)
plt.imshow(tmp.asnumpy()); plt.show()
在了解如何使用两个内置Image迭代器读取数据之前,我们可以得到一个包含101个类的样本Caltech 101数据集,并将其转换为记录io格式。
下载并解压
fname = mx.test_utils.download(url='http://www.vision.caltech.edu/Image_Datasets/Caltech101/101_ObjectCategories.tar.gz', dirname='data', overwrite=False)
tar = tarfile.open(fname)
tar.extractall(path='./data')
tar.close()
我们来看看数据。可以看到,在根文件夹(./data/101_ObjectCategories)下,每个类别都有一个子文件夹(./ data / 101_ObjectCategories / yin_yang)。
现在让我们使用im2rec.py 脚本将它们转换成记录io格式。首先,我们需要列出包含所有图像文件及其类别的列表:
os.system('python %s/tools/im2rec.py --list=1 --recursive=1 --shuffle=1 --test-ratio=0.2 data/caltech data/101_ObjectCategories'%os.environ['MXNET_HOME'])
生成的列表文件(./data/caltech_train.lst)格式为index\t(一个或多个label)\ tpath。在这种情况下,每个图像只有一个标签,但是可以修改列表以添加更多标签进行多标签训练。
然后我们可以使用此列表创建我们的记录io文件:
os.system("python %s/tools/im2rec.py --num-thread=4 --pass-through=1 data/caltech data/101_ObjectCategories"%os.environ['MXNET_HOME'])
记录的io文件现在保存在这里(./data)
ImageRecordIter 可用于加载以io格式保存的图像数据。要使用ImageRecordIter,只需通过加载记录文件就可以创建一个实例:
data_iter = mx.io.ImageRecordIter(
path_imgrec="./data/caltech.rec", # the target record file
data_shape=(3, 227, 227), # output data shape. An 227x227 region will be cropped from the original image.
batch_size=4, # number of samples per batch
resize=256 # resize the shorter edge to 256 before cropping
# ... you can add more augumentation options as defined in ImageRecordIter.
)
data_iter.reset()
batch = data_iter.next()
data = batch.data[0]
for i in range(4):
plt.subplot(1,4,i+1)
plt.imshow(data[i].asnumpy().astype(np.uint8).transpose((1,2,0)))
plt.show()
ImageIter 是一个灵活的界面,支持以RecordIO和Raw格式加载图像。
data_iter = mx.image.ImageIter(batch_size=4, data_shape=(3, 227, 227),
path_imgrec="./data/caltech.rec",
path_imgidx="./data/caltech.idx" )
data_iter.reset()
batch = data_iter.next()
data = batch.data[0]
for i in range(4):
plt.subplot(1,4,i+1)
plt.imshow(data[i].asnumpy().astype(np.uint8).transpose((1,2,0)))
plt.show()