MXNet チュートリアル : データをロードする

MXNet チュートリアル : データをロードする (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
日時 : 02/19/2017

* 本ページは、MXNet 本家サイトの Loading Data Tutorial を翻訳した上で適宜、補足説明したものです:
    http://mxnet.io/tutorials/python/data.html

Data Loading – カスタム・データを Module (そして他のインターフェイス) に供給する data iterator をどのように書くか。

 

このチュートリアルはトレーニングと推論プログラムにどのようにデータを供給するかに焦点を当てます。Mixed Programming で示されたようにデータをバインドされた symbol に手動でコピーできます。MXNet のトレーニングと推論 modules の多くは data iterators を受け取り、これはこの手続きを単純化します、特にファイルシステムから巨大データセットを読むときです。ここでは API 規則と幾つかの提供される iterators について議論します。

 

基本的なデータ Iterator

MXNet のデータ iterators は Python の iterator に似ています。Python では、 iterator を返すために (リストのような) iterable オブジェクトとともに組み込み関数 iter を使用します。例えば、$x = iter([1, 2, 3]) $ において list $[1,2,3]$ 上の iterator を得ます。繰り返し $x.next()$ ( $__next__()$ for Python 3) を呼び出すならば、リストから一つ一つ要素を得て $StopIteration$ 例外で終わります。

MXNet のデータ iterator は各 $next$ コールで一束のデータを返します。最初にデータ・バッチとは何のように見えるのかをそして基本的なデータ iterator をどのように書くかを紹介します。

データ・バッチ

データ・バッチは $n$ サンプルと相当するラベルを良く含みます。ここで $n$ はバッチサイズと良く呼ばれます。

次のコードは有効なデータバッチを定義していて多くのトレーニング/推論 module から読まれることができます。

class SimpleBatch(object):
    def __init__(self, data, label, pad=0):
        self.data = data
        self.label = label
        self.pad = pad

各属性が意味するところを説明します:

  • $data$ は NDArray のリストで、各々は長さ $n$ の最初の次元を持ちます。例えば、サンプルがサイズ $224 \times 224$ で RGB チャネルの画像であるならば、配列 shape は $ (n, 3, 224, 244)$ であるべきです。
    MXNet で使用される画像バッチ・フォーマットは 以下である点に注意してください :

\[batch\_size×num\_channel×height×width\]

チャネルは良く RGB order です。

各配列は Symbol の自由変数に後でコピーされます。配列から自由変数へのマッピングは iterator の provide_data 属性で与えられるべきです、これは短く議論されます。

  • $label$ もまた NDArray のリストです。各 NDArray は shape (n,) の 1-次元配列です。分類のためには、各クラスは 0 から始まる整数で表されます。
  • $pad$ は整数でどれだけのサンプルが単に padding に使われるかを示し、結果においては無視されるべきです。 nonzero padding は、データの最後に達してそしてサンプルの総数がバッチサイズで割れない時に良く使われます。

Symbol と Data 変数

iterator へ移る前に、Symbol のどの変数が入力データのためであるかをどのように見つけるかを最初に見ます。MXNet では、operator ( $mx.sym.*$ ) は一つまたはそれ以上の入力変数と出力変数を持ちます; 幾つかの operators は内部ステートのために追加の auxiliary (補助) 変数を持つかもしれません。operator の入力変数のために、この operator を作成する間に他の operator の出力とともにそれを割り当てない時、 するとこの入力変数は自由 (free) です。実行する前に外部データでそれを割り当てる必要があります。

次のコードは単純な multilayer perceptron (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())
['data', 'fc1_weight', 'fc1_bias', 'fc2_weight', 'fc2_bias', 'softmax_label']
['softmax_output']

見て取れるように、変数をそれが atomic (e.g. $sym.Variable$) ならばその operator 名であるいは $opname_varname$ 規則で変数を命名します。$varname$ はこの変数がどのためかを良く意味します:

  • $weight$ : 重みパラメータ
  • $bias$ : バイアス・パラメータ
  • $output$ : 出力
  • $label$: 入力ラベル

上の例では、パラメータのための 4 変数があり、入力データのために 2 つあることが分かります: サンプルのための $data$ と相当するラベルのための $softmax_label$ です。

次のサンプルはレコメンデーション・システムのための行列分解オブジェクト関数を定義しています。それは3つの入力変数、ユーザ ID のための $user$、アイテム ID のための $item$、そして $score$ はアイテムにユーザが与えたレーティングです。

num_users = 1000
num_items = 1000
k = 10 
user = mx.symbol.Variable('user')
item = mx.symbol.Variable('item')
score = mx.symbol.Variable('score')
# user feature lookup
user = mx.symbol.Embedding(data = user, input_dim = num_users, output_dim = k) 
# item feature lookup
item = mx.symbol.Embedding(data = item, input_dim = num_items, output_dim = k)
# predict by the inner product, which is elementwise product and then sum
pred = user * item
pred = mx.symbol.sum_axis(data = pred, axis = 1)
pred = mx.symbol.Flatten(data = pred)
# loss layer
pred = mx.symbol.LinearRegressionOutput(data = pred, label = score)

データ Iterators

さて有効な MXNet データ iterator をどのように作成するかを示す準備ができました。iterator は次のようであるべきです :

  1. python 2 では $next()$ が python 3 では $\_\_next()\_\_$ が呼び出された時にデータバッチを返すか $StopIteration$ 例外をあげます。
  2. 最初から再度読み込むための $reset()$ メソッドを持つ。
  3. $provide_data$ と $provide_label$ 属性を持ちます、前者は $(str, tuple)$ ペアのリストを返し、各ペアは入力データ変数名とその shape を保持します。$provide_label$ についても同様で、入力ラベルについての情報を提供します。

次のコードは単純な iterator を定義し、これは毎回 some random データを返します。

import numpy as np
class SimpleIter:
    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)]
            assert len(data) > 0, "Empty batch data."
            label = [mx.nd.array(g(d[1])) for d,g in zip(self._provide_label, self.label_gen)]
            assert len(label) > 0, "Empty batch label."
            return SimpleBatch(data, label)
        else:
            raise StopIteration

Now we can feed the data iterator into a training problem. Here we used the Module class, more details about this class is discussed in module.ipynb.

# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
import logging
logging.basicConfig(level=logging.INFO)

n = 32
data = 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, num_epoch=5)
INFO:root:Epoch[0] Train-accuracy=0.112500
INFO:root:Epoch[0] Time cost=0.191
INFO:root:Epoch[1] Train-accuracy=0.100000
INFO:root:Epoch[1] Time cost=0.045
INFO:root:Epoch[2] Train-accuracy=0.128125
INFO:root:Epoch[2] Time cost=0.033
INFO:root:Epoch[3] Train-accuracy=0.096875
INFO:root:Epoch[3] Time cost=0.044
INFO:root:Epoch[4] Train-accuracy=0.090625
INFO:root:Epoch[4] Time cost=0.023

一方で Symbol $pred$ のためには、3つの入力、サンプルのための2つとラベルのための一つが必要です。
While for Symbol pred,
we need to provide three inputs, two for examples and one for label.

# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
data = SimpleIter(['user', 'item'],
                  [(n,), (n,)],
                  [lambda s: np.random.randint(0, num_users, s),
                   lambda s: np.random.randint(0, num_items, s)],
                  ['score'], [(n,)],
                  [lambda s: np.random.randint(0, 5, s)])

mod = mx.mod.Module(symbol=pred, data_names=['user', 'item'], label_names=['score'])
mod.fit(data, num_epoch=5)
INFO:root:Epoch[0] Train-accuracy=0.218750
INFO:root:Epoch[0] Time cost=0.046
INFO:root:Epoch[1] Train-accuracy=0.259375
INFO:root:Epoch[1] Time cost=0.039
INFO:root:Epoch[2] Train-accuracy=0.221875
INFO:root:Epoch[2] Time cost=0.029
INFO:root:Epoch[3] Train-accuracy=0.212500
INFO:root:Epoch[3] Time cost=0.021
INFO:root:Epoch[4] Train-accuracy=0.212500
INFO:root:Epoch[4] Time cost=0.027

 

More Iterators

MXNet provides multiple efficient data iterators.

TODO. Explain more here.

 

Implementation

Iterators は C++ でも Python のようなフロントエンド言語でも実装できます。C++ 定義は include/mxnet/io.h にあり、全ての C++ 実装は src/io にあります。これらの実装は dmlc-core に大きく依存しています、これは様々なデータ・フォーマットとファイルシステムからデータを読むことをサポートしています。

 

Further Readings

train = mx.io.NDArrayIter(data=np.zeros((1200, 3, 224, 224), dtype='float32'), 
    label={'annot': np.zeros((1200, 80), dtype='int8'),
    'label': np.zeros((1200, 1), dtype='int8')}, 
    batch_size=10)

 

Next Steps

 

以上