[Python] AI 07 Chainer + MNISTによる深層学習

[macOS Catalina 10.15.7]

日本で開発されている深層学習フレームワークChainerと手書き数字データセットMNISTを使って深層学習してみました。

今回は学習モデルの作成・検証と保存です。

教本で使っているChainnerのバージョン5.0.0に対し、現在pipでダウンロードできる最新は7.8.0です。イテレータのリセット方法が違うので注意が必要です。

教本ではリセットする位置がprintの前になっていますが、後に持ってくるのが妥当かと思います。でないとエポックが切り替わる最後の検証結果が出力できません。

ここから中級になりますが、Pythonの文法的にも急に難易度が高くなる印象です。Chainerの過去バージョンを使いJupyter Notebookで単純になぞる分には問題ないものの、最新バージョンを使う場合はコードを修正する必要があるので脱落者が一気に増えそうな気がします。

import numpy as np
import matplotlib.pyplot as plt
import chainer
import chainer.links as L
import chainer.functions as F
from chainer import iterators
from chainer import optimizers
from chainer.dataset import concat_examples
from chainer import serializers

print(f'chainerバージョン {chainer.__version__}')

class MLP(chainer.Chain):
    def __init__(self, number_hidden_units=1000, number_out_units=10):
        super(MLP, self).__init__()

        with self.init_scope():
            self.layer1=L.Linear(None, number_hidden_units)
            self.layer2=L.Linear(number_hidden_units, number_hidden_units)
            self.layer3=L.Linear(number_hidden_units, number_out_units)

    def __call__(self, input_data):
        result1 = F.relu(self.layer1(input_data))
        result2 = F.relu(self.layer2(result1))
        return self.layer3(result2)

def testEpoch(train_iterator,loss):
    print(f'学習回数:{train_iterator.epoch:02d} --> 学習損失:{float(loss.data):.02f}')

    # 検証用損失と精度
    test_losses = []
    test_accuracies = []

    i = 1
    while True:
        test_dataset = test_iterator.next()
        test_data, test_teacher_labels = concat_examples(test_dataset)

        # 検証データをモデルに渡す
        prediction_test = model(test_data)

        # 検証データに対して得られた予測値と正解を比較し損失を算出する
        loss_test = F.softmax_cross_entropy(prediction_test, test_teacher_labels)
        test_losses.append(loss_test.data)

        # 精度を計算する
        accuracy = F.accuracy(prediction_test, test_teacher_labels)
        test_accuracies.append(accuracy.data)

        print(f'{i}回目 検証損失:{np.mean(test_losses):.4f} 検証精度:{np.mean(test_accuracies):.2f}')
        
        # Chainer7.8.0用にリセット方法を変更
        if test_iterator.is_new_epoch:
            print('\n',end='') # 1回だけ改行
            test_iterator.reset()
            break

        i = i + 1

train_data, test_data = chainer.datasets.get_mnist(withlabel=True, ndim=1)

model = MLP()

# print('入力層のバイアスパラメータ配列の形\n', model.layer1.b.shape)
# print('初期化後の値\n', model.layer1.b.data)

BATCH_SIZE = 100

train_iterator = iterators.SerialIterator(train_data, BATCH_SIZE)
test_iterator = iterators.SerialIterator(test_data, BATCH_SIZE,
repeat=False, shuffle=False)

optimizer = optimizers.SGD(lr=0.01)
optimizer.setup(model)

MAX_EPOCH = 5

loss_list = list()
while train_iterator.epoch < MAX_EPOCH:
    # 学習データセットをイテレータから取り出す
    train_dataset = train_iterator.next()

    # 学習データセットを学習データと正解データに分離する
    train_data, teacher_labels = concat_examples(train_dataset)

    # 予測値の計算をする
    prediction_train = model(train_data)

    # 得られた予測値と正解データと比較し学習損失を計算する
    loss = F.softmax_cross_entropy(prediction_train, teacher_labels)

    # ニューラルネットワークの中の勾配を初期化する
    model.cleargrads()

    # 勾配を計算する
    loss.backward()

    # 損失を反映してパラメータを更新する
    optimizer.update()

    # 一回学習(epoch)する度に検証データに対する予測精度を計る
    if train_iterator.is_new_epoch:
        loss_list.append(loss)
        print(loss_list)
        testEpoch(train_iterator,loss)

# 学習済モデルを保存する
serializers.save_npz('chainer-mnist.model', model)