• ホーム
  • Chainer
  • Chainerのチュートリアルを試してみた(トレーナー編)

Chainerのチュートリアルを試してみた(トレーナー編)

Chainerのチュートリアルを試してみました。トレーナーを使ったものです。

目次

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

Chainerとは

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

本家でチュートリアルを公開されてますが、今回試してみたのは こちらのサイトのもの で、中盤に書かれているtrainerを使用した場合です。

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

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

基本的にはチュートリアルにあるコードを、ローカルで動かせるように改変しながらトレースしたものです。

UpdaterとTrainer

コードの途中でL.Classifierというメソッドが出てきますが、これはネットワークのインスタンスにロスを計算する機能を追加するものです。

UpdaterはIteratorとOptimizerを使って、ミニバッチを作り、順方向の計算をして、ロスを計算して、パラメーターのアップデートをしてくれます。

Updaterが一連の学習の手順を実行してくれるのですが、そのUpdaterを使って学習全体を管理するのがTrainerです。学習する回数や、ログの出力などの制御をします。

データの準備

トライアンドエラーで何度も実行することを想定しまして、Chainerの便利機能でダウンロードしたMNISTデータをローカルに保存して利用します。手順は 前の投稿 を参照してください。

学習

おおまかに言って、こんな流れです。

  1. データセットの準備をする。

  2. イテレーターを作る。

  3. ネットワークを作る。

  4. オプティマイザーを作ってネットワークを渡す。

  5. アップデーターにイテレーターとオプティマイザーを渡す。

  6. トレーナーにアップデーターを渡して学習を実行する。

  7. 学習結果を保存する。

コードがかなりすっきりするように感じますね。

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 import training
from chainer.training import extensions

# 乱数を初期化する関数
def reset_seed(seed=0):
    random.seed(seed)
    numpy.random.seed(seed)
    if chainer.cuda.available:
        chainer.cuda.cupy.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):
        # データを受け取った際の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)
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()

# ネットワークをClassifierで包んで、ロスの計算などをモデルに含める
net = L.Classifier(net)

# 最適化手法の選択
optimizer = optimizers.SGD(lr=0.01).setup(net)

# UpdaterにIteratorとOptimizerを渡す
gpu_id = -1  # GPUは不使用
updater = training.StandardUpdater(train_iter, optimizer, device=gpu_id)

# TrainerにUpdaterを渡す
max_epoch = 10
trainer = training.Trainer(updater, (max_epoch, 'epoch'), out='mnist_result')

# エクステンションの設定
trainer.extend(extensions.LogReport())
trainer.extend(extensions.snapshot(filename='snapshot_epoch-{.updater.epoch}'))
trainer.extend(extensions.Evaluator(valid_iter, net, device=gpu_id), name='val')
trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'main/accuracy', 'val/main/loss', 'val/main/accuracy', 'l1/W/data/std', 'elapsed_time']))
trainer.extend(extensions.ParameterStatistics(net.predictor.l1, {'std': numpy.std}))
trainer.extend(extensions.PlotReport(['l1/W/data/std'], x_key='epoch', file_name='std.png'))
trainer.extend(extensions.PlotReport(['main/loss', 'val/main/loss'], x_key='epoch', file_name='loss.png'))
trainer.extend(extensions.PlotReport(['main/accuracy', 'val/main/accuracy'], x_key='epoch', file_name='accuracy.png'))
trainer.extend(extensions.dump_graph('main/loss'))

# 学習の実行
trainer.run()

ロスの変遷のグラフが出力されますので見てみます。

ロスのグラフ

精度のグラフはこうなります。

精度のグラフ

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

推論してみた

学習結果を使って、テスト用データから100枚の画像を推論してみました。

コードは前の投稿とほぼ同じです。推論にトレーナーは使いませんからね。学習データの読み込みのところが異なっています。

import pickle
import matplotlib.pyplot as plt
import numpy

from chainer.datasets import split_dataset_random
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('mnist_result/snapshot_epoch-10', infer_net, path='updater/model:main/predictor/')

# テスト用のバッチデータを作る
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()

やはり100枚中5枚が不正解でした。

不正解1 不正解2 不正解3 不正解4 不正解5

公開日

広告