sklearnのMLPClassifierとMNISTデータでディープラーニング!

MLPClassifierとは

MLPClassifierはMulti-layer Perceptron classifierの略で、多層パーセプトロンによる分類器です。交差エントロピー誤差関数を、L-BFGS*1準ニュートン法に属すBFGS法の一種。または確率的勾配降下法を使用して最適化します。

簡単に言えば、sklearnでディープラーニングを実装する夢を叶えてくれるものです。

MLPClassifierのパラメータ

MLPClassifierではパラメータとして何が設定できるのか覗いてみます。

MLPClassifier(
    hidden_layer_sizes=(100,),
    activation='relu',
    solver='adam',
    alpha=0.0001,
    batch_size='auto',
    learning_rate='constant',
    learning_rate_init=0.001,
    power_t=0.5,
    max_iter=200,
    shuffle=True,
    random_state=None,
    tol=0.0001,
    verbose=False,
    warm_start=False,
    momentum=0.9,
    nesterovs_momentum=True,
    early_stopping=False,
    validation_fraction=0.1,
    beta_1=0.9,
    beta_2=0.999,
    epsilon=1e-08,
    n_iter_no_change=10,
)

このように、22個のパラメータを引数として受け取ることができます。では、個々に説明していきます。

hidden_layer_sizes

デフォルト値:hidden_layer_sizes=(100,)

中間層のサイズを設定します。デフォルトでは、ニューロンを100個もつ中間層が1つのみに定義されています。つまり、図1のような三層ニューラルネットワークです。

図1 デフォルトのMLP

です。

中間層が3つ(各中間層のニューロン数が100個、200個、100個)の5層ニューラルネットワークを設定したいときは、

hidden_layer_sizes=(100, 200, 100)

のように記述します。

図2 設定したいMLPの例

activation

デフォルト値:activation=’relu’

中間層の活性化関数を設定します。デフォルトでは、ランプ関数(ReLU)が設定されています。引数として渡せる活性化関数は以下の4種類です。

・identity:恒等関数( \(f(x) = x\) )
・logistic:シグモイド関数(\(f(x) = \frac{1}{1 + e^{-x}}\) )
・tanh:双曲線正接関数
・relu:ランプ関数(\(f(x) = max(0, x)\) )

問題点は、中間層の層ごとに活性化関数を違うものにできないことです。いくら層を深くしても、設定できる活性化関数の種類は1つのみです。

solver

デフォルト値:solver=’adam’

重みの最適化手法を設定します。ここで設定できる最適化手法は、

・lbfgs:準ニュートン法に属すBFGSの一種
・sgd:確率的勾配降下法
・adam:確率的勾配降下法にモーメントなる動きをつけたもので現在の主流

比較的大きなデータセットではadamを、小さなデータセットではlbfgsを設定するとうまくいくことが知られています。

alpha

デフォルト値: alpha=0.0001

L2正則化項が誤差関数に与える影響の大きさを調節する正則化パラメータです。過学習に陥るときは、小さな入力値の変化で出力値が大きく変化する状態で、重みベクトルのユークリッドノルムが大きいことが知られています。そのため、重みベクトルのユークリッドノルムが大きくならないように、L2正則化項を追加し、それが与える影響を調節します。

batch_size

デフォルト値:batch_size=’auto’

最適化時のバッチサイズを決めます。デフォルトではautoに設定されています。autoの場合、batch_size=min(200, n_samples)、つまり、バッチサイズは最も多くて200です。最適化手法としてL-BFGSを使用した場合、ミニバッチを使用しません。

learning_rate

デフォルト値:learning_rate=’constant’

学習率の変化を設定します。学習率は、引数learning_rate_initに渡しましが、デフォルトでは、それが定数として設定されています。設定できるのは、

・constant:学習率は定数です。
・invscaling:effective_learning_rate = learning_rate_init / pow(t, power_t) のように、時間とともに学習率が小さくなります。ここで、power_tはtの指数部です。pow(x, y)はxのy乗を返す関数なので、 pow(t, power_t)は、\(t^{power_t}\)となり、学習率が、時間の累乗に反比例して小さくなります。
・adaptive:誤差関数が減少し続ける限り、学習率を一定に保ちます。しかし、2回連続するepochで、訓練誤差が最低でもtolより減少しなかった場合と、検証スコアが上昇が少なくともtolより上昇しなかった場合に、現在の学習率を5で割ったものに更新します。

learning_rate_init

デフォルト値:learning_rate_init=0.001

学習率を設定します。これは、最適化手法としてSGDかadamが選ばれたときにのみ使用されます。

power_t

デフォルト値:power_t=0.5

learning_rateの’invscaling’において、時間tの指数部を設定します。この値が大きいと、学習率が急速に小さくなります。これは、SGDが最適化手法として選ばれたときのみ使用されます。

max_iter

デフォルト値:max_iter=200

エポック数を指定します。デフォルトではエポックが最大で200に設定されています。途中で打ち切りが生じる場合を考慮すると、すべてのエポックが最後まで実行されるとは限りません。

shuffle

デフォルト値:shuffle=True

学習時のサンプルを毎回シャッフルするかどうかを設定します。SGDとAdamの場合にのみ使用されます。

random_state

デフォルト値:random_state=None

基本的にはランダム値のシードを設定します。しかし、整数を入力した場合は、random_stateはシードを表します。一方で、RandamStateのインスタンスを入力した場合、random_stateは乱数ジェネレータになります。デフォルトのNoneではnp.randomによるRandomStateのインスタンスが使用されます。

tol

デフォルト値:tol=0.0001

学習がある程度進んだところで、学習率を変化させたり打ち切らせるときに、使用されるパラメータです。

verbose

デフォルト値:verbose=False

学習途中の進捗状況を標準出力に出力するかどうか設定します。

warm_start

デフォルト値:warm_start=False

学習を何度も行いたいとき、つまり、fitを何度も呼び出すときに、呼び出す以前に行った重みを引き継ぐかどうかを設定します。

momentum

デフォルト値:momentum=0.9

勾配降下法による学習時にモーメンタムをどの程度反映させるかを0~1の間で設定します。

nesterovs_momentum

デフォルト値:nesterovs_momentum=True

モーメンタム法の一種である、Nesterovの加速法を有効化するかどうかを設定します。

early_stopping

デフォルト値:early_stopping=False

学習を途中で打ち切るかどうかを設定します。

validation_fraction

デフォルト値:validation_fraction=0.1

上のearly_stoppingがTrueのとき、早く打ち切るかどうかを判断するために使用される検証用データの割合を設定します。

beta_1

デフォルト値:beta_1=0.9

Adamで使用します。

beta_2

デフォルト値:beta_2=0.999

Adamで使用します。

epsilon

デフォルト値:epsilon=1e-08

Adamで使用します。

n_iter_no_change

デフォルト値:n_iter_no_change=10

tolの条件を満たさないエポックの最大数を設定します。

MLPClassifierの使い方

驚くべきことに、ほとんどのパラメータがあらかじめ設定されていることが分かります。主にこちらで設定すべき項目は

・hidden_layer_sizes:各層のニューロン数
・activation:活性化関数
・max_iter:エポック数
・verbose:学習の進捗の表示の有無
・warm_start:fitを実行するときに重みを引き継ぐかどうか

でしょう。では、中間層が3層(各層のニューロン数は100個とする)で、活性化関数を全てrelu、学習の進捗状況を表示させ、重みを引き継がせたい場合を考えてみましょう。このとき、

mlpclf = MLPClassifier(hidden_layer_sizes=(100, 100, 100), activation='relu', verbose=True, warm_start=True)

のようにインスタンス化します。この後、このインスタンスに対してメソッドを使用して学習、予測、正解率などを計算していきます。各メソッドについて説明します。

predict_proba()

array_likeかmatrixである入力を受けます。形は(n_samples, n_features)です。つまり、個々の入力データは行ベクトルです。出力はarray-likeで形は(n_samples, n_classes)です。各クラスに属する確率を出力します。

set_params()

略。

fit()

入力Xをarray_likeまたは行列(n_samples, n_features)、教師データyをarray-likeまたは行列、つまり (n_samples,) または (n_samples, n_outputs)とします。このとき、mlpclf.fit(X, y)のように、モデルのトレーニングを行います。

predict()

array_likeかmatrixである入力を受けます。形は(n_samples, n_features)です。つまり、個々の入力データは行ベクトルです。出力はarray-likeで形は(n_samples, n_classes)です。推定されるクラスのみ1になります。

get_params()

パラメータが確認できます。例えば、

{'activation': 'relu',
 'alpha': 0.0001,
 'batch_size': 'auto',
...
 'validation_fraction': 0.1,
 'verbose': True,
 'warm_start': True}

が取得できます。

predict_log_proba()

略。

score()

テスト用データとテスト用教師データを入力することで、正解率を簡単に得ることができます。例えば

score(X, y)

のように使用します。

MNISTデータを学習させる

sklearnの深層学習クラスのMLPClassifierを使用してMNISTデータを学習させてみます。

MNISTデータを読み込んで訓練データとテストデータに分ける

まずはMNISTデータを読み込みます。sklearnでうまく読み込めなかったため、kerasを使用して読み込みました。

from keras.datasets import mnist
from keras import utils                 #OneHot表現に変更する関数
(x_train, y_train), (x_test, y_test) = mnist.load_data()

ニューラルネットワークに入力する形(各サンプリングは行ベクトル)に変更します。また、ピクセル値は最大255ですが、ニューロンに入力できる値は0から1ですので255で正規化します。

X_train = x_train.reshape(60000, 784) / 255
X_test = x_test.reshape(10000, 784) / 255

教師信号をOne-Hot表現にします。

Y_train = utils.to_categorical(y_train, 10)
Y_test = utils.to_categorical(y_test, 10)

学習させる

実際にディープニューラルネットワークを定義し、深層学習させます。学習の進捗状況などはhistoryに記録させました。

mlpclf = MLPClassifier(hidden_layer_sizes=(100, 100, 100), activation='relu', verbose=True, warm_start=True)
history = mlpclf.fit(X_train, Y_train)

進捗状況は、以下のような感じに出力されます。

[pyton]
Iteration 1, loss = 1.09724523
Iteration 2, loss = 0.32702274
Iteration 3, loss = 0.22946111

Iteration 47, loss = 0.00765463
Iteration 48, loss = 0.00445658
Iteration 49, loss = 0.00384859
training loss did not improve more than tol=0.000100 for 10 consecutive epochs. Stopping.
[/python]

評価する

テストデータを使用して正解率を計算すてみます。

y_proba = mlpclf.predict_proba(X_test) #mlpclf.predict(X_test)でもOK
y_esti = np.argmax(y_proba, 1)
y_real = np.argmax(Y_test, 1)
acc = np.sum(y_esti == y_real)/len(y_esti) 
print(str(acc) + '%')

より、出力は0.9757%となりました。

実はもっと簡単に正解率を求めることができます。

mlpclf.score(X_test, Y_test)

により求められます。

訓練誤差の変化を可視化しました。

plt.plot(history.loss_curve_)
plt.xlabel('Iteration')
plt.ylabel('loss')
plt.grid(True)
図3 訓練誤差の変化

まとめ

機械学習ライブラリscikit-learnは統計的な機械学習手法に特化しているイメージですが、そこに取り入れられた深層学習クラスの一つであるMLPClassifier()はとても簡単にディープラーニングできることが分かりました。一方で、個々のパラメータの設定等は柔軟性に欠けていることが事実です。もちろん、MLPClassifierで事足りるなら問題ないですが、事足りない学習対象であっても、深層学習がどの程度その学習対象に有用なのか、確かめるときに使いやすい便利なクラスでした。

Follow me!

References   [ + ]

1. 準ニュートン法に属すBFGS法の一種。