【Chainer実践編6】Trainerを使ってみよう。書き換えて分かるTrainerの便利機能

【Chainer実践編6】Trainerを使ってみよう。書き換えて分かるTrainerの便利機能

そろそろガチでこのブログでも、数学に触れた方が説明しやすいし理解が深まるし、いいことづくめだと思うのです・・・が。

しかし、人類には「数式を見るとイラつく」という、成長期に刷り込まれた闇が残っていることがあるので注意。

ども、アイーンです。

 

今回は前回の続き、Trainerを使ってアヤメの品種分類を書き換えてみましょう。

まだ見てない方は、Trainerの有用性も書いてあるためそちらをチラッとご覧ください。

【Chainer実践編5】Trainerってなに?超便利なChainer学習のデフォルトスタンダード!

 

それでは、ここからコードの解説です。

 

Trainerコードの解説

まずは下に、Trainerを使ったアヤメの品種分類コードを載せておきます。

赤い部分が、今回解説するTrainerを使って書かれたコードです。

 

導入前

import chainer
import chainer.links as L
import chainer.functions as F
from chainer import Variable,Chain,optimizers

#追加

import numpy as np
from sklearn import datasets

IrisData = datasets.load_iris()
x = IrisData.data.astype(np.float32)
t = IrisData.target
n = t.size

t_matrix = np.zeros(3 * n).reshape(n, 3).astype(np.float32)

for i in range(n):
    t_matrix[i, t[i]] = 1.0

index = np.arange(n)
index_train = index[index % 2 != 0]
index_test = index[index % 2 == 0]

x_train = x[index_train, : ] #入力データ(訓練用)
t_train = t_matrix[index_train, : ] #正解データ(訓練用)
x_test = x[index_test, : ] #入力データ(テスト用)
t_test = t[index_test] #正解データ(テスト用)

x_train_v = Variable(x_train)
t_train_v = Variable(t_train)
x_test_v = Variable(x_test)


class IrisLearn(Chain):
    def __init__(self):
        super(IrisLearn, self).__init__(
            l1=L.Linear(4, 6),
            l2=L.Linear(6, 6),
            l3=L.Linear(6, 3),
        )
    def predict(self, x):
        h1 = F.sigmoid(self.l1(x))
        h2 = F.sigmoid(self.l2(h1))
        h3 = self.l3(h2)
        return h3



model = IrisLearn()
opt = optimizers.Adam()
opt.setup(model)

for i in range(10000):
    model.cleargrads()
    y_train_v = model.predict(x_train_v)
    loss = F.mean_squared_error(y_train_v,t_train_v)
    loss.backward()
    opt.update()
    

model.cleargrads()
y_test_v = model.predict(x_test_v)
y_test = y_test_v.data

correct = 0
count = y_test.shape[0]

for i in range(count):
    maxIndex = np.argmax(y_test[i, :])
    print(y_test[i, :], maxIndex)
    if maxIndex == t_test[i]:
        correct += 1

print("正解数 ", correct, ",問題数 ",count , ", ", correct/ count * 100,"%")

 

 

導入後

import chainer
import chainer.links as L
import chainer.functions as F
from chainer import Variable,Chain,optimizers

from chainer.datasets import tuple_dataset
from chainer import training, iterators
from chainer.training import extensions

import numpy as np

from sklearn import datasets

IrisData = datasets.load_iris()
x = IrisData.data.astype(np.float32)
t = IrisData.target
n = t.size

t_matrix = np.zeros(3 * n).reshape(n, 3).astype(np.float32)

for i in range(n):
    t_matrix[i, t[i]] = 1.0

index = np.arange(n)
index_train = index[index % 2 != 0]
index_test = index[index % 2 == 0]

x_train = x[index_train, : ] #入力データ(訓練用)
t_train = t_matrix[index_train, : ] #正解データ(訓練用)
x_test = x[index_test, : ] #入力データ(テスト用)
t_test = t[index_test] #正解データ(テスト用)

train = tuple_dataset.TupleDataset(x_train, t_train)

x_test_v = Variable(x_test)


class IrisLearn(Chain):
    def __init__(self):
        super(IrisLearn, self).__init__(
            l1=L.Linear(4, 6),
            l2=L.Linear(6, 6),
            l3=L.Linear(6, 3),
        )

    def __call__(self, x, t):
        return F.mean_squared_error(self.predict(x), t)

    def predict(self, x):
        h1 = F.sigmoid(self.l1(x))
        h2 = F.sigmoid(self.l2(h1))
        h3 = self.l3(h2)
        return h3



model = IrisLearn()
opt = optimizers.Adam()
opt.setup(model)

train_iter = iterators.SerialIterator(train, 30)
updater = training.StandardUpdater(train_iter, opt)
trainer = training.Trainer(updater, (5000, 'epoch'))
trainer.extend(extensions.ProgressBar())
trainer.run()

model.cleargrads()
y_test_v = model.predict(x_test_v)
y_test = y_test_v.data

correct = 0
count = y_test.shape[0]

for i in range(count):
    maxIndex = np.argmax(y_test[i, :])
    print(y_test[i, :], maxIndex)
    if maxIndex == t_test[i]:
        correct += 1

print("正解数 ", correct, ",問題数 ",count , ", ", correct/ count * 100,"%")

まずは追加されたimportについて解説です。

from chainer.datasets import tuple_dataset

from chainer import training, iterators

from chainer.training import extensions

tuple_datasetとは、データをタプル化(途中で変更できないリストみたいなの)するために用いられます。タプル化することで、複数のデータセットを一つの変数にぶち込めています。

そして、trainingとiteratorをインポートし

chainerのtrainingモジュールからextensionsもインポートします。

これで、Trainerを使う準備が出来ました。

 

続いて、訓練させるデータを書き換えてみましょう。

 

訓練データの準備

変更前
x_train_v = Variable(x_train)
t_train_v = Variable(t_train)

 

変更後
train = tuple_dataset.TupleDataset(x_train, t_train)

 

タプルデータセットを使ってtrainという変数に訓練用のデータを詰め込みます。

ここで詰め込むのは、x_train(訓練データ)とt_train(正解データ)です。

一行でスッキリ!

 

次はChainクラスに一部追加するコードです。

ChainクラスをTrainer用に書き換え

 

変更箇所

class IrisLearn(Chain):
    def __init__(self):
        super(IrisLearn, self).__init__(
            l1=L.Linear(4, 6),
            l2=L.Linear(6, 6),
            l3=L.Linear(6, 3),
        )

     def __call__(self, x, t):
        return F.mean_squared_error(self.predict(x), t)

     def predict(self, x):
         h1 = F.sigmoid(self.l1(x))
         h2 = F.sigmoid(self.l2(h1))
         h3 = self.l3(h2)
         return h3

赤い部分が変更箇所です。

Trainerを使用する場合、Chainクラスの内部に__call__メソッドの形式で損失関数を返す必要があります。

ここ、忘れがちですので気を付けましょう。

 

さて、最後に学習のコードを書き換えます。

学習コードの書き換え

変更前

for i in range(10000):

    model.cleargrads()
    y_train_v = model.predict(x_train_v)
    loss = F.mean_squared_error(y_train_v,t_train_v)
    loss.backward()
    opt.update()

変更後

train_iter = iterators.SerialIterator(train, 30)
updater = training.StandardUpdater(train_iter, opt)
trainer = training.Trainer(updater, (5000, ‘epoch’))
trainer.extend(extensions.ProgressBar())
trainer.run()

変更前は、forループで回数を指定しバッチ学習を行わせました。

でもコイツはスゲェやです。

変更後では

train_iterでイテレータに、タプル化した訓練データを入れます。

この際(train,30)となっていますが、trainが訓練データ、30がバッチサイズになります。

バッチサイズとは、「訓練データを分ける量」のことです。ミニバッチ学習を行うためにデータを分けます。

アヤメの訓練データは全部で75個でしたが

「30個を取り出して重みとバイアスを変更する」

と指定しています。

 

続いて、updaterにイテレータと最適化関数を入れます。

これは、重みとバイアスの変更を担う部分です。

 

そして、trainerにupdaterとepoch(エポック)数を入れます。

エポック数は、学習をループする回数です。

今回5000に設定するため、内部では

 

30個で更新 |
30個で更新 | 1エポック
15個で更新 |

    ・
    ・
    ・

 

という動きをします。

そして、extensions.ProgressBar()で学習の進捗状況をコンソールに表示します。

 

そして.run()で実行です。

 

PyCharmで実行したら、学習過程がこんな感じで表示されました。

 

プログレスバ~がだんだんと伸びていく過程をみて、「俺もここまで来たか」と少し感慨深くもあります。

 

そして学習結果がコチラ!

 

 

 

 

変わっとらんやんけ

なんも変わっとらんやんけ

 

まぁコードの意味は全く変わってないので当たり前ですね。

 

はい、いかがでしたか?

前回ご紹介しましたが、Trainerは古くからChainerをいじくり倒してる方の中には使わない人も多いと聞きました。

使わなくても学習は出来るという前提のもとで、使うも使わないも皆さんの判断にお任せしたいと思います。

 

これで貴方は「トレーナー」です。

「トレーナーにボールをはじかれた!ひとのものをとったらどろぼう!」。

 

それでは。