実装関連事項

各種プログラミング言語の基本的な書き方やソフトウェア等の使用方法について.

多層パーセプトロン (multilayer perceptron (MLP)) は数あるニューラルネットワークの学習法の中でも最も基本的な手法.Scikit-learn に実装されている MLP は ChainerTheano 等で実現できるような高度なネットワークを構築することはできないが,専用のライブラリと比較して手軽に MLP を実装できるという利点がある.ただの MLP ではあるが,中間層の数,すわなち深さも自由に変更することができ,一応,ディープラーニングもできる.

学習させるデータの構造

以下のようなデータセットを考える.このデータセットでは,60次元 (アトリビュート) のインプットベクトルに対して0または1のスカラーの値が対応している.インプットベクトルの各アトリビュートはコンマ (,) によって分割されており,インプットベクトルとターゲットベクトル (スカラー) はタブ (\t) によって分割されている.

0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0	0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1	1
0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0	0
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0	1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0	1
0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1	1
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0	1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0	1
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0	0
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0	0
.
.
.

データセット

このような構造のインスタンスが400個含まれるデータをラーニングデータセット (classification_01_learning.txt),411個含まれるデータをテストデータセット (classification_01_test.txt) とする.以下からダウンロードできる.

classification_01_learning.txt

classification_01_test.txt

学習による予測器の生成

このラーニングデータセットを学習して予測器を作るには以下のようにする.これは,Scikit-learnによるロジスティック回帰の学習方法とほぼ同じように書くことができる.ロジスティック回帰における,LogisticRegression() の代わりに MLPClassifier() を用いれば良い.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sklearn
from sklearn.neural_network import MLPClassifier
import numpy as np
np.random.seed(0)

def main():
	# 1. reading data
	xtrain,ttrain=[],[]
	fin=open("classification_01_learning.txt","r")
	for i,line in enumerate(fin):
		line=line.rstrip()
		if line:
			tmp=line.split("\t")
			tmpx=tmp[0].split(",")
			tmpx=[float(j) for j in tmpx]
			tmpt=int(tmp[1])
			xtrain.append(tmpx)
			ttrain.append(tmpt)
	fin.close()
	xtrain=np.asarray(xtrain,dtype=np.float32)
	ttrain=np.asarray(ttrain,dtype=np.int32)
	
	# 2. learning, cross-validation
	diparameter={"hidden_layer_sizes":[(100,),(200,),(300,)],"max_iter":[1000],"batch_size":[20,50,100,200],"early_stopping":[True],"random_state":[123],}
	licv=sklearn.model_selection.GridSearchCV(MLPClassifier(),param_grid=diparameter,scoring="accuracy",cv=5)
	licv.fit(xtrain,ttrain)
	predictor=licv.best_estimator_
	sklearn.externals.joblib.dump(predictor,"predictor_mlp.pkl",compress=True)
	
	# 3. evaluating the performance of the predictor
	liprediction=predictor.predict(xtrain)
	table=sklearn.metrics.confusion_matrix(ttrain,liprediction)
	tn,fp,fn,tp=table[0][0],table[0][1],table[1][0],table[1][1]
	print("TPR\t{0:.3f}".format(tp/(tp+fn)))
	print("SPC\t{0:.3f}".format(tn/(tn+fp)))
	print("PPV\t{0:.3f}".format(tp/(tp+fp)))
	print("ACC\t{0:.3f}".format((tp+tn)/(tp+fp+fn+tn)))
	print("MCC\t{0:.3f}".format((tp*tn-fp*fn)/((tp+fp)*(tp+fn)*(tn+fp)*(tn+fn))**(1/2)))
	print("F1\t{0:.3f}".format((2*tp)/(2*tp+fp+fn)))
	
	# 4. printing parameters of the predictor
	print(sorted(predictor.get_params(True).items()))
	
	# 5. printing importance of each attribute (connection weight)
	print(np.dot(predictor.coefs_[0],predictor.coefs_[1]).T)
	
if __name__ == '__main__':
	main()

以上のプログラムにおいて学習は以下の5ステップからなる.

  1. データの読み込み.
  2. グリッドサーチおよびクロスバリデーションによる学習.
  3. 構築した予測器のラーニングデータセットにおける性能の評価結果の出力.
  4. 予測器のパラメーターの出力.
  5. 入力ベクトルの各特徴量の重要度の出力.

ニューラルネットワークでは入力ベクトルの標準化はしなくて良い場合が多いのでしない.以下の25行目からの部分ではクロスバリデーションによるグリッドサーチを行う.このグリッドサーチによってハイパーパラメーターを決定する.探索するハイパーパラメーターは26行目で設定する.この場合,中間層のユニットの数は100,200,300を探索する.ユニット数は増やせば増やすほど表現力は上がるが,その分,過学習も起りやすくなる.値はタプルで渡すが,これを (100,100,) のように書くと,100ユニットの層を2層つなげることを意味する.入力層および出力層と中間層を併せて4層以上のネットワークで学習した場合,深層学習したことになるので,その場合は一応深層学習といえる.デフォルトでは,活性化関数には正規化線形関数 (ReLU) が,重みの探索法には Adam が実装されている.普通,ここは変化させる必要はない.batch_size とあるのは正確にはミニバッチサイズのことであり,ここを探索するのは結構効く.これが1のとき,逐次更新法,サンプルサイズのとき一括更新法という.27行目は,上の探索範囲で 5-fold クロスバリデーションを行うという書き方.28行目で学習を行い,構築した予測器で最も良いものを29行目で選択,それを30行目でファイルに出力する.

	# 2. learning, cross-validation
	diparameter={"hidden_layer_sizes":[(100,),(200,),(300,)],"max_iter":[1000],"batch_size":[20,50,100,200],"early_stopping":[True],"random_state":[123],}
	licv=sklearn.model_selection.GridSearchCV(MLPClassifier(),param_grid=diparameter,cv=5)
	licv.fit(xtrain,ttrain)
	predictor=licv.best_estimator_
	sklearn.externals.joblib.dump(predictor,"predictor_mlp.pkl",compress=True)

以上のプログラムを実行した結果は以下のようになる.この場合,正確度は0.873,MCC は0.748,F1スコアは0.878である.その下には予測器のパラメーターが出力される.最終的に,ユニットサイズは300となったことがわかる.ミニバッチサイズは20であった.また,その以下に出力されている値は最終的な重み行列の積,すなわち,入力層と中間層をつなぐ最初の重みと中間層と出力層をつなぐ最後の重みの積であり,コネクションウエイトと呼ばれる値.これは,入力ベクトルの60次元の各アトリビュート (変数) の重要度を示す値であり,絶対値が大きいほどその変数が重要,すなわち,最終的な結果に対しての寄与率が高いといえる.

TPR     0.920
SPC     0.825
PPV     0.840
ACC     0.873
MCC     0.748
F1      0.878
[('activation', 'relu'), ('alpha', 0.0001), ('batch_size', 20), ('beta_1', 0.9), ('beta_2', 0.999), ('early_stopping', True), ('epsilon', 1e-08), ('hidden_layer_sizes', 300), ('learning_rate', 'constant'), ('learning_rate_init', 0.001), ('max_iter', 1000), ('momentum', 0.9), ('nesterovs_momentum', True), ('power_t', 0.5), ('random_state', 123), ('shuffle', True), ('solver', 'adam'), ('tol', 0.0001), ('validation_fraction', 0.1), ('verbose', False), ('warm_start', False)]
[[  1.25318664e+00   2.85815050e-03   5.64434360e-02   3.16804828e-03
    7.74855890e-01   1.13785341e-02   4.27880680e-01  -1.89255407e+00
   -1.05283713e-02  -4.98118301e-03   2.06954348e-02   7.80452220e-02
    7.19145132e-02  -1.31569327e-03  -2.29688145e+00   2.00801535e+00
   -1.49212053e-01  -2.02456079e-02  -7.46167549e-02  -1.81896321e+00
    9.77016503e-02  -1.21520260e+00  -2.71283778e-01   1.62826934e+00
   -3.82847428e-01   2.63001545e-01   1.39492131e+00  -2.85827009e-01
   -2.52502324e-01  -7.52585307e-01  -3.91108052e-01  -8.76285428e-01
    4.88379164e-01  -7.41143736e-01  -1.66124562e+00   2.79248410e-01
   -1.70514887e-01   8.79686499e-01  -2.76877387e-01  -7.05565831e-01
    1.68969910e-01   9.34189704e-02   4.32955154e-01  -1.19582273e-01
   -3.82132493e-01   2.40756465e-01   2.58469666e-01  -5.70685807e-01
   -7.86974721e-02   1.85307672e-01  -4.50684182e-01  -2.18500589e-01
    6.86198455e-01   5.12285213e-01  -1.39858707e-01  -6.33169147e-01
    6.70770588e-01  -6.66761702e-01  -4.91530280e-01  -9.75877228e-02]]

構築した予測器を用いたテスト

以上で学習した予測器を読み込んで,学習データとは完全に独立なデータセット上で構築した予測器のテストを行うには以下のように書く.構築した標準化器は,25行目のように書くことで読み込むことができる.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sklearn
import numpy as np
np.random.seed(0)

def main():
	# 1. reading data
	xtest,ttest=[],[]
	fin=open("classification_01_test.txt","r")
	for i,line in enumerate(fin):
		line=line.rstrip()
		if line:
			tmp=line.split("\t")
			tmpx=tmp[0].split(",")
			tmpx=[float(j) for j in tmpx]
			tmpt=int(tmp[1])
			xtest.append(tmpx)
			ttest.append(tmpt)
	fin.close()
	xtest=np.asarray(xtest,dtype=np.float32)
	ttest=np.asarray(ttest,dtype=np.int32)
	
	# 2. reading predictor
	predictor=sklearn.externals.joblib.load("predictor_mlp.pkl")
	
	# 3. evaluating the performance of the predictor on the test dataset
	liprediction=predictor.predict(xtest)
	table=sklearn.metrics.confusion_matrix(ttest,liprediction)
	tn,fp,fn,tp=table[0][0],table[0][1],table[1][0],table[1][1]
	print("TPR\t{0:.3f}".format(tp/(tp+fn)))
	print("SPC\t{0:.3f}".format(tn/(tn+fp)))
	print("PPV\t{0:.3f}".format(tp/(tp+fp)))
	print("ACC\t{0:.3f}".format((tp+tn)/(tp+fp+fn+tn)))
	print("MCC\t{0:.3f}".format((tp*tn-fp*fn)/((tp+fp)*(tp+fn)*(tn+fp)*(tn+fn))**(1/2)))
	print("F1\t{0:.3f}".format((2*tp)/(2*tp+fp+fn)))

if __name__ == '__main__':
	main()

これを実行した結果は以下のようになる.まずまずの性能が出ている.ニューラルネットワーク,特に,誤差逆伝播法は過学習が起りやすいことが問題であるが,この場合,多分,過学習は起こっていない.

TPR     0.810
SPC     0.740
PPV     0.767
ACC     0.776
MCC     0.552
F1      0.788

学習パラメーター一覧

多層パーセプトロンでコントロールすべきハイパーパラメーターには以下のものがある.これを上述のグリッドサーチの部分で変化させることで最も良い予測器を構築する.

パラメーター詳細
hidden_layer_sizes整数からなるタプルを指定.デフォルトは (100,).隠れ層のユニットサイズを入力層側から順に書く.(100,200,) にした場合は,中間層は2層でそれぞれのユニットサイズは100と200.
activation活性化関数を文字列で指定.デフォルトは relu で,identyty,logistic,tanh,relu から選択することができる.それぞれ,線形変換,ロジスティック関数,ハイパボリックタンジェント,正規化線形関数.特に,層が深い場合,ここは変更しない.
solver文字列で指定.lbfgs,sgd,adam から選択.それぞれ,準ニュートン法,普通の勾配降下法,Adam.Adam はそれまでの平均と分散を記録して,次の重みの更新値を最適化する手法.ここも,普通,adam から変更する必要はない.
alpha小数を指定.L2ノルム正則化の係数.
batch_size整数または auto を指定.ミニバッチのサイズ.これは結構強力なハイパーパラメーターなのでグリッドサーチした方が良い.auto の設定は 200 またはサンプルサイズのうち,小さい方をミニバッチサイズとする.
learning_rate文字列を指定.学習率 (ステップサイズ) の指定.探索法 (solver) が sgd のときのみ有効.constant,invscaling,adaptive から選択.constant を指定した場合,learning_rate_init で指定した値に固定.invscaling を指定した場合,learning_rate_init / pow(t, power_t) の式に従って値が減衰.adaptive にした場合,ロスが下がり続ける限り一定,それ以外で,2連続で tol で指定した値分ロスが下がらなかった場合,または early_stopping が True の場合において, tol の値分,バリデーションのスコアが上がらなかったとき,現在の学習率を5で割った値に置換.
max_iter整数を指定.最適解探索の際の最大探索回数を指定.
random_state乱数のタネの指定.何かしらの整数を指定すべき.
shuffleTrue か False で指定.デフォルトは True.サンプルをエポックごとにシャッフルするかどうか.普通,する.
tol小数を指定.トレランスの指定.上の learning_rate および下の early_stopping で用いられる.
learning_rate_init小数を指定.デフォルトは0.001.最初の学習率.sgd を用いるならかなり重要.
power_t小数を指定.上の learning_rate で使用.
verbose整数を指定.モデル構築の過程のメッセージを出すかどうか.デフォルトは0.
warm_startTrue または False を指定.デフォルトは False.ここに True を設定すると既にフィットしたモデルに学習を追加することができる.
momentum小数を指定.慣性項の指定.最適解の探索の際に,慣性の法則に従うように,これまで動いていた方向へ動きやすくする.探索法 (solver) が sgd のときのみ有効.これを利用した最急降下法のとこをモーメンタムSGDという.
nesterovs_momentumTrue または False で指定.ネストロフのモーメンタムを利用するかどうか.
early_stoppingTrue または False で指定.早期停止をするかどうかの指定.ここでの早期停止は,バリデーションスコアが2エポック連続で上の tol で指定した値分改善しなかった場合,学習ループを打ち切る.このとき,バリデーションデータセットは入力のラーニングデータセットの10%が抽出されて作られる.残りの90%はトレーニングデータセット.
validation_fraction小数を指定.デフォルトは0.1.上の early_stopping でバリデーションデータセットとして用いる全ラーニングデータセットに対する割合を指定.
beta_10以上1未満の小数を指定.デフォルトは0.9.Adam の β1.探索法 (solver) が adam のときのみ有効.多くの場合,変更して良いことはない.
beta_20以上1未満の小数を指定.デフォルトは0.999.Adam の β2.探索法 (solver) が adam のときのみ有効.多くの場合,変更して良いことはない.
epsilon小数を指定.デフォルトは1e-8.Adam のイプシロンのこと.探索法 (solver) が adam のときのみ有効.
  1. Olden JD et al., An accurate comparison of methods for quantifying variable importance in artificial neural networks using simulated data. Ecological Modeling, 178:389-397,2004.
Hatena Google+