実装関連事項

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

ロジスティック回帰 (logistic regression) は最もナイーブな機械学習法のひとつ.回帰と冠されているが基本的には分類問題に利用される.回帰に使うこともできる.ロジスティック関数,多くの場合,標準ロジスティック関数 (シグモイド関数) の値を学習の過程で計算するが,この値の大小によって分類結果を出力する.つまり,シグモイド関数の出力が0.5より大きい場合を正,それ以外の場合を負,みたいな感じで分類する.中身は単純パーセプトロンと完全に同じ.

学習させるデータの構造

以下のようなデータセットを考える.このデータセットでは,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によるランダムフォレストの学習方法とほぼ同じように書くことができる.ランダムフォレストにおける,RandomForestClassifier() の代わりに LogisticRegression() を用いれば良い.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sklearn
from sklearn.linear_model import LogisticRegression
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. standarization of data
	scaler=sklearn.preprocessing.StandardScaler()
	scaler.fit(xtrain)
	xtrain=scaler.transform(xtrain)
	sklearn.externals.joblib.dump(scaler,"scaler_lr.pkl",compress=True)
	
	# 3. learning, cross-validation
	diparameter={"C":[10**i for i in range(-2,4)],"random_state":[123],}
	licv=sklearn.model_selection.GridSearchCV(LogisticRegression(),param_grid=diparameter,cv=5)
	licv.fit(xtrain,ttrain)
	predictor=licv.best_estimator_
	sklearn.externals.joblib.dump(predictor,"predictor_lr.pkl",compress=True)
	
	# 4. 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)))
	
	# 5. printing parameters of the predictor
	print(sorted(predictor.get_params(True).items()))
	print(predictor.coef_)
	print(predictor.intercept_)
	
if __name__ == '__main__':
	main()

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

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

以下に抜粋した25行目からの部分ではデータの標準化を行う.データおよび学習方法によってはデータの標準化をした方が良い場合がある.標準化器は後のテストで用いるため sklearn.externals.joblib.dump() で保存しておく.

	# 2. standarization of data
	scaler=sklearn.preprocessing.StandardScaler()
	scaler.fit(xtrain)
	xtrain=scaler.transform(xtrain)
	sklearn.externals.joblib.dump(scaler,"scaler_lr.pkl",compress=True)

以下の31行目からの部分ではクロスバリデーションによるグリッドサーチを行う.この場合,正則化項,すなわちモデルの複雑さが過剰に増えないように加えられるペナルティの大きさを決める係数である C のみ探索している.34行目で学習を行い,35行目で最も良い予測器を選択.最後の行で予測器を保存する.

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

以上のプログラムを実行した結果は以下のようになる.この場合,正確度は0.865,MCC は0.735,F1スコアは0.873である.その下には予測器のパラメーターが出力される.正則化項の係数は0.01となっている.また,その以下に出力されている値は最終的な重みベクトルとバイアス項の値.すなわち入力ベクトルの60次元の各アトリビュート (変数) の回帰係数と切片に対応する値である.そのため,重み行列の絶対値が大きいほどその変数が重要,すなわち,最終的な結果に対しての寄与率が高いといえる.

TPR     0.925
SPC     0.805
PPV     0.826
ACC     0.865
MCC     0.735
F1      0.873
[('C', 0.01), ('class_weight', None), ('dual', False), ('fit_intercept', True), ('intercept_scaling', 1), ('max_iter', 100), ('multi_class', 'ovr'), ('n_jobs', 1), ('penalty', 'l2'), ('random_state', 123), ('solver', 'liblinear'), ('tol', 0.0001), ('verbose', 0), ('warm_start', False)]
[[ 0.21825782  0.          0.          0.          0.05262041  0.
   0.03777102 -0.25020708  0.          0.          0.          0.          0.
   0.         -0.30799448  0.3212276   0.01166467  0.          0.
  -0.2575022   0.04840727 -0.13792698 -0.02045913  0.22100068 -0.04943508
   0.05019673  0.20818981  0.02280218 -0.03465073 -0.06158518 -0.06258553
  -0.1217509   0.03640961 -0.0480304  -0.21323805  0.05667356  0.00301974
   0.07045268 -0.01521128 -0.05979388  0.02447458  0.02774868  0.06095794
  -0.00666364 -0.04012911  0.0366543   0.02521605 -0.03801839  0.02397596
  -0.00155586 -0.02433519 -0.00922083  0.07395191  0.02099796 -0.00804508
  -0.09013005  0.08671706 -0.04986552 -0.06471949  0.00466963]]
[-0.00930099]

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

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

#!/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. standardization of data
	scaler=sklearn.externals.joblib.load("scaler_lr.pkl")
	xtest=scaler.transform(xtest)
	
	# 3. reading predictor
	predictor=sklearn.externals.joblib.load("predictor_lr.pkl")
	
	# 4. 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.891
SPC     0.805
PPV     0.828
ACC     0.849
MCC     0.700
F1      0.858

学習パラメーター一覧

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

パラメーター詳細
penalty文字列を指定.正則化をL1ノルムでやるかL2ノルムでやるかを,l1 または l2 から選択.
dualTrue または False で指定.デフォルトは False.双対問題を解くか主問題を解くかの指定.
C数値を指定.デフォルトの値は1.0.これは単に正則化項の係数.
fit_interceptTrue または False で指定.デフォルトは True.バイアス項を最適化しない場合,False を指定.
intercept_scaling小数を指定.デフォルトは1.探索法に liblinear が指定され,fit_intercept が True の場合のみ有効.指定される場合,インプットベクトルの要素に intercept_scaling で指定した値が追加される.このときのバイアス項は intercept_scaling * synthetic_feature_weight で計算される値となる.
class_weightディクショナリまたは balanced を指定.クラスに対する重みをディクショナリで指定できる.指定しない場合は全てのクラスに1が設定されている.balanced を指定すると,y の値により n_samples / (n_classes * np.bincount(y)) を計算することで自動的に重みを調整する.
max_iter整数を指定.最適解探索の際の最大探索回数を指定.-1を指定した場合,収束するまで.
random_state乱数のタネの指定.何かしらの整数を指定すべき.
solver文字列を指定.最適解の探索手法を newton-cg,lbfgs,liblinear,sag から選択する.
tol小数で指定.計算を停止するための基準.小さいと探索時間がかかる.
multi_class文字列,ovr または multinomial で指定.デフォルトは ovr.多クラスを分類する場合において,あるインスタンスを対象のクラスにいれるか,それ以外に入れるか (one-versus-rest, ovr) という二値分類の拡張か,多クラス分類をするかの指定.
verbose整数を指定.モデル構築の過程のメッセージを出すかどうか.デフォルトは0.
warm_startTrue または False を指定.デフォルトは False.ここに True を設定すると既にフィットしたモデルに学習を追加することができる.
n_jobs整数を指定.デフォルトは1.フィットおよび予測の際に用いるスレッドの数を指定.-1 を指定した場合は計算機に載っている全スレッド分確保される.
Hatena Google+