MXNet チュートリアル : 手書き数字認識 – MNIST

MXNet チュートリアル : 手書き数字認識 – MNIST (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
日時 : 02/21/2017

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

Handwritten Digit Classification – MLP と畳込みネットワークを使用して MNIST データセットから手書き数字を分類する単純なサンプルです。

* サンプルコードの動作確認はしておりますが、適宜、追加改変しています。
* 画像の多くは自作していますが一部 github から fork して引用しています。

 

このチュートリアルは古典的なコンピュータビジョン・アプリケーションを案内します : ニューラルネットワークで手書き数字を識別します。

 

データをロードする

最初に MNIST データセットを取得します、これは手書き数字認識のために一般的に使用されるデータセットです。このデータセットの各画像は 0 と 254 の間のグレースケール値で 28×28 にリサイズされます。次のコードはダウンロードして画像と相当するラベルを numpy にロードします。

import numpy as np
import os
import urllib
import gzip
import struct
def download_data(url, force_download=True): 
    fname = url.split("/")[-1]
    if force_download or not os.path.exists(fname):
        urllib.urlretrieve(url, fname)
    return fname

def read_data(label_url, image_url):
    with gzip.open(download_data(label_url)) as flbl:
        magic, num = struct.unpack(">II", flbl.read(8))
        label = np.fromstring(flbl.read(), dtype=np.int8)
    with gzip.open(download_data(image_url), 'rb') as fimg:
        magic, num, rows, cols = struct.unpack(">IIII", fimg.read(16))
        image = np.fromstring(fimg.read(), dtype=np.uint8).reshape(len(label), rows, cols)
    return (label, image)

path='http://yann.lecun.com/exdb/mnist/'
(train_lbl, train_img) = read_data(
    path+'train-labels-idx1-ubyte.gz', path+'train-images-idx3-ubyte.gz')
(val_lbl, val_img) = read_data(
    path+'t10k-labels-idx1-ubyte.gz', path+'t10k-images-idx3-ubyte.gz')

最初の 10 画像をプロットしてそれらのラベルをプリントとします。

%matplotlib inline
import matplotlib.pyplot as plt
for i in range(10):
    plt.subplot(1,10,i+1)
    plt.imshow(train_img[i], cmap='Greys_r')
    plt.axis('off')
plt.show()
print('label: %s' % (train_lbl[0:10],))

label: [5 0 4 1 9 2 1 3 1 4]

次に MXNet のためのデータ iterator を作成します。データ iterator は (python の) iterator に似たもので、各 $next()$ 呼び出しでデータのバッチを返します。バッチは幾つかの画像を相当するラベルと一緒に含みます。これらの画像は shape $(batch\_size, num\_channels, width, height)$ で 4-D 行列にストアされます。MNIST データセットについては、一つだけのカラー・チャネルがあり、width と height はともに 28 です。それから、トレーニングに使用される画像はしばしばシャッフルします、これはトレーニング・プロセスを加速します。

import mxnet as mx

def to4d(img):
    return img.reshape(img.shape[0], 1, 28, 28).astype(np.float32)/255

batch_size = 100
train_iter = mx.io.NDArrayIter(to4d(train_img), train_lbl, batch_size, shuffle=True)
val_iter = mx.io.NDArrayIter(to4d(val_img), val_lbl, batch_size)

 

多層パーセプトロン

多層パーセプトロンは幾つかの完全結合層を含みます。$n \times m$ 入力行列 $X$ を持つ完全結合層はサイズ $n \times k$ の行列を出力します、ここで $k$ はしばしば隠れサイズと呼ばれます。この層は2つのパラメータ、$m \times k$ 重み行列 $W$ と $m \times 1$ バイアス行列 $b$ を持ちます。それは出力を以下で計算します。

$$Y = W X + b$$

完全結合層の出力はしばしば活性化層に供給され、これは element-wise な演算を実行します。2つの一般的なオプションは sigmoid 関数、あるいは rectifier (or “relu”) 関数で、これは 0 と入力の max を出力します。

最後の完全結合層はしばしばデータセットのクラス数と同じ隠れサイズを持ちます。そして softmax 層を積み、これは入力を確率スコアにマップします。再び入力 $X$ はサイズ $x \times m$ を持ちます :

$$ \left[\frac{\exp(x_{i1})}{\sum_{j=1}^m \exp(x_{ij})},\ldots, \frac{\exp(x_{im})}{\sum_{j=1}^m \exp(x_{ij})}\right] $$

MXNet で多層パーセプトロンを定義することは率直 (= straightforward) で、次のように示されます :

# Create a place holder variable for the input data
data = mx.sym.Variable('data')
# Flatten the data from 4-D shape (batch_size, num_channel, width, height) 
# into 2-D (batch_size, num_channel*width*height)
data = mx.sym.Flatten(data=data)

# The first fully-connected layer
fc1  = mx.sym.FullyConnected(data=data, name='fc1', num_hidden=128)
# Apply relu to the output of the first fully-connnected layer
act1 = mx.sym.Activation(data=fc1, name='relu1', act_type="relu")

# The second fully-connected layer and the according activation function
fc2  = mx.sym.FullyConnected(data=act1, name='fc2', num_hidden = 64)
act2 = mx.sym.Activation(data=fc2, name='relu2', act_type="relu")

# The thrid fully-connected layer, note that the hidden size should be 10, which is the number of unique digits
fc3  = mx.sym.FullyConnected(data=act2, name='fc3', num_hidden=10)
# The softmax and loss layer
mlp  = mx.sym.SoftmaxOutput(data=fc3, name='softmax')

# We visualize the network structure with output size (the batch_size is ignored.)
shape = {"data" : (batch_size, 1, 28, 28)}
mx.viz.plot_network(symbol=mlp, shape=shape)

 
さてネットワーク定義とデータ iterator の準備ができました。トレーニングを開始できます。

# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
import logging
logging.getLogger().setLevel(logging.DEBUG)

model = mx.model.FeedForward(
    symbol = mlp,       # network structure
    num_epoch = 10,     # number of data passes for training 
    learning_rate = 0.1 # learning rate of SGD 
)
model.fit(
    X=train_iter,       # training data
    eval_data=val_iter, # validation data
    batch_end_callback = mx.callback.Speedometer(batch_size, 200) # output progress for each 200 data batches
)
INFO:root:Start training with [cpu(0)]
INFO:root:Epoch[0] Batch [200]	Speed: 19178.20 samples/sec	Train-accuracy=0.112900
INFO:root:Epoch[0] Batch [400]	Speed: 24869.00 samples/sec	Train-accuracy=0.110750
INFO:root:Epoch[0] Batch [600]	Speed: 25210.35 samples/sec	Train-accuracy=0.140050
INFO:root:Epoch[0] Resetting Data Iterator
INFO:root:Epoch[0] Time cost=2.726
INFO:root:Epoch[0] Validation-accuracy=0.258200
INFO:root:Epoch[1] Batch [200]	Speed: 25492.90 samples/sec	Train-accuracy=0.421850
INFO:root:Epoch[1] Batch [400]	Speed: 25741.42 samples/sec	Train-accuracy=0.745950
INFO:root:Epoch[1] Batch [600]	Speed: 25685.61 samples/sec	Train-accuracy=0.835700
INFO:root:Epoch[1] Resetting Data Iterator
INFO:root:Epoch[1] Time cost=2.355
INFO:root:Epoch[1] Validation-accuracy=0.848900
INFO:root:Epoch[2] Batch [200]	Speed: 26176.50 samples/sec	Train-accuracy=0.855650
INFO:root:Epoch[2] Batch [400]	Speed: 25339.32 samples/sec	Train-accuracy=0.885600
INFO:root:Epoch[2] Batch [600]	Speed: 25406.15 samples/sec	Train-accuracy=0.904950
INFO:root:Epoch[2] Resetting Data Iterator
INFO:root:Epoch[2] Time cost=2.368
INFO:root:Epoch[2] Validation-accuracy=0.911900
INFO:root:Epoch[3] Batch [200]	Speed: 26090.96 samples/sec	Train-accuracy=0.914800
INFO:root:Epoch[3] Batch [400]	Speed: 23369.18 samples/sec	Train-accuracy=0.927450
INFO:root:Epoch[3] Batch [600]	Speed: 24945.86 samples/sec	Train-accuracy=0.936700
INFO:root:Epoch[3] Resetting Data Iterator
INFO:root:Epoch[3] Time cost=2.455
INFO:root:Epoch[3] Validation-accuracy=0.935800
INFO:root:Epoch[4] Batch [200]	Speed: 25774.72 samples/sec	Train-accuracy=0.940500
INFO:root:Epoch[4] Batch [400]	Speed: 25342.44 samples/sec	Train-accuracy=0.946850
INFO:root:Epoch[4] Batch [600]	Speed: 17439.10 samples/sec	Train-accuracy=0.950700
INFO:root:Epoch[4] Resetting Data Iterator
INFO:root:Epoch[4] Time cost=2.738
INFO:root:Epoch[4] Validation-accuracy=0.948900
INFO:root:Epoch[5] Batch [200]	Speed: 25016.45 samples/sec	Train-accuracy=0.952700
INFO:root:Epoch[5] Batch [400]	Speed: 25466.91 samples/sec	Train-accuracy=0.957000
INFO:root:Epoch[5] Batch [600]	Speed: 24626.90 samples/sec	Train-accuracy=0.959550
INFO:root:Epoch[5] Resetting Data Iterator
INFO:root:Epoch[5] Time cost=2.425
INFO:root:Epoch[5] Validation-accuracy=0.957700
INFO:root:Epoch[6] Batch [200]	Speed: 25067.18 samples/sec	Train-accuracy=0.960950
INFO:root:Epoch[6] Batch [400]	Speed: 24772.56 samples/sec	Train-accuracy=0.963550
INFO:root:Epoch[6] Batch [600]	Speed: 15472.62 samples/sec	Train-accuracy=0.965000
INFO:root:Epoch[6] Resetting Data Iterator
INFO:root:Epoch[6] Time cost=2.911
INFO:root:Epoch[6] Validation-accuracy=0.964000
INFO:root:Epoch[7] Batch [200]	Speed: 25828.42 samples/sec	Train-accuracy=0.966100
INFO:root:Epoch[7] Batch [400]	Speed: 25189.55 samples/sec	Train-accuracy=0.968550
INFO:root:Epoch[7] Batch [600]	Speed: 25013.47 samples/sec	Train-accuracy=0.969950
INFO:root:Epoch[7] Resetting Data Iterator
INFO:root:Epoch[7] Time cost=2.399
INFO:root:Epoch[7] Validation-accuracy=0.965300
INFO:root:Epoch[8] Batch [200]	Speed: 26268.23 samples/sec	Train-accuracy=0.971450
INFO:root:Epoch[8] Batch [400]	Speed: 19536.00 samples/sec	Train-accuracy=0.972100
INFO:root:Epoch[8] Batch [600]	Speed: 12375.30 samples/sec	Train-accuracy=0.974000
INFO:root:Epoch[8] Resetting Data Iterator
INFO:root:Epoch[8] Time cost=3.439
INFO:root:Epoch[8] Validation-accuracy=0.967000
INFO:root:Epoch[9] Batch [200]	Speed: 25806.82 samples/sec	Train-accuracy=0.974350
INFO:root:Epoch[9] Batch [400]	Speed: 24996.94 samples/sec	Train-accuracy=0.975600
INFO:root:Epoch[9] Batch [600]	Speed: 24979.98 samples/sec	Train-accuracy=0.977250
INFO:root:Epoch[9] Resetting Data Iterator
INFO:root:Epoch[9] Time cost=2.408
INFO:root:Epoch[9] Validation-accuracy=0.969700

 
トレーニングが終了した後は、単一の画像を予測できます。

# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
plt.imshow(val_img[0], cmap='Greys_r')
plt.axis('off')
plt.show()
prob = model.predict(val_img[0:1].astype(np.float32)/255)[0]
assert max(prob) > 0.99, "Low prediction accuracy."
print 'Classified as %d with probability %f' % (prob.argmax(), max(prob))

Classified as 7 with probability 0.999889

データ iterator が与えられた時の精度を評価することもできます。

# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
valid_acc = model.score(val_iter)
print 'Validation accuracy: %f%%' % (valid_acc *100,)
assert valid_acc > 0.95, "Low validation accuracy."
Validation accuracy: 96.960000%

更に以下のコードを利用すれば Web ページのボックスに書かれる数字を認識することもできます。

from IPython.display import HTML
import cv2
import numpy as np

def classify(img):
    img = img[len('data:image/png;base64,'):].decode('base64')
    img = cv2.imdecode(np.fromstring(img, np.uint8), -1)
    img = cv2.resize(img[:,:,3], (28,28))
    img = img.astype(np.float32).reshape((1,1,28,28))/255.0
    return model.predict(img)[0].argmax()

HTML(filename="mnist_demo.html")



Result: 

(訳注: 以下は実際に組み込んで訳者の手書き数字を認識させたスナップショットです。)

 

畳込みニューラルネットワーク

前の完全結合層では画像をトレーニングの間ベクトルに単に reshape していることに注意しましょう。それは、ピクセルが水平にも垂直次元にも相関関係にあるという空間情報を無視しています。畳込み層は、より構造的な重み $W$ を使用してこの欠点を改善することを目指しています。単なる行列-行列操作の代わりに、出力を得るために 2-D 畳込みを使用しています。

 
異なる特徴を捕捉するためにまた、それぞれが個別の重み行列を持つ複数の特徴マップを持てます :

 
畳込み層の他にも、畳込みニューラルネットワークの他の主要な変更はプーリング層の追加です。プーリング層は $n \times m$ (= しばしばカーネルサイズと呼ばれます) 画像パッチをネットワークが空間位置に敏感でなくなるように単一の値に減じます。

data = mx.symbol.Variable('data')
# first conv layer
conv1 = mx.sym.Convolution(data=data, kernel=(5,5), num_filter=20)
tanh1 = mx.sym.Activation(data=conv1, act_type="tanh")
pool1 = mx.sym.Pooling(data=tanh1, pool_type="max", kernel=(2,2), stride=(2,2))
# second conv layer
conv2 = mx.sym.Convolution(data=pool1, kernel=(5,5), num_filter=50)
tanh2 = mx.sym.Activation(data=conv2, act_type="tanh")
pool2 = mx.sym.Pooling(data=tanh2, pool_type="max", kernel=(2,2), stride=(2,2))
# first fullc layer
flatten = mx.sym.Flatten(data=pool2)
fc1 = mx.symbol.FullyConnected(data=flatten, num_hidden=500)
tanh3 = mx.sym.Activation(data=fc1, act_type="tanh")
# second fullc
fc2 = mx.sym.FullyConnected(data=tanh3, num_hidden=10)
# softmax loss
lenet = mx.sym.SoftmaxOutput(data=fc2, name='softmax')
mx.viz.plot_network(symbol=lenet, shape=shape)

 
LeNet は前の multilayer perceptron よりも複雑なので、トレーニングには CPU の代わりに GPU を使います。

# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
model = mx.model.FeedForward(
    ctx = mx.gpu(0),     # use GPU 0 for training, others are same as before
    symbol = lenet,       
    num_epoch = 10,     
    learning_rate = 0.1)
model.fit(
    X=train_iter,  
    eval_data=val_iter, 
    batch_end_callback = mx.callback.Speedometer(batch_size, 200)
) 
assert model.score(val_iter) > 0.98, "Low validation accuracy."

(訳注: 以下は CPU で試しています。)

INFO:root:Start training with [cpu(0)]
INFO:root:Epoch[0] Batch [200]	Speed: 718.21 samples/sec	Train-accuracy=0.112550
INFO:root:Epoch[0] Batch [400]	Speed: 720.94 samples/sec	Train-accuracy=0.110750
INFO:root:Epoch[0] Batch [600]	Speed: 732.30 samples/sec	Train-accuracy=0.112050
INFO:root:Epoch[0] Resetting Data Iterator
INFO:root:Epoch[0] Time cost=82.999
INFO:root:Epoch[0] Validation-accuracy=0.113500
INFO:root:Epoch[1] Batch [200]	Speed: 664.57 samples/sec	Train-accuracy=0.408550
INFO:root:Epoch[1] Batch [400]	Speed: 651.25 samples/sec	Train-accuracy=0.889900
INFO:root:Epoch[1] Batch [600]	Speed: 730.05 samples/sec	Train-accuracy=0.935100
INFO:root:Epoch[1] Resetting Data Iterator
INFO:root:Epoch[1] Time cost=88.266
INFO:root:Epoch[1] Validation-accuracy=0.947700
INFO:root:Epoch[2] Batch [200]	Speed: 714.93 samples/sec	Train-accuracy=0.949900
INFO:root:Epoch[2] Batch [400]	Speed: 652.99 samples/sec	Train-accuracy=0.965900
INFO:root:Epoch[2] Batch [600]	Speed: 733.28 samples/sec	Train-accuracy=0.970400
INFO:root:Epoch[2] Resetting Data Iterator
INFO:root:Epoch[2] Time cost=85.936
INFO:root:Epoch[2] Validation-accuracy=0.974400
INFO:root:Epoch[3] Batch [200]	Speed: 743.78 samples/sec	Train-accuracy=0.971700
INFO:root:Epoch[3] Batch [400]	Speed: 736.98 samples/sec	Train-accuracy=0.977950
INFO:root:Epoch[3] Batch [600]	Speed: 748.92 samples/sec	Train-accuracy=0.978450
INFO:root:Epoch[3] Resetting Data Iterator
INFO:root:Epoch[3] Time cost=80.793
INFO:root:Epoch[3] Validation-accuracy=0.980400
INFO:root:Epoch[4] Batch [200]	Speed: 715.04 samples/sec	Train-accuracy=0.979100
INFO:root:Epoch[4] Batch [400]	Speed: 689.19 samples/sec	Train-accuracy=0.983000
INFO:root:Epoch[4] Batch [600]	Speed: 734.42 samples/sec	Train-accuracy=0.983700
INFO:root:Epoch[4] Resetting Data Iterator
INFO:root:Epoch[4] Time cost=84.282
INFO:root:Epoch[4] Validation-accuracy=0.983300
INFO:root:Epoch[5] Batch [200]	Speed: 740.29 samples/sec	Train-accuracy=0.982300
INFO:root:Epoch[5] Batch [400]	Speed: 737.16 samples/sec	Train-accuracy=0.986000
INFO:root:Epoch[5] Batch [600]	Speed: 742.74 samples/sec	Train-accuracy=0.986600
INFO:root:Epoch[5] Resetting Data Iterator
INFO:root:Epoch[5] Time cost=81.135
INFO:root:Epoch[5] Validation-accuracy=0.983700
INFO:root:Epoch[6] Batch [200]	Speed: 753.02 samples/sec	Train-accuracy=0.985550
INFO:root:Epoch[6] Batch [400]	Speed: 748.51 samples/sec	Train-accuracy=0.988600
INFO:root:Epoch[6] Batch [600]	Speed: 746.93 samples/sec	Train-accuracy=0.988700
INFO:root:Epoch[6] Resetting Data Iterator
INFO:root:Epoch[6] Time cost=80.118
INFO:root:Epoch[6] Validation-accuracy=0.985100
INFO:root:Epoch[7] Batch [200]	Speed: 746.74 samples/sec	Train-accuracy=0.988000
INFO:root:Epoch[7] Batch [400]	Speed: 748.86 samples/sec	Train-accuracy=0.989950
INFO:root:Epoch[7] Batch [600]	Speed: 750.64 samples/sec	Train-accuracy=0.990250
INFO:root:Epoch[7] Resetting Data Iterator
INFO:root:Epoch[7] Time cost=80.202
INFO:root:Epoch[7] Validation-accuracy=0.985600
INFO:root:Epoch[8] Batch [200]	Speed: 749.68 samples/sec	Train-accuracy=0.989800
INFO:root:Epoch[8] Batch [400]	Speed: 745.11 samples/sec	Train-accuracy=0.991150
INFO:root:Epoch[8] Batch [600]	Speed: 735.06 samples/sec	Train-accuracy=0.991300
INFO:root:Epoch[8] Resetting Data Iterator
INFO:root:Epoch[8] Time cost=80.796
INFO:root:Epoch[8] Validation-accuracy=0.986600
INFO:root:Epoch[9] Batch [200]	Speed: 729.48 samples/sec	Train-accuracy=0.991300
INFO:root:Epoch[9] Batch [400]	Speed: 749.17 samples/sec	Train-accuracy=0.992200
INFO:root:Epoch[9] Batch [600]	Speed: 739.76 samples/sec	Train-accuracy=0.992300
INFO:root:Epoch[9] Resetting Data Iterator
INFO:root:Epoch[9] Time cost=81.223
INFO:root:Epoch[9] Validation-accuracy=0.987500

 
同じハイパーパラメータで LeNet は 98.7% 検証精度を達成することに注意してください、これは前の multilayer perceptron 精度 96.6% を改善します。

モデル・パラメータを $mod$ で書き直すので、新しい CNN モデルが分類精度を改善するか否かを先の数字認識ボックスで試すことができます。

 

Next Steps

 

以上