Deep LearningでIrisデータを分類し、Dropoutの効果を確かめる
Deep Learningはなんとなく知ってはいたけど、実際に動かしてみたことがなかったので、まずはIrisデータを分類してみました。ついでに、Dropoutの有無で学習結果がどう違うのか確認しました。
なお、Deep Learningの実装にはChainerを利用しまいた。本当はTensorFlowとどちらを使おうか迷ったのですが、実際にDeep Learningを使うとしたら所諸事情によりWindowsで実行する必要があるので、Windowsにインストールしている記事*1のあったChainerを選択しました。
(TensorFlowもWindows上で動かす方法はあるみたいなのですが、Dockerを入れるなど、多少手間が必要そうなので。。。)
1. ライブラリのインポート
Chainerの他に、データの読み込みをPandas、標準化をScipy、グラフ描画をmatplotlibで実施したので、それらもインポートします。
import pandas as pd import numpy as np import scipy as sp import matplotlib.pyplot as plt from sklearn.cross_validation import train_test_split from sklearn import datasets import chainer from chainer import Variable, FunctionSet, optimizers import chainer.functions as F import chainer.links as L
2. データの準備
Irisデータはscikit-learnのdatasetsを使うと簡単に読み込めます。ここで説明変数の標準化を行った後に、学習用とテスト用にデータを分割しておきます。なお、変数の型がそのままだと後でChainerに怒られるので、目的変数はnp.int32、説明変数はnp.float32に変換しておきます。
# Irisデータの読み込み iris = datasets.load_iris() # 目的変数 target_data = iris.target.astype(np.int32) # 説明変数 tmp_predictor_data = sp.stats.zscore(iris.data, axis=0) # 標準化 predictor_data = tmp_predictor_data.astype(np.float32) # 学習データとテストデータに分割 test_size = 30 data_num = len(target_data) predictor_train, predictor_test, target_train, target_test = train_test_split(predictor_data, target_data, test_size=test_size, stratify=target_data) train_data_num = len(target_train) test_data_num = len(target_test)
3. モデルの定義
いよいよここからChainerを使ってDeep Learning(多層パーセプトロン)を実装します。まずはモデルの定義をするのですが、chainer.Chainを継承して作成します。
作成するクラスでは、__init__()と__call__()を定義します。
- __init___() :多層パーセプトロンの各層を定義します。今回は中間層が2層のパーセプトロンを利用するため、中間層2つと出力層1つを定義しました。それぞれ全結合のため、Linear()を用いています。
- __call__(): 順伝搬の計算処理(多層パーセプトロンの構造)を定義します。学習時にDropoutを利用するため、2つの中間層の計算の際にdropout()を用いています。また、各層の活性化関数はReLUを利用しました。
上記2つの関数の他に、学習時のDropoutの利用有無を切り替えるために、(少し冗長ですが)2つのフラグtrian、drop_outを定義しました。
class MultiLayerPerceptron(chainer.Chain): ''' モデルの定義(多層パーセプトロン) ''' def __init__(self, input_dim, n_units, output_dim, train=True, drop_out_ratio=0.3): ''' コンストラクタ ''' # 多層パーセプトロンの各層の定義 super(MultiLayerPerceptron, self).__init__( l1=F.Linear(input_dim, n_units), l2=F.Linear(n_units, n_units), l3=F.Linear(n_units, output_dim) ) # 学習の場合:True self.__train = train # drop outの実施有無 self.__drop_out = True # drop outの比率 self.drop_out_ratio = drop_out_ratio def __call__(self, x): ''' 順伝搬(foward)計算 ''' # 入力層から順に計算(多層パーセプトロンの構造) drop_out = self.__train and self.__drop_out h1 = F.dropout(F.relu(self.l1(x)), train=drop_out, ratio=self.drop_out_ratio) h2 = F.dropout(F.relu(self.l2(h1)), train=drop_out, ratio=self.drop_out_ratio) y = self.l3(h2) return y # 学習の場合;True def __get_train(self): return self.__train def __set_train(self, train): self.__train = train train = property(__get_train, __set_train) # Dropoutを使用する場合:True def __get_drop_out(self): return self.__drop_out def __set_drop_out(self, drop_out): ''' drop outフラグの設定 ''' self.__drop_out = drop_out drop_out = property(__get_drop_out, __set_drop_out)
4. 学習クラスの定義
学習を行うクラスでは、主に以下の2つの関数を定義します。
- learn():ミニバッチで学習を行います。各バッチデータで学習を行い、誤差(交差エントロピー関数の値)と精度を取得してリストに格納して、最後に平均値に直して呼び出し元に返します。
- evaluate():テストデータを用いた評価用の関数。テストデータに対して分類を行い、誤差と精度を呼び出し元に返します。
class MiniBatchLearner: ''' ミニバッチによる学習を行うクラス ''' def __init__(self, optimizer, epoch_num, batch_size): ''' コンストラクタ ''' #--- インスタンス変数 --- # optimizer self.__optimizer = None # 学習の繰り返し数 self.__epoch_num = None # バッチサイズ self.__batch_size = None # 学習データ、テストデータの誤差、精度(正答率) self.__train_loss = None self.__train_acc = None self.__test_loss = None self.__test_acc = None # パラメータの初期化 self.set_param(optimizer, epoch_num, batch_size) self.__init_loss_acc() def set_param(self, optimizer, epoch_num, batch_size): self.__optimizer = optimizer self.__epoch_num = epoch_num self.__batch_size = batch_size def __init_loss_acc(self): self.__train_loss = [] self.__train_acc = [] self.__test_loss = [] self.__test_acc = [] def learn(self, model, predictor_train_data, target_train_data, drop_out=True): ''' 学習の実施 ''' self.__init_loss_acc() # 学習データのインデックス(ランダム) train_data_num = len(target_train_data) perm = np.random.permutation(train_data_num) sum_accuracy = 0 sum_loss = 0 # 学習モード model.predictor.train = True model.predictor.drop_out = drop_out # バッチサイズごとに学習 for idx in xrange(0, train_data_num, self.__batch_size): predictor_batch = chainer.Variable(predictor_train_data[perm[idx:idx+self.__batch_size]]) target_batch = chainer.Variable(target_train_data[perm[idx:idx+self.__batch_size]]) # 勾配を初期化 model.zerograds() # 順伝播させて誤差と精度を算出 loss = model(predictor_batch, target_batch) acc = model.accuracy # 誤差逆伝播で勾配を計算 loss.backward() # 更新 self.__optimizer.update() self.__train_loss.append(loss.data) self.__train_acc.append(acc.data) sum_loss += float(loss.data) * len(target_batch) sum_accuracy += float(acc.data) * len(target_batch) # 訓練データの誤差と、正解精度を返す train_mean_loss = sum_loss / train_data_num train_mean_acc = sum_accuracy / train_data_num return train_mean_loss, train_mean_acc def evaluate(self, model, predictor_test_data, target_test_data): sum_accuracy = 0 sum_loss = 0 # 評価モード model.predictor.train = False predictor_batch = chainer.Variable(predictor_test_data) target_batch = chainer.Variable(target_test_data) # 順伝播させて誤差と精度を算出 loss = model(predictor_batch, target_batch) acc = model.accuracy test_data_mum = len(target_test_data) sum_loss = float(loss.data) * test_data_mum sum_accuracy = float(acc.data) * test_data_mum # テストデータでの誤差と正解精度を返す return float(loss.data), float(acc.data)
また、今回は後々でのコードの見易さのために、以下の2つの関数を作成しました。
- train_and_eval():学習とテストデータによる評価を指定した回数(n_epoch)だけ実行。各回での誤差と精度は配列に格納しておき、戻り値として呼び出し元に返す。
- draw_loss_and_acc():各回での学習時の誤差と精度、評価時の誤差と精度を描画。
# 誤差、精度を格納 train_mean_loss_list = [] train_mean_acc_list = [] test_mean_loss_list = [] test_mean_acc_list = [] def train_and_eval(batchsize, n_epoch, print_epoch, drop_out, drop_out_ratio, intermediate_layer_num): train_mean_loss_list = [] train_mean_acc_list = [] test_mean_loss_list = [] test_mean_acc_list = [] # モデルのインスタンス定義 tmp_model = MultiLayerPerceptron(4, intermediate_layer_num, 3, drop_out_ratio=drop_out_ratio) mlp_model = L.Classifier(tmp_model) # Optimizerの定義 optimizer = optimizers.Adam() optimizer.setup(mlp_model) # 学習クラスのインスタンス定義 mb_learner = MiniBatchLearner(optimizer=optimizer, epoch_num=n_epoch, batch_size=batchsize) # Learning loop for epoch in xrange(1, n_epoch+1): if epoch % print_epoch == 0: print 'epoch', epoch # training train_mean_loss, train_mean_acc = mb_learner.learn(mlp_model, predictor_train, target_train, drop_out) if epoch % print_epoch == 0: print 'train mean loss={}, accuracy={}'.format(train_mean_loss, train_mean_acc) # evaluation test_mean_loss, test_mean_acc = mb_learner.evaluate(mlp_model, predictor_test, target_test) if epoch % print_epoch == 0: print 'test mean loss={}, accuracy={}'.format(test_mean_loss, test_mean_acc) train_mean_loss_list.append(train_mean_loss) train_mean_acc_list.append(train_mean_acc) test_mean_loss_list.append(test_mean_loss) test_mean_acc_list.append(test_mean_acc) return train_mean_loss_list, train_mean_acc_list, test_mean_loss_list, test_mean_acc_list def draw_loss_and_acc(train_mean_loss_list, train_mean_acc_list, test_mean_loss_list, test_mean_acc_list, xlabel="epoch", set_ylim=True): fig = plt.figure(figsize=(14, 5)) # train ax1 = fig.add_subplot(1,2,1) plt.plot(range(len(train_mean_loss_list)), train_mean_loss_list) plt.plot(range(len(train_mean_acc_list)), train_mean_acc_list) plt.legend(["train_loss","train_acc"],loc=1) plt.title("Loss / Accuracy of Iris class recognition.") plt.xlabel(xlabel) plt.ylabel("loss/accracy") if set_ylim: ax1.set_ylim([0, 1.2]) plt.grid() # test ax2 = fig.add_subplot(1,2,2) plt.plot(range(len(test_mean_loss_list)), test_mean_loss_list) plt.plot(range(len(test_mean_acc_list)), test_mean_acc_list) plt.legend(["test_loss","test_acc"],loc=1) plt.title("Loss / Accuracy of Iris class recognition.") plt.xlabel(xlabel) plt.ylabel("loss/accracy") if set_ylim: ax2.set_ylim([0, 1.2]) plt.grid() plt.show()
5. 学習の実施と結果の描画
それでは準備が整ったので、実際にIrisデータを用いて学習を行い、結果(学習・評価時の誤差と精度)を描画します。今回は前提として、以下のパラメータを用いました。
# 基本パラメータ batchsize = 10 # 確率的勾配降下法で学習させる際の1回分のバッチサイズ n_epoch = 2000 # 学習の繰り返し回数 print_epoch = n_epoch / 2 # printするepoch数 drop_out_ratio = 0.3 # drop_outの比率 intermediate_layer_num = 8 # 中間層のノード数
まずDropoutを利用して、学習を実施します。
# drop outあり drop_out = True train_mean_loss_list, train_mean_acc_list, test_mean_loss_list, test_mean_acc_list = train_and_eval(batchsize, n_epoch, print_epoch, drop_out, drop_out_ratio, intermediate_layer_num) draw_loss_and_acc(train_mean_loss_list, train_mean_acc_list, test_mean_loss_list, test_mean_acc_list)
左図が学習時の誤差と精度、右図が評価時の誤差と精度になります。学習時はDropoutを行っているため、誤差・精度ともに値が振動していますが、学習を重ねていくうちに値が小さくなっています。
評価時については、700〜800回程度までは誤差・精度ともに値は徐々に小さくなっていますが、700〜800回くらいを超えたあたりから徐々に誤差が増え始めており、若干、過学習気味になっています。
次にDropoutなしで学習します。
# drop outなし drop_out = False train_mean_loss_list, train_mean_acc_list, test_mean_loss_list, test_mean_acc_list = train_and_eval(batchsize, n_epoch, print_epoch, drop_out, drop_out_ratio, intermediate_layer_num) draw_loss_and_acc(train_mean_loss_list, train_mean_acc_list, test_mean_loss_list, test_mean_acc_list)
Dropoutを利用した場合に比べて、学習時の収束は早くなっていますが、評価時の誤差を見ると、一旦減った後に急激に増加しており、過学習を起こしていることが分かります。