MNISTを単純なLSTMで解く(MXNet)

環境

Windows 10 Pro
GPUなし
Python 3.6.6(venv使用)

astroid==2.0.4
certifi==2018.8.24
chardet==3.0.4
colorama==0.3.9
graphviz==0.8.4
idna==2.6
isort==4.3.4
lazy-object-proxy==1.3.1
mccabe==0.6.1
mxnet==1.3.0
numpy==1.14.6
Pillow==5.2.0
pylint==2.1.1
requests==2.18.4
six==1.11.0
typed-ast==1.1.0
urllib3==1.22
wrapt==1.10.11

(注)Visual Studio Codeを使用するためpylintがインストールされている

方法の概略

イデアはこちらから拝借。

初めてのTensorFlow 数式なしのディープラーニング

初めてのTensorFlow 数式なしのディープラーニング

28×28の画像を28個の時系列データ(28単語の文字列)として扱う。(28行)
1つの時系列データ(1単語)がそれぞれ28次元のベクトルで表現されていると考える。(28列)
(単語がEmbedding層を通って28次元のベクトルに変換されたイメージ)
つまり、すでにEmbedding層を通過していると考えられるので、いきなりデータをLTSM層に放り込むことが可能。

モデル(Model.py)

import mxnet as mx
from mxnet.gluon import Block, nn, rnn

class Model(Block):
    def __init__(self, **kwargs):
        super(Model, self).__init__(**kwargs)
        with self.name_scope():
            self.lstm = rnn.LSTM(128)
            self.dense = nn.Dense(10)
            
    def forward(self, x):
        input = x.swapaxes(0,1)
        out = self.lstm(input)
        out = out.swapaxes(0,1)
        out = self.dense(out)
        return out

(注)GluonではLSTM層の入出力のレイアウトはデフォルトで 'TNC' のためswapaxesが必要。
T: sequence length
N: batch size
C: feature dimensions
mxnet.incubator.apache.org

MNISTデータ

データ取得はこちらのコードをそのまま拝借。
github.com
datasetフォルダ内のmnist.pyを使用

実行

from mnist import load_mnist
import mxnet as mx
from mxnet import gluon, autograd
import numpy as np

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=False, normalize=True)

x_train = x_train.reshape(-1,28,28)
x_test = x_test.reshape(-1,28,28)

x_train = mx.nd.array(x_train)
t_train = mx.nd.array(t_train)
x_test = mx.nd.array(x_test)
t_test = mx.nd.array(t_test)

'''
#画像表示
from PIL import Image
img = Image.fromarray(x_train[0]*255)
img.show()
'''

import Model
model = Model.Model()
model.initialize(mx.init.Xavier())
loss_func = gluon.loss.SoftmaxCrossEntropyLoss()
trainer = gluon.Trainer(model.collect_params(), 'adam')

def evaluate_accuracy(input, label, net):
    acc = mx.metric.Accuracy()    
    output = net(input)
    predictions = mx.nd.argmax(output, axis=1)
    acc.update(preds=predictions, labels=label)
    return acc.get()[1]

print('start training...')
batch_size = 100
epochs = 10
loss_n = [] #ログ表示用

for epoch in range(1, epochs + 1):
    #ランダムに並べ替えたインデックスを作成
    indexs = np.random.permutation(x_train.shape[0])
    cur_start = 0
    while cur_start < x_train.shape[0]:
        cur_end = (cur_start + batch_size) if (cur_start + batch_size) < x_train.shape[0] else x_train.shape[0]
        data = x_train[indexs[cur_start:cur_end]]
        label = t_train[indexs[cur_start:cur_end]]
        #ニューラルネットワークの順伝播
        with autograd.record():
            output = model(data)
            #損失を求める
            loss = loss_func(output, label)
            #ログ表示用に損失の値を保存
            loss_n.append(np.mean(loss.asnumpy()))
        #損失の値から逆伝播する
        loss.backward()
        #学習ステータスをデータサイズ分進める
        trainer.step(data.shape[0])
        cur_start = cur_end
    #ログを表示
    ll = np.mean(loss_n)
    test_acc = evaluate_accuracy(x_test, t_test, model)
    train_acc = evaluate_accuracy(x_train, t_train, model)
    print('%d epoch loss = %f train_acc = %f test_acc = %f' %(epoch, ll, train_acc, test_acc))
    loss_n = []

model.save_parameters('lstm.params')

結果

start training...
1 epoch loss = 0.286337 train_acc = 0.970117 test_acc = 0.971500
2 epoch loss = 0.084964 train_acc = 0.979317 test_acc = 0.978100
3 epoch loss = 0.056930 train_acc = 0.987733 test_acc = 0.984300
4 epoch loss = 0.042924 train_acc = 0.988817 test_acc = 0.986000
5 epoch loss = 0.035680 train_acc = 0.992967 test_acc = 0.989000
6 epoch loss = 0.028391 train_acc = 0.994150 test_acc = 0.988400
7 epoch loss = 0.024141 train_acc = 0.994000 test_acc = 0.986800
8 epoch loss = 0.021252 train_acc = 0.994783 test_acc = 0.988700
9 epoch loss = 0.019520 train_acc = 0.993967 test_acc = 0.987400
10 epoch loss = 0.019161 train_acc = 0.995467 test_acc = 0.988600

最後に

いまさら感が強いMNISTであるが、LSTMを使った「非常に簡単」なモデルで良い結果が得られた。