Chainerのチュートリアルを試してみた

Chainerのチュートリアルを試してみました。初めてのChainerです。

目次

  1. Chainer
  2. データの準備
  3. 学習
  4. 推論してみた

Chainer

Chainer とは、言わずと知れたPython用のディープラーニングフレームワークです。pipでインストールできます。

本家でチュートリアルを公開されてますが、今回試してみたのは こちらのサイトのもの です。

試した環境は、Windows10 64bit、Python 3.7、Chainer 6.1です。GPUは使用しません。 参考にしたサイトはJupyter notebookでの実行を前提に書かれていますが、この投稿ではVisual Studio Codeで実行しました。

内容としては、簡単なネットワークを使ってMNISTの画像セットの分類をするというものです。

基本的にはチュートリアルにあるコードをそのままトレースしてます。

データの準備

まずデータを準備します。

Chainerの便利機能を使ってMNISTのデータセットをダウンロードするのですが、トライアンドエラーを繰り返すことが想定されましたので、ダウンロードしたデータをpickleで保存して2回目以降はローカルに保存したデータを使うことにしました。

import pickle
import matplotlib.pyplot as plt
from chainer.datasets import mnist

# データセットのダウンロード
train_val, test = mnist.get_mnist(withlabel=True, ndim=1)

# データセットをローカルファイルに保存
with open('train_val.pickle', mode='wb') as fo1:
    pickle.dump(train_val, fo1)
with open('test.pickle', mode='wb') as fo2:
    pickle.dump(test, fo2)

# データを表示
x, t = train_val[0]  # 0番目の (data, label) を取り出す
plt.imshow(x.reshape(28, 28), cmap='gray')
plt.axis('off')
plt.show()
print('label:', t)

学習

学習して、得られた学習データを保存します。

どこのコードで何をしているかはコメントを見ていただきたいのですが、基本的にはこういう流れのようです。

  1. ネットワークを定義するクラスを作成する。
  2. データセットを読み込む。
  3. イテレーター(データからバッチを取り出す機能)を設定する。
  4. ネットワークのインスタンスを作る。
  5. オプティマイザー(パラメータを最適化する機能)を設定する。
  6. 学習(順方向の計算→損失の計算→勾配の計算→パラメータ更新)

ミニバッチ単位で学習をして、指定したエポック数だけ繰り返すわけですね。

import pickle
import matplotlib.pyplot as plt
import random
import numpy

import chainer
from chainer.datasets import split_dataset_random
from chainer import iterators
import chainer.links as L
import chainer.functions as F
from chainer import optimizers
from chainer.dataset import concat_examples
from chainer.cuda import to_cpu
from chainer import serializers

# 乱数を初期化する関数
def reset_seed(seed=0):
    random.seed(seed)
    numpy.random.seed(seed)

# ネットワークを定義するクラス
class MLP(chainer.Chain):

    def __init__(self, n_mid_units=100, n_out=10):
        super(MLP, self).__init__()

        # 各層の定義
        with self.init_scope():
            self.l1 = L.Linear(None, n_mid_units)
            self.l2 = L.Linear(n_mid_units, n_mid_units)
            self.l3 = L.Linear(n_mid_units, n_out)

    def __call__(self, x):
        # ネットワークの定義
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        return self.l3(h2)


# ローカルファイルからデータセットを読み込む
with open('test.pickle', mode='rb') as fi1:
    test = pickle.load(fi1)
with open('train_val.pickle', mode='rb') as fi2:
    train_val = pickle.load(fi2)

# データセットを学習用と検証用に分割する
train, valid = split_dataset_random(train_val, 50000, seed=0)

# イテレーターの設定
batchsize = 128
train_iter = iterators.SerialIterator(train, batchsize)
valid_iter = iterators.SerialIterator(valid, batchsize, repeat=False, shuffle=False)
test_iter = iterators.SerialIterator(test, batchsize, repeat=False, shuffle=False)

# 乱数の初期設定
reset_seed(0)

# ネットワークのインスタンスを作る
net = MLP()

# オプティマイザーの設定
optimizer = optimizers.SGD(lr=0.01).setup(net)

# 学習の実行
max_epoch = 10
gpu_id = -1 # GPU不使用

while train_iter.epoch < max_epoch:

    train_batch = train_iter.next()
    x, t = concat_examples(train_batch, gpu_id)

    # 予測値の計算
    y = net(x)

    # ロスの計算
    loss = F.softmax_cross_entropy(y, t)

    # 勾配の計算
    net.cleargrads()
    loss.backward()

    # パラメータの更新
    optimizer.update()

    # 1エポック終わったらロスと精度を表示する
    if train_iter.is_new_epoch:
        # ロスの表示
        print('epoch:{:02d} train_loss:{:.04f} '.format(train_iter.epoch, float(to_cpu(loss.data))), end='')

        valid_losses = []
        valid_accuracies = []
        while True:
            valid_batch = valid_iter.next()
            x_valid, t_valid = concat_examples(valid_batch, gpu_id)

            # Validationデータをforward
            with chainer.using_config('train', False), chainer.using_config('enable_backprop', False):
                y_valid = net(x_valid)

            # ロスを計算
            loss_valid = F.softmax_cross_entropy(y_valid, t_valid)
            valid_losses.append(to_cpu(loss_valid.array))

            # 精度を計算
            accuracy = F.accuracy(y_valid, t_valid)
            accuracy.to_cpu()
            valid_accuracies.append(accuracy.array)

            if valid_iter.is_new_epoch:
                valid_iter.reset()
                break

        print('val_loss:{:.04f} val_accuracy:{:.04f}'.format(numpy.mean(valid_losses), numpy.mean(valid_accuracies)))

# テストデータでの評価
test_accuracies = []
while True:
    test_batch = test_iter.next()
    x_test, t_test = concat_examples(test_batch, gpu_id)

    # テストデータをforward
    with chainer.using_config('train', False), chainer.using_config('enable_backprop', False):
        y_test = net(x_test)

    # 精度を計算
    accuracy = F.accuracy(y_test, t_test)
    accuracy.to_cpu()
    test_accuracies.append(accuracy.array)

    if test_iter.is_new_epoch:
        test_iter.reset()
        break

print('test_accuracy:{:.04f}'.format(numpy.mean(test_accuracies)))

# 学習結果の保存
serializers.save_npz('my_mnist.model', net)

出力はこうなります。

epoch:01 train_loss:0.9035 val_loss:0.9046 val_accuracy:0.8071
epoch:02 train_loss:0.4777 val_loss:0.5205 val_accuracy:0.8667
epoch:03 train_loss:0.4600 val_loss:0.4219 val_accuracy:0.8851
epoch:04 train_loss:0.3510 val_loss:0.3747 val_accuracy:0.8955
epoch:05 train_loss:0.2335 val_loss:0.3468 val_accuracy:0.9021
epoch:06 train_loss:0.2353 val_loss:0.3288 val_accuracy:0.9049
epoch:07 train_loss:0.3196 val_loss:0.3137 val_accuracy:0.9100
epoch:08 train_loss:0.2127 val_loss:0.2988 val_accuracy:0.9156
epoch:09 train_loss:0.4406 val_loss:0.2892 val_accuracy:0.9155
epoch:10 train_loss:0.2950 val_loss:0.2790 val_accuracy:0.9194
test_accuracy:0.9233

学習を繰り返すうちに正解率が上がっていきます。最終的には92.3%になりました。

なお、この程度の学習でしたらSurface Pro6(Core i5-8250U)でも数分で計算が終わります。

推論してみた

学習データを使って推論してみました。

import pickle
import matplotlib.pyplot as plt
import numpy

import chainer
import chainer.links as L
import chainer.functions as F
from chainer import serializers

# ネットワークを定義するクラス
class MLP(chainer.Chain):

    def __init__(self, n_mid_units=100, n_out=10):
        super(MLP, self).__init__()

        # パラメータを持つ層の登録
        with self.init_scope():
            self.l1 = L.Linear(None, n_mid_units)
            self.l2 = L.Linear(n_mid_units, n_mid_units)
            self.l3 = L.Linear(n_mid_units, n_out)

    def __call__(self, x):
        # データを受け取った際のforward計算を書く
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        return self.l3(h2)

# テスト用データを読み込む
with open('test.pickle', mode='rb') as fi1:
    test = pickle.load(fi1)

# ネットワークのインスタンスを作る
infer_net = MLP()

# ネットワークに学習済みのパラメータを読み込む
serializers.load_npz('my_mnist.model', infer_net)

# テスト用のバッチデータを作る
test_batch_size = 100
t = [] # 画像のラベル
x, tt = test[0] # xが画像のデータ
t.append(tt)
for i in range(1,test_batch_size):
    xt, tt = test[i]
    t.append(tt)
    x = numpy.vstack((x,xt))

# ネットワークと同じデバイス上にデータを送る
x = infer_net.xp.asarray(x)

# モデルのforward関数に渡す
with chainer.using_config('train', False), chainer.using_config('enable_backprop', False):
    y = infer_net(x)

# Variable形式で出てくるので中身を取り出す
y = y.array

# 予測確率の最大値のインデックスを見る
pred_label = y.argmax(axis=1)

# 予測に失敗したデータの抽出
failed_index = []
for i in range(len(pred_label)):
    if t[i] != pred_label[i]:
        failed_index.append(i)
print('Failed: n = ', len(failed_index), ' / ', len(pred_label))

# 予測に失敗した画像の表示
for i in range(len(failed_index)):
    label_text = 'Label: ' + str(t[failed_index[i]])
    pred_text = 'Pred: ' + str(pred_label[failed_index[i]])
    plt.imshow(x[failed_index[i]].reshape(28, 28), cmap='gray')
    plt.text(1,1, label_text, color='white')
    plt.text(1,2, pred_text, color='white')
    plt.show()

チュートリアルでは画像1つだけでテストしていましたが、ここではバッチにしてみました。 ついでに、判定に失敗した画像を全て表示するようにしてあります。

100枚の画像データでテストして、判定に失敗したのは5枚でした。

失敗した5枚はこれなのですが、私も正解する自信のない画像があります・・・

失敗画像1 失敗画像2 失敗画像3 失敗画像4 失敗画像5

広告

Chainerカテゴリの投稿