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 は次のようであるべきです :
- python 2 では $next()$ が python 3 では $\_\_next()\_\_$ が呼び出された時にデータバッチを返すか $StopIteration$ 例外をあげます。
- 最初から再度読み込むための $reset()$ メソッドを持つ。
- $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
以上