chainerでニューラルネットワーク簡単実装(初心者向け)

 2018/02/22    機械学習    

当記事では、chainerというPythonのライブラリを用いることでニューラルネットワークが簡単に実装できることをご紹介したいと思います。初心者向けの記事ということで、一番オーソドックスな普通のニューラルネットワークの実装を目指します。そのまま使えるプログラム(Python3系)をご用意いたしました。当記事では例として、こちらのcsvファイル(→sampleNN.csv)を使います。

以下がデータの形です。

     age  blood_pressure  lung_capacity  weight  disease
0     23             117           4000      75        1
1     66             139           4100      75        1
2     96             127           3500      99        0
3     65             123           3500      91        1
4     52             127           3800     107        0
5     67             141           3400     107        1

こちらのデータの前の4列(年齢、血圧、肺活量、体重)から5列目(病気)であるかどうかを予測したいと思います。1が病気有りで、0が病気なしです。(データはこちらで独自に作ったものであり、実在するものではありません。)

学習のサンプルプログラム

以下が、chainerによるニューラルネットワークのサンプルプログラムになります。

import pandas as pd
import numpy as np
 
import chainer
from chainer import training, iterators, optimizers, serializers, Chain
import chainer.functions as F
import chainer.links as L

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


#データの読み込み
df = pd.read_csv("sampleNN.csv")

#学習したモデルを出力するファイル名
resultFn = "sampleNN.model"

#入力層のノードの数
inputNum = len(df.columns)-1

#データの行数を取得
N = len(df)

#データの正規化、各列をその列の最大値で割ることで全て0~1の間にする
df.iloc[:,:-1] /= df.iloc[:,:-1].max()

#学習に関する基本情報の定義
epoch = 400 #学習回数
batch = 1 #バッチサイズ
hiddens = [inputNum,800,400,len(df.iloc[:,inputNum].unique())] #各層のノード数

#学習、検証データの割合(単位:割)
trainSt = 0 #学習用データの開始位置 0割目から〜
trainPro = 8 #学習用データの終了位置 8割目まで
testPro = 10 #検証用データの終了位置 8割目から10割目まで

#ニューラルネットワークの構築。
class MyChain(Chain):

	def __init__(self):
		super(MyChain, self).__init__(
		l1=L.Linear(hiddens[0], hiddens[1]),
		l2=L.Linear(hiddens[1], hiddens[2]),
		l3=L.Linear(hiddens[2], hiddens[3]),
	)

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

def learning():	
	#学習用データと検証用データに分ける
	train_df= df.iloc[0:int(N*trainPro/10),:]
	test_df = df.iloc[int(N*trainPro/10):int(N*testPro/10),:]
	import pdb;pdb.set_trace()
	
	#データの目的変数を落としてnumpy配列にする。
	train_data = np.array(train_df.iloc[:, :-1].astype(np.float32))
	test_data = np.array(test_df.iloc[:, :-1].astype(np.float32))

	#目的変数もnumpy配列にする。
	train_target = np.array(train_df.iloc[:,inputNum]).astype(np.int32)
	test_target =  np.array(test_df.iloc[:,inputNum]).astype(np.int32)

	#ランダムにデータを抽出してバッチ学習する設定
	train = tuple_dataset.TupleDataset(train_data, train_target)
	test = tuple_dataset.TupleDataset(test_data, test_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())
	
	#最適化手法の設定。今回はAdamを使ったが他にAdaGradやSGDなどがある。
	optimizer = optimizers.Adam()
	optimizer.setup(model)
	
	#学習データの割り当てを行う
	updater = training.StandardUpdater(train_iter, optimizer)
	
	#学習回数を設定してtrainerの構築
	trainer = training.Trainer(updater, (epoch, 'epoch'), out='result')

	#trainerの拡張をしておく
	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()

	#モデルの保存
	serializers.save_npz(resultFn, model)

if __name__ == "__main__":	
	learning()
	print("write to " + str(resultFn))

大雑把な説明はコメントアウトしてプログラム内に書いていますが、以下で詳細な説明も加えていきたいと思います。

モデルの設定

まず、クラスMyChain

#ニューラルネットワークの構築。
class MyChain(Chain):

	def __init__(self):
		super(MyChain, self).__init__(
		l1=L.Linear(hiddens[0], hiddens[1]),
		l2=L.Linear(hiddens[1], hiddens[2]),
		l3=L.Linear(hiddens[2], hiddens[3]),
	)

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

の部分ではニューラルネットワークのノード数や活性化関数の設定を行なっています。今回は入力層、中間層2層、出力層の全4層構成で活性化関数にはシグモイド関数を採用しています。

入力層は今回の場合、説明変数が4つなので4、出力層は2値分類なので2としました。中間層は適当に800,400としています。入力層、出力層の数をデータから自動で取得しているため今回は、ノード数に変数を使っていますが、以下のように書いてるのと同じことです。

class MyChain(Chain):

	def __init__(self):
		super(MyChain, self).__init__(
		l1=L.Linear(4, 800),
		l2=L.Linear(800, 400),
		l3=L.Linear(400, 2),
	)

より深いネットワーク(ディープラーニング)にしたい場合は、ここで層の数を増やしましょう。

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

以下の部分で、学習用データと検証用データに分けています。

#学習用データと検証用データに分ける
train_df= df.iloc[0:int(N*trainPro/10),:]
test_df = df.iloc[int(N*trainPro/10):int(N*testPro/10),:]

今回は、データの前8割を学習に使い、残りの2割を検証に使っています。この他にランダムに分ける手法などもあります。データを分けることによって、学習の結果と予測の結果を同時に確認できるというメリットがあります。これは過学習防止などに繋がるので分けるのは必須です。

32ビットのnumpy配列に変換

chinerに学習させるときにデータをそのままでは学習ができません。64ビット配列などにも対応していないので、かならず32ビットのnumpy配列に変換して入力しましょう。

学習結果

上記のプログラムを回した結果、以下のようになりました。

最初は、ほとんど変わらず。

epoch       main/accuracy  validation/main/accuracy
1           0.5625         0.6                       
2           0.58125        0.6                       
3           0.6375         0.6                       
4           0.6            0.6                       
5           0.6375         0.6                       
6           0.60625        0.6                       
7           0.59375        0.6                       
8           0.63125        0.6                       
9           0.5875         0.6                       
10          0.6125         0.6                    

 

学習回数100辺りから、徐々に上がり始め、

91          0.69375        0.55                      
92          0.7125         0.7                       
93          0.7375         0.575                     
94          0.675          0.65                      
95          0.7375         0.775                     
96          0.69375        0.65                      
97          0.69375        0.725                     
98          0.7125         0.7                       
99          0.73125        0.775                     
100         0.73125        0.725                     
101         0.7625         0.675                     
102         0.75           0.675         

 

400回目付近で98%に。

420         0.9625         0.9                       
421         0.91875        0.925                     
422         0.9625         0.9                       
423         0.94375        0.875                     
424         0.975          1                         
425         0.96875        0.975                     
426         0.96875        0.975                     
427         0.96875        0.9                       
428         0.95           0.95                      
429         0.98125        1                         
430         0.98125        0.95  

学習成功まで、だいぶ学習回数を費やしました、、。ニューラルネットワークの学習の効率はデータや入力の仕方、ネットワークの形などによって変わってきます。その辺の設定を調整すれば、より効率の良い学習を目指すことが出来ます。

分類機を別のデータに適用して予測

学習が済んだモデルは、serializers.save_npz("ファイル名", model)で保存することができます。
保存したモデルは、

#学習済みモデルの読み込み
serializers.load_npz('sampleNN.model', model)

で取り出すことが出来、model.predictor()で新しい入力データを与えることで予測に使えます。
以下に予測のサンプルプログラムを掲載いたします。

import pandas as pd
import numpy as np
 
import chainer
from chainer import serializers,Chain
import chainer.functions as F
import chainer.links as L


#モデルの形を設定。こちらは、学習させた時と同じ形にする。
class MyChain(Chain):

	def __init__(self):
		super(MyChain, self).__init__(
		l1=L.Linear(4, 800),
		l2=L.Linear(800, 400),
		l3=L.Linear(400, 2),      
	)

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

model = L.Classifier(MyChain())

#学習済みモデルの読み込み
serializers.load_npz('sampleNN.model', model)

#予測したいデータの読み込み
df = pd.read_csv("sampleNN.csv")
N = len(df) #データの行数

#データの正規化。学習時におこなったものと同じものを行う。
df.iloc[:,:-1] /= df.iloc[:,:-1].max()

#入力データをnumpy配列に変更
data = np.array(df.iloc[:,:-1]).astype(np.float32)

#予測後の出力ノードの配列を作成
outputArray = model.predictor(data).data

#予測結果の配列を作成
ansArray = np.argmax(outputArray,axis=1)

#出力ノードの値のデータフレーム版を作成
outputDF = pd.DataFrame(outputArray,columns=["output_0","output_1"])

#予測結果のデータフレーム版を作成
ansDF = pd.DataFrame(ansArray,columns=["PredictedValue"])

#真の値と、予測結果、出力ノードの値を格納したデータフレームを作成
result = pd.concat([df.disease,ansDF,outputDF],axis=1)

#正解数、正答率を表示
correctCount = len(np.where(result.iloc[:,0] == result.iloc[:,1])[0])
correctRate = correctCount/N
print("データ数:",N)
print("正解数:",correctCount)
print("正答率:",correctRate)

#結果をcsvファイルへ出力
result.to_csv("samplePredict.csv",index=False)

こちらのプログラムではターミナル上に簡単な正答率を出力して、最終的に真の値と予測値をcsvファイルに出力するというものです。

まとめ

いかがでしたでしょうか。Chainerを使えば比較的直感的な記述で簡単にニューラルネットワークを実装できることがお分かりいただけたかと思います。層を深くして、ディープラーニングにしたい場合はMychainで調整すれば簡単に出来ますし、最適化手法の変更も非常に簡単なので、ニューラルネットワークの実装初心者には非常にオススメです。

もし、掲載してるプログラムで何か分からないことがあればコメントいただければと思います!最後までお読みいただきありがとうございました。

  • スポンサーリンク

  • 関連コンテンツ

  • コメントを残す

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

    CAPTCHA