1. 程式人生 > >將資料載入到網路

將資料載入到網路

訓練一個機器學習深度學習模型一般可以簡單概括為以下三個步驟:

  • 準備資料
  • 定義網路結構
  • 訓練

我們可以把整個過程用下面的一個Pipeline圖例來表示。

其中的reader就主要負責把資料按一定的格式feed到深度學習網路的輸入層上。不同的深度學習框架對為放進網路中的資料格式要求不一樣。在MXNet中對於Module的訓練與推理介面要求的資料都是一個data iterator。下面我們會詳細來介紹MXNet中的Data Iterator。

MXNet Data Iterator

MXNet裡的Date Iterators與Python中的iterator object非常類似。在Python中,有一類被稱為iterable的物件,它允許我們使用其中的next

方法來順序的抽取元素,比如list。迭代法器提供了一種遍歷整個容器的簡便方法,而不用關心容器具體的內容。

在MXNet中,data iterators每次返回一個DataBatch。一個DataBatch一般包含n個訓練樣本以及它們對應的標籤。這裡的n一般等於指定的batch size,當整個資料流迭代到尾巴,沒有更多的資料返回時,迭代器將返回一個StopIteration的異常。DataBatch裡包含了一些關於樣本的資訊:名稱,形狀,資料型別以及內在佈局,可以通過provide_dataprovide_label這兩個訪法返回的DataDesc物件來獲取。

所有MXNet關於IO的處理都是由mx.io.DataIter

以及它的子類來完成的。

下面我們通過使用幾個典型的DataIter來說明它的用法。

從記憶體中讀取資料

當資料是在記憶體中,以NDArray或者numpy中的ndarray的形式存在時,我們可以使用NDArrayIter來讀取。

import mxnet as mx
%matplotlib inline
import os
import sys
import subprocess
import numpy as np
import matplotlib.pyplot as plt
import tarfile

import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
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])

從CSV檔案中讀取資料

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提供的一些資料迭代器不滿足我們的需求時,我們可以自己寫一個數據迭代器。那麼一個數據迭代器的物件,一定要包括下面幾個方法:

  • 實現__next()__(python3),該方法返回一個DataBatch物件,並且當沒有剩餘資料時,返回一個StopIteration的異常
  • 實現reset()方法用於重置資料讀取到開始的位置
  • 提供了provide_data屬性,它是一個DataDesc物件的list,儲存了資料的名稱,形狀,資料型別及內在佈局資訊。
  • 提供了provide_label屬性,它是一個DataDesc物件的list,儲存了標籤的名稱,形狀,資料型別及內在佈局資訊。

當我們建立一個新的iterator時,我們可以選擇從頭建立,也可以選擇從一個已經存在的迭代器那擴充套件。比如果我們要做影象描述(image captioning)的應用。那輸入的資料是影象,而對應的Label是一個句子。那我們可以使用ImageRecordIter建立一個image_iter,然後通過NDArrayIter建立一個caption_iter。我們的nxet()方法將返回image_iter.next()caption_iter.next()的一個合併。

下面是我們自定義的一個迭代器。

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 = list(zip(data_names, data_shapes))
        self._provide_label = list(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

Record IO

Record IO是MXNet為了資料IO設計的一種檔案格式。它將資料打包成一種十分便於在分散式儲存系統,如HDFS和AWS S3上進行高效讀取的資料塊。MXNet提供了MXRecordIO用於順序資料儲存的情況,提供了MXIndexedRecordIO用於隨機資料存取的情況。

MXRecordIO

我們先通過一個例子說明MXRecordIO用於順序資料讀寫的用法。

def str_or_bytes(str):
    """
    A utility function for this tutorial that helps us convert string 
    to bytes if we are using python3.

    Parameters
    ----------
    str : string

    Returns
    -------
    string (python2) or bytes (python3)
    """
    if sys.version_info[0] < 3:
        return str
    else:
        return bytes(str, 'utf-8')

我們將幾個連續的字串寫到一個以.rec結尾的檔案中

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

record.close()

我們再從一個.rec檔案中來順序的讀取

record = mx.recordio.MXRecordIO('tmp.rec', 'r')
while True:
    item = record.read()
    if not item:
        break
    print (item)
record.close()

不同與MXRecordIO物件,我們只能不斷的呼叫read()方法來順序的獲取裡面的資料。MXIndexedRecordIO可以隨機的訪問。

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

record.close()
record = mx.recordio.MXIndexedRecordIO('tmp.idx', 'tmp.rec', 'r')
record.read_idx(3)
# 也可以單獨的把index輸出出來
record.keys

打包和解包

我們放到RecordIO裡面包含的是一個個pack,它可以是任何二進位制資料。但是對於大部分深度學習的任務來說,我們往往需要的是資料/標籤這樣的格式。mx.recordio提供了一些介面函式來進行這些操作。

Packing/Unpacking Binary Data

# pack
data = 'data'
label1 = 1.0
header1 = mx.recordio.IRHeader(flag=0, label=label1, id=1, id2=0)
s1 = mx.recordio.pack(header1, str_or_bytes(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, str_or_bytes(data))
# unpack
print(mx.recordio.unpack(s1))
print(mx.recordio.unpack(s2))

Packing/Unpacking Image Data

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))

影象IO

當我們做計算機視訊方面的應用時,要處理的大部分資料都是影象與視訊(也會拆成視訊幀處理)。所以我們這個小節重點介紹在MXNet中是如何處理輸入資料為影象的場景的。

有4種方法可以讓我們選擇來把資料載入到MXNet中

  1. 使用mx.image.imdecode來載入原始的影象資料
  2. 使用mx.img.ImageIter它是用Python來實現的,比較靈活,方便我們修改 ,它可以讀取.rec的檔案或者原始檔案。
  3. 使用mx.io.ImageRecordIter它在MXNet中是放在後端用C++實現的,所以不太便於修改。
  4. 自己基於mx.io.DataIter寫一個自己的迭代器

影象的處理

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()
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 Iterators來載入圖片

我們先下載一個數據集,Caltech 101,它包含了101類物體。我們先將它轉換成RecordIO格式檔案。

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),每一個類別都是一個子檔案平。我們可以使用指令碼im2rec.py來將整個目錄轉化為成ReecordIO檔案。第一步,我們把所有的圖片路徑以及它們的label列到一個文字中。

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'])

上面的命令會生成一個caltech_train.lst的檔案,檔案的內容是index\t(one or more label)\tpath的格式。在這個例子中,只有一個label。然後我們就可以用這個檔案列表資訊雲生成我們的RecordIO檔案了。

os.system("python %s/tools/im2rec.py --num-thread=4 --pass-through=1 data/caltech data/101_ObjectCategories"%os.environ['MXNET_HOME'])

ImageRecordIter可以通過RecordIO格式來載入圖片資料。

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()

除了ImageRecordIter外,我們可以使用ImageIter來讀取一個RecordIO檔案或者直接讀取原始格式的檔案。

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()