TECH MEMO

技術的な内容に関する個人的なメモ

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)

f:id:curlyst:20160706233650p:plain

左図が学習時の誤差と精度、右図が評価時の誤差と精度になります。学習時は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)

f:id:curlyst:20160707000319p:plain

Dropoutを利用した場合に比べて、学習時の収束は早くなっていますが、評価時の誤差を見ると、一旦減った後に急激に増加しており、過学習を起こしていることが分かります。

まとめ

今回はWindowsで使いたいというモチベーションがあったため、Chainerを用いて多層パーセプトロンを実装し、Irisデータを分類しました。また、Dropoutの有無による、誤差・精度の挙動の違いも見てみました。

ただ、Deep Learningのライブラリは他にもたくさんあり、最近、Keras(+Tensorflow)がなんか良い感じみたいなので、次は(いつになるかわかりませんが。。。)Kerasも利用してみたいなぁ、と思っています。