【初めて使う人向け】Chainerでニューラルネットを学習する手順を整理してみた

概要

Preferred Networks社が提供する深層学習ツールである chainerを使ってニューラルネットワークを学習する手順を整理しました。大きな流れは以下です。

  1. Chainerが使える形にデータを整形する
  2. 学習するニューラルネットワークのモデルを定義する
  3. その他の学習条件・パラメータをセットし、trainerを構築する
  4. 学習を実行し、学習結果を確認する

なお、chainer自体は以下のコマンドで簡単にインストールできます。

pip install chainer

また、以下のモジュールがimportされているものとします。

import pandas as pd
import numpy as np
import sklearn.preprocessing as sp

import chainer
from chainer import cuda, Function, gradient_check, report, training, utils, Variable
from chainer import datasets, iterators, optimizers, serializers
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L
from chainer.training import extensions
from chainer.functions.loss.mean_squared_error import mean_squared_error
from chainer.datasets import tuple_dataset

Chainerが使える形にデータを整形する

具体的には以下の状態を満たしていることを指します。

  1. データが全てNumpyのArray型になっている
  2. データが全て数値(int又はfloat)型になっている
  3. データが全て32bit型になっている
  4. データが学習データと検証データに分かれている
  5. データが説明変数と目的変数に分かれている

NumpyのArray型にする

元データがlist型やpandasのDataFrame型になっている場合、np.arrayのコマンドを使ってarray型に変更しましょう。

data = np.array(hoge)

全て数値(int又はfloat)型にする

文字列データが混じっている場合は数値にエンコードしておきましょう。以下ではDataFrameの1列目が文字列データだったとして、数値データにエンコードするスクリプトを示しています。

le = sp.LabelEncoder()
le.fit(df[df.columns[0]].unique())
df[df.columns[0]] = le.transform(df[df.columns[0]])

scikit-learnのpreprocessingについては以下記事も参考にしてみてください。

Sklearnのpreprocessingの全メソッドを解説


数値データを32bit型にする

どういう仕様か、chainerは64bitのデータ型を扱ってくれません。astypeを使って32bit型に置き換えましょう。

#float型の場合
data = data.astype(np.float32)
#int型の場合
data = data.astype(np.int32)

学習データと検証データに分ける

機械学習をする上では基本的な話ですが、学習データと検証データに分けておきましょう。ランダムに分割したかったら、numpy.random.choice等を使えばいいと思います。

説明変数と目的変数に分ける

説明変数の集合(data)と目的変数(target)に分けておきましょう。所謂Scikit-learnのBunch型にしておくと尚いいと思います。DataFrame型をBunch型にする方法については以下記事を参考にしてください。

PandasのデータフレームをScikit-learnの入力データに変換する方法(2.コーディング)


学習するニューラルネットワークのモデルを定義する

Chainを継承したクラスを作り、initで構造を、callで計算内容を定義します。以下に例を示します。

class MyChain(Chain):

    def __init__(self):
        super(MyChain, self).__init__(
            l1=L.Linear(4, 5),
            l2=L.Linear(5, 5),
            l3=L.Linear(5, 3)           
        )

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

その他の学習条件・パラメータをセットし、trainerを構築する

具体的には以下を設定します。

  1. データオブジェクトの作成と抽出オプションの設定
  2. モデルと最適化関数の設定
  3. 学習回数の設定とtrainer構築

trainerの構成は以下のイメージです。

データオブジェクトの作成と抽出オプションの設定

まずtuple_datasetを使ってchainerで使えるオブジェクトを作ります。

train = tuple_dataset.TupleDataset(data, target)

その後抽出オプションを指定してイテレータ型にします。こうすることで、trainerをrunしたときに自動的にランダムにデータを抽出してバッチ学習がすることができます。(これは便利!)

train = iterators.SerialIterator(train, batch_size=batch, shuffle=True, repeat=True)

モデルと最適化関数の設定

まず定義したモデルを使う宣言をします。説明変数がクラス型の時はClassifierを指定すると適切に処理されます。

model = L.Classifier(MyChain())

次に最適化関数を指定します。ここではSGDを指定しています。こうすることでrunの度に自動で最適化計算が走ります。さらにこれにモデルを割り当てます。

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

そして、学習データを割り当てます。

updater = training.StandardUpdater(train_iter, optimizer)

学習回数の設定とtrainer構築

最後に学習回数(epoch)を指定すればtrainer構築完了です。

trainer = training.Trainer(updater, (epoch, 'epoch'), out='result')

学習を実行し、学習結果を確認する

以下を実行することで自動的に指定回数の学習が行われます。

trainer.run()

またこのとき、extendを指定しておくと、モデルの保存や精度確認等が行えます。

#精度確認
trainer.extend(extensions.Evaluator(test_iter, model))
#レポート出力
trainer.extend(extensions.LogReport())
#レポート内容
trainer.extend(extensions.PrintReport(['epoch', 'main/accuracy', 'validation/main/accuracy']))
#プログレスバー出力
trainer.extend(extensions.ProgressBar())
#モデルの保存
trainer.extend(extensions.snapshot(trigger=(1, 'epoch'), filename='snapshot_iter_{0:04d}', mode='a'))

Irisデータで実証

irisのデータを使って検証してみました。実行するとコンソール上に以下のような画面が出力されるはずです。今回の例では学習データと検証データに同じデータを指定しているという条件ですが、50回の学習で精度33%→90%程度まで上昇しており、確かにニューラルネットワークが学習されていることが確認できました。

import glob
import pickle

import pandas as pd
import numpy as np
import sklearn.preprocessing as sp
import random

import chainer
from chainer import cuda, Function, gradient_check, report, training, utils, Variable
from chainer import datasets, iterators, optimizers, serializers
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L
from chainer.training import extensions
from chainer.functions.loss.mean_squared_error import mean_squared_error
from chainer.datasets import tuple_dataset

def set_random_seed(seed):
    # set Python random seed
    random.seed(seed)

    # set NumPy random seed
    np.random.seed(seed)
    
class MyChain(Chain):

    def __init__(self):
        super(MyChain, self).__init__(
            l1=L.Linear(4, 5),
            l2=L.Linear(5, 5),
            l3=L.Linear(5, 3)           
        )

    def __call__(self, x):
        h1 = F.sigmoid(self.l1(x))
        h2 = F.sigmoid(self.l2(h1))
        o = self.l3(h2)
        return o
    
if __name__ == "__main__":
    
    epoch = 50
    batch = 1
    
    df = pd.read_csv("iris.csv")
    data = np.array(df.iloc[:, :-1].astype(np.float32))
#    target = sp.label_binarize(df[df.columns[4]], classes=df[df.columns[4]].unique()).astype('float32')
    le = sp.LabelEncoder()
    tmp = df[df.columns[4]].unique()
    tmp.sort()
    le.fit(tmp)
    target = le.transform(df[df.columns[4]]).astype('int32')
    set_random_seed(0)
    
    train = tuple_dataset.TupleDataset(data, target)
    test = tuple_dataset.TupleDataset(data, target)
    
    train_iter = iterators.SerialIterator(train, batch_size=batch, shuffle=True)
    test_iter = iterators.SerialIterator(test, batch_size=batch, repeat=False, shuffle=False)
    
    model = L.Classifier(MyChain()) #, lossfun=mean_squared_error
    optimizer = optimizers.SGD()
    optimizer.setup(model)
    
    updater = training.StandardUpdater(train_iter, optimizer)
    trainer = training.Trainer(updater, (epoch, 'epoch'), out='result')
    
    trainer.extend(extensions.Evaluator(test_iter, model))
    trainer.extend(extensions.LogReport())
    trainer.extend(extensions.PrintReport(['epoch', 'main/accuracy', 'validation/main/accuracy']))
    trainer.extend(extensions.ProgressBar())
    trainer.extend(extensions.snapshot(trigger=(1, 'epoch')))
    trainer.run()

ソースコードから、MyChainの部分をより細かく設定するだけでディープラーニングにできることはご理解いただけると思います。実に100行足らずであり、内容もとても合理的と思いますので、私的にはchainerはとても使い易いソフトと考えています。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA