実装関連事項

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

サポートベクターマシン (support vector machine (SVM)) はノンパラメトリックな機械学習法の代表格である.ものすごく性能が良い.サポートベクターマシンの主な特徴は,マージン最大化法とカーネル関数の利用のふたつ.ニューラルネットワークで用いられる最適点の探索法であるバックプロパゲーション法と比較して,マージン最大化法は理論的には過学習を起こさない.マージンを最大化するから.加えて,手持ちのデータを完全に分離できるような超高次元空間があるとして,そこにデータを投射する.しかしそのような投射するための関数がどんなのか分からないし,計算がとても重くなるので,それを直接計算する代わりに,代替可能であることが数学的に証明されているカーネルと呼ばれる関数を使った計算を代わりに行う.このような計算をコマンド一発で可能にしているのが Scikit-learn の関数.

学習させるデータの構造

以下のようなデータセットを考える.このデータセットでは,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による決定木の学習方法とほぼ同じように書くことができる.決定木における,DecisionTreeClassifier() の代わりに SVC() を用いれば良い.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sklearn
from sklearn.svm import SVC
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={"kernel":["rbf"],"gamma":[10**i for i in range(-4,2)],"C":[10**i for i in range(-2,4)],"random_state":[123],}
	licv=sklearn.model_selection.GridSearchCV(SVC(),param_grid=diparameter,scoring="accuracy",cv=5,n_jobs=5)
	licv.fit(xtrain,ttrain)
	predictor=licv.best_estimator_
	sklearn.externals.joblib.dump(predictor,"predictor_svc.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()))
	print(predictor.support_vectors_)
	print(predictor.dual_coef_)
	print(predictor.intercept_)
	print(predictor.gamma)
	
if __name__ == '__main__':
	main()

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

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

以下に抜粋した25行目からの部分では予測器の構築を行う.学習はグリッドサーチおよびクロスバリデーションを用いて行う.予測器構築においてグリッドサーチは止むを得ない特別な事情がない限り絶対に行うべき.26行目ではグリッドサーチで探索するパラメーターを設定している.カーネルにはラジアル基底関数 RBF を指定する.大体これを使えば問題ない.RBF を用いた場合に最適化すべきパラメーターは gamma と C なのでその探索範囲を指定する.カーネルと予測目的によって最適化すべきパラメーターは異なる.Scikit-learn に実装されているサポートベクターマシンの学習器は LIBSVM に由来するので詳細は LIBSVMの設定 を参照する.27行目はグリッドサーチを行うための記述.scoring= のところにグリッドサーチの評価方法を指定することができる.29行目で最も性能が良い予測器を選択.それを30行目の記述で保存する.これによって predictor_svc.pkl というファイルが生成される.

	# 2. learning, cross-validation
	diparameter={"kernel":["rbf"],"gamma":[10**i for i in range(-4,2)],"C":[10**i for i in range(-2,4)],"random_state":[123],}
	licv=sklearn.model_selection.GridSearchCV(SVC(),param_grid=diparameter,scoring="accuracy",cv=5,n_jobs=5)
	licv.fit(xtrain,ttrain)
	predictor=licv.best_estimator_
	sklearn.externals.joblib.dump(predictor,"predictor_svc.pkl",compress=True)

以上のプログラムを実行した結果は以下のようになる.この場合,正確度は0.978,MCC は0.955,F1スコアは0.978である.その下にはパラメーターが出力される.

TPR     0.990
SPC     0.965
PPV     0.966
ACC     0.978
MCC     0.955
F1      0.978
[('C', 1), ('cache_size', 200), ('class_weight', None), ('coef0', 0.0), ('decision_function_shape', None), ('degree', 3), ('gamma', 1), ('kernel', 'rbf'), ('max_iter', -1), ('probability', False), ('random_state', None), ('shrinking', True), ('tol', 0.001), ('verbose', False)]
.
.
.

学習されたパラメーターの意味

サポートベクター分類の場合,以下の式を解くことで分類が出力される.上のパラメーターにおいて .support_vectors_ は以下の $x_i$,.dual_coef_ は $y_i\alpha_i$,.intercept_ は $\rho$ である.

\begin{eqnarray*}\operatorname{sgn}(\sum_{i=1}^{n}y_i\alpha_iK(x_i,x)+\rho)\tag{1}\end{eqnarray*}

また,カーネルの中身は今回用いた RBF の場合,以下で計算されるが,このとき用いるのが .gamma であり,これは以下の $\gamma$ である.すなわち,上のパラメーターさえ抽出すればものすごく簡単な計算で予測結果を得ることができる.これは別のプログラミング言語で書き換える際に便利.

\begin{eqnarray*}K(x,x')=e^{(-\gamma|x-x'|^2)}\tag{2}\end{eqnarray*}

一方でサポートベクター回帰 (SVR) をした場合は以下のような式を解くことで値が得られる.上のパラメーターにおいて,.support_vectors_ は以下の $x_i$,.dual_coef_ は $\alpha_i-\alpha_i^*$,.intercept_ は $\rho$ である.カーネルの計算およびパラメーターに関してはサポートベクター分類の場合と同じ.

\begin{eqnarray*}\sum_{i=1}^{n}(\alpha_i-\alpha_i^*)K(x_i,x)+\rho\tag{3}\end{eqnarray*}

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

以上で学習した予測器を読み込んで,学習データとは完全に独立なデータセット上で構築した予測器のテストを行うには以下のように書く.構築した予測器は,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_svc.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()

これを実行した結果は以下のようになる.独立なデータセットでは学習の段階より性能が落ちている.SVM も場合によっては過学習する.

TPR     0.858
SPC     0.830
PPV     0.842
ACC     0.844
MCC     0.688
F1      0.850

学習パラメーター一覧

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

パラメーター詳細
C数値を指定.デフォルトの値は1.0.これは単に正則化項の係数.
kernel文字列を指定.rbf,linear,poly,sigmoid,precomputed を指定することで用いるカーネルを選択できる.デフォルトは rbf.普通,rbf で良い.
degree整数を指定.デフォルトは3.多項式カーネル poly を使った場合の次数の指定.他のカーネルを指定した場合,この値は無視される.普通,学習によって最適値を決める.
gamma小数で指定.rbf,poly,sigmoid をカーネルに用いた場合のカーネル関数の係数.普通,学習によって最適値を決める.
coef0小数で指定.カーネルに poly または sigmoid を選択したとき有効.普通,学習によって最適値を決める.
probabilityTrue または False で指定.分類等をした際にその分類結果の確率を計算するかどうか.超平面からの距離で近似された値.データサイズが大きいとは学習時間に如実に負担がかかる.
shrinkingTrue または False で指定.縮小推定,すなわち,有効でないアトリビュートの重要度を下げるかどうかの指定.デフォルトは True で普通は指定する.
tol小数で指定.計算を停止するための基準.小さいと探索時間がかかる.
cache_size小数で指定.カーネル行列が大きくなる場合,キャッシュが行われるがその際に確保するキャッシュのサイズ.単位はメガバイト.
class_weightディクショナリまたは balanced を指定.クラスに対する重みをディクショナリで指定できる.指定しない場合は全てのクラスに1が設定されている.balanced を指定すると,y の値により n_samples / (n_classes * np.bincount(y)) を計算することで自動的に重みを調整する.
verbose整数を指定.モデル構築の過程のメッセージを出すかどうか.デフォルトは0.
max_iter整数を指定.最適解探索の際の最大探索回数を指定.-1を指定した場合,収束するまで.
decision_function_shape文字列,ovr または ovo で指定.多クラスを分類する場合において,あるインスタンスを対象のクラスにいれるか,それ以外に入れるか (one-versus-rest, ovr),対象のクラスにいれるかまたは別のクラスに入れるか (one-versus-one, ovo) の指定をすることができる.
random_state乱数のタネの指定.何かしらの整数を指定すべき.
Hatena Google+