プロクラシスト

今日の寄り道 明日の近道

【Day-10】Cross Validationとパラメータサーチでモデルの調整


スポンサーリンク

f:id:imslotter:20171210183652p:plain

データ分析ガチ勉強アドベントカレンダー 10日目。

  • データを集め、前処理を行い、学習をする。
  • どういう学習器が良いのかの評価基準

の勉強までできた。でも、データがあって、評価基準がわかっていても、どうやって評価すればいいかについてはまだあまり触れていない。

もうちょっと具体的に言うと、データと学習器があって、どういう風に教師データとテストデータを分ければいいのか。この方法について、実はまだ何も言っていない。

そこで登場するのが、sklearn.model_selection

いよいよ最後の仕上げ。良い学習器、良いパラメータの探り方について学ぶ

Validationとは

モデルを作ってデータを分析する際、データをtrain data, validation data, test dataに分ける。 最終的なテストをする前に、訓練データの中を分割してテストを回すことで、パラメータ調整を行うために用いられる。

sklearnでは、originalデータをtrainとtestに分けるところをsklearn.model_selection.test_train_splitで行い、Validationデータに分けるところにもsklearn.model_selection.cross_val_scoreなどが用意されている。

また、それにしたがって学習器のパタメータを変えながら評価していく仕組みも用意されていて、sklearn.model_selection.GridSearchCVで実現される。(後述)

skleanのCross Validation

cross_val_score

sklearnで最も簡単にCross Validationをするには、cross_val_scoreという関数を用いる。

`cross_val_score(clf, data, target, cv=5, scoring="accuracy")`
変数 役割
clf 学習器
data データ
target ラベル
cv クロスバリデーションの回数
scoring 評価する指標(参照:metrics)

scoringには、下記の値を用いることができる。

['accuracy', 'adjusted_rand_score', 'average_precision', 
'f1', 'f1_macro', 'f1_micro', 'f1_samples', 'f1_weighted', 'neg_log_loss', 
'neg_mean_absolute_error', 'neg_mean_squared_error', 'neg_median_absolute_error', 
'precision', 'precision_macro', 'precision_micro', 'precision_samples', 'precision_weighted', 
'r2', 'recall', 'recall_macro', 'recall_micro', 'recall_samples', 'recall_weighted', 
'roc_auc']

各評価値が何なのかを詳しく知りたい方は、Day9を参照

出力は、cvの回数分の、評価値の配列となっている。例として、digitsデータをRandomForestで取り扱ってみる。

from sklearn.datasets import load_digits
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
digits = load_digits()
clf = RandomForestClassifier()
scoring = "f1_macro"
scores = cross_val_score(clf, digits.data, digits.target, cv=5, scoring=scoring)
print("{}:{:.3f}+/-{:.3f}".format(scoring, scores.mean(), scores.std()))

結果

f1_macro:0.901+/-0.031

pipline

cross validationを行う際に、sklearn.pipelineを使うと、処理が簡潔に書ける。(参考)

例 : 正規化した後の効果をcross_validationしたい

# 通常
from sklearn import preprocessing
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, train_size=0.8)
scaler = preprocessing.StandardScaler().fit(X_train)
X_train_transformed = scaler.transform(X_train)
clf = SVC(C=1).fit(X_train_transformed, y_train)
X_test_transformed = scaler.transform(X_test)
print(clf.score(X_test_transformed, y_test))
# pipeline
from sklearn.pipeline import make_pipeline
# 処理を流す順に関数を書いていく
scoring = "f1_macro"
clf = make_pipeline(preprocessing.StandardScaler(), SVC(kernel="linear",C=1))
scores = cross_val_score(clf, digits.data, digits.target, cv=5, scoring=scoring)
print("{}:{:.3f}+/-{:.3f}".format(scoring, scores.mean(), scores.std()))

pipelineを使うと、複数処理を一括でCVにかけられるので、とてもよい。

cross_validate 関数

sklearn 0.19.1から、cross_validateが用意された。

さっきまでのがcross_val_scores関数。ややこしいが少し違う

  • 評価に複数の指標を考慮できる
  • テストスコアに加えて、学習の時のスコア、学習時間、テストの時間などを算出してくれる。

つまり、より強力なcross validationを行える。

from sklearn.model_selection import cross_validate
from sklearn.metrics import recall_score
from sklearn.svm import SVC
scoring = ["f1_macro", "recall_macro"]
clf = SVC(C=1)
scores = cross_validate(clf, digits.data, digits.target, scoring=scoring, cv=5)
for key,value in scores.items():
    print("{}:{:.2f}+/-{:.2f}".format(key, value.mean(), value.std()))

結果はこちら

fit_time:0.51+/-0.01
score_time:0.12+/-0.00
test_f1_macro:0.50+/-0.04
train_f1_macro:1.00+/-0.00
test_recall_macro:0.45+/-0.04
train_recall_macro:1.00+/-0.00

Validation iterators

CrossValidationを行うときの、データセットの分け方にも、いろんなバリエーションがある。 sklearn.model_selection内には、いろんなCVの分け方の方法が載っている。

  • i.i.d data
    • K-fold
    • Repeated K-Fold
    • Leave One Out (LOO)
    • Leave P out (LPO)
    • Shuffle & Split
  • iterators with stratification based on class labels(サンプリングとか)
    • Stratified k-fold
    • Stratified Shuffle Split
  • Grouped data i.i.dデータと基本おなじ

代表的なのが、K-Fold法とLeave One Out法。両者を図にするとこんな感じ

今回は、K-Fold法でのCrossValidationを実装してみる

import numpy as np
from sklearn.model_selection import KFold
from sklearn.datasets import load_digits
from sklearn.metrics import accuracy_score
# K-Fold交差検定
iterater = KFold(n_splits=5)
results = []
for train_indexes, test_indexes in iterater.split(digits.data):
#     print(train_indexes, test_indexes)
    X = digits.data[train_indexes]
    y = digits.target[train_indexes]
    clf = RandomForestClassifier()
    clf.fit(X,y)
    pred_y = clf.predict(digits.data[test_indexes])
    ac = accuracy_score(digits.target[test_indexes], pred_y)
    results.append(ac)
results = np.array(results)
print("KFold accuracy: {:.2f}+/-{:.2f}".format(results.mean(),results.std()))

出力

KFold accuracy: 0.90+/-0.04

パラメータを調整する

機械学習は複雑なので、モデルの持つパラメータをどのように調節すべきか難しい。

sklearnでは、下記の方法でパラメータ探索が行えるようになっている(参考 : Tuning the hyper-parameters of an estimator)

  • グリッドサーチ : パラメータを総当り式で調べる
  • ランダム選択 : ランダムにパラメータをサンプリングして試す
  • モデルに合わせた賢いCV*1

他に、変数最適化の問題として、ある指標を評価関数にして解くというのもよさそう。Gaussian Process Optimizationなどがよさげ (参考)

今回は、グリッドサーチでの方法を載せる

パラメータ

SVCのパラメータである、C, kernel, その他変数について調整する。調整する パラメータは下記のように、キーとリストで保持する。

param_grid = [
    {'C': [1, 10, 100, 1000], 'kernel': ['linear']},
    {'C': [1, 10, 100, 1000], 'kernel': ['rbf'], 'gamma': [0.001, 0.0001]},
    {'C': [1, 10, 100, 1000], 'kernel': ['poly'], 'degree': [2, 3, 4], 'gamma': [0.001, 0.0001]},
    {'C': [1, 10, 100, 1000], 'kernel': ['sigmoid'], 'gamma': [0.001, 0.0001]}
    ]

関数にはsklearn.model_selection.GridSearchCVを用いる。引数は

  • 学習器のインスタンス(今回はSVC)
  • パラメータグリッド(上記param_grid)
  • cv : cross validationの回数
  • scoring : 評価する指標

実装

実装は下記の通り

from sklearn.model_selection import GridSearchCV
import pandas as pd
clf.get_params()
svc = SVC()
scoring = "f1_macro"
param_grid = [
    {'C': [1, 10, 100, 1000], 'kernel': ['linear']},
    {'C': [1, 10, 100, 1000], 'kernel': ['rbf'], 'gamma': [0.001, 0.0001]},
    {'C': [1, 10, 100, 1000], 'kernel': ['poly'], 'degree': [2, 3, 4], 'gamma': [0.001, 0.0001]},
    {'C': [1, 10, 100, 1000], 'kernel': ['sigmoid'], 'gamma': [0.001, 0.0001]}
    ]
clf = GridSearchCV(svc, param_grid,cv=4)
clf.fit(digits.data, digits.target)
df = pd.DataFrame(clf.cv_results_)
df_scored = df.sort_values(by=["rank_test_score"])[["params","mean_test_score","std_test_score","mean_fit_time"]]

結果を見る

結果は、pandasのデータフレームに保管可能になっていて、簡単に好きな値でソートができる。今回はテストスコアの良い順にソートを行ってみた。(pandasの扱いが苦手な人はDay4をご覧ください)

こんな風に、どういうパラメータがどういう結果だったかが保存されているので、より良いパラメータを選ぶことができる。

まとめ

今日は学習器の調整に重要なCross Validationとパラメータサーチの方法について触れた。 実際の業務の場では、パラメータを調整する機会もとても多いので、知っておくと良い。また、少し述べたが、もう少し効率的なパラメータ調整方法についても勉強しておきたい。

機械学習の一連の流れがコレでざっと洗えたと思う。明日はscikit-learnのチートシートについてでも触れようかな。明日もお楽しみに!

*1:ドキュメントによればあるらしい...?

PROCRASIST