機械学習のお勉強(モデルの評価とパラメータのチューニング)
教科書
Data準備
import pandas as pd import urllib try: df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases' '/breast-cancer-wisconsin/wdbc.data', header=None) except urllib.error.URLError: df = pd.read_csv('https://raw.githubusercontent.com/rasbt/' 'python-machine-learning-book/master/code/' 'datasets/wdbc/wdbc.data', header=None) if Version(sklearn_version) < '0.18': from sklearn.cross_validation import train_test_split else: from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = \ train_test_split(X, y, test_size=0.20, random_state=1)
PipeLine
パイプラインを使うと変換を一つにまとめて行うことができる。
以下はスケーリング、主成分分析、ロジスティック回帰をパイプラインで繋いで実行している。
from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline pipe_lr = Pipeline([('scl', StandardScaler()), ('pca', PCA(n_components=2)), ('clf', LogisticRegression(random_state=1))]) pipe_lr.fit(X_train, y_train) print('Test Accuracy: %.3f' % pipe_lr.score(X_test, y_test)) y_pred = pipe_lr.predict(X_test)
結果
Test Accuracy: 0.947
モデルの評価
モデル選択:未知のデータへの予測性能を上げるため、パラメータ設定のチューニングや比較を行う。ここでのチューニングパラメータをハイパーパラメータということがある。
ホールドアウト法
ホールドアウト法でのモデル選択での効果を上げるには、データセットを
の3つにわける。
問題はデータのサンプル数や分割の仕方に影響を受けることである。
k分割交差検証
非復元抽出を用いて、トレーニングデータセットをk個に分割し、k-1個をトレーニングに使用し、1個をテストに使用する。一般的には10分割することが多いが、小さいデータセットの場合などは1個抜き交差検証などが使用される。
非復元抽出:選んだ対象をサンプルに戻さない
層化k分割交差検証
各クラスの比率が均等でない時に、評価のバイアスとバリアンスが改善される。
import numpy as np if Version(sklearn_version) < '0.18': from sklearn.cross_validation import StratifiedKFold else: from sklearn.model_selection import StratifiedKFold if Version(sklearn_version) < '0.18': kfold = StratifiedKFold(y=y_train, n_folds=10, random_state=1) else: kfold = StratifiedKFold(n_splits=10, random_state=1).split(X_train, y_train) scores = [] for k, (train, test) in enumerate(kfold): pipe_lr.fit(X_train[train], y_train[train]) score = pipe_lr.score(X_train[test], y_train[test]) scores.append(score) print('Fold: %s, Class dist.: %s, Acc: %.3f' % (k+1, np.bincount(y_train[train]), score)) print('\nCV accuracy: %.3f +/- %.3f' % (np.mean(scores), np.std(scores)))
結果
CV accuracy: 0.950 +/- 0.029
skkearnで簡略化すると
if Version(sklearn_version) < '0.18': from sklearn.cross_validation import cross_val_score else: from sklearn.model_selection import cross_val_score scores = cross_val_score(estimator=pipe_lr, X=X_train, y=y_train, cv=10, n_jobs=1) #CPUのコア数を指定できる print('CV accuracy scores: %s' % scores) print('CV accuracy: %.3f +/- %.3f' % (np.mean(scores), np.std(scores)))
結果
CV accuracy scores: [ 0.89130435 0.97826087 0.97826087 0.91304348 0.93478261 0.97777778 0.93333333 0.95555556 0.97777778 0.95555556] CV accuracy: 0.950 +/- 0.029
参考
http://www.jmlr.org/papers/volume6/markatou05a/markatou05a.pdf
Improvements on Cross-Validation: The .632+ Bootstrap Method on JSTOR
アルゴリズムの診断
学習曲線
サンプルサイズの値の変化による評価
バイアスが高い:トレーニングと検証の正解率が低い
バリアンスが高い:トレーニングと交差検証の精度に大きな差がある
import matplotlib.pyplot as plt if Version(sklearn_version) < '0.18': from sklearn.learning_curve import learning_curve else: from sklearn.model_selection import learning_curve pipe_lr = Pipeline([('scl', StandardScaler()), ('clf', LogisticRegression(penalty='l2', random_state=0))]) train_sizes, train_scores, test_scores =\ learning_curve(estimator=pipe_lr, X=X_train, y=y_train, train_sizes=np.linspace(0.1, 1.0, 10), cv=10, n_jobs=1) train_mean = np.mean(train_scores, axis=1) train_std = np.std(train_scores, axis=1) test_mean = np.mean(test_scores, axis=1) test_std = np.std(test_scores, axis=1) plt.plot(train_sizes, train_mean, color='blue', marker='o', markersize=5, label='training accuracy') plt.fill_between(train_sizes, train_mean + train_std, train_mean - train_std, alpha=0.15, color='blue') plt.plot(train_sizes, test_mean, color='green', linestyle='--', marker='s', markersize=5, label='validation accuracy') plt.fill_between(train_sizes, test_mean + test_std, test_mean - test_std, alpha=0.15, color='green') plt.grid() plt.xlabel('Number of training samples') plt.ylabel('Accuracy') plt.legend(loc='lower right') plt.ylim([0.8, 1.0]) plt.tight_layout() # plt.savefig('./figures/learning_curve.png', dpi=300) plt.show()
結果
fill_betweenによって標準偏差に色が塗られている
検証曲線
モデルのパラメータの変化に対する評価
if Version(sklearn_version) < '0.18': from sklearn.learning_curve import validation_curve else: from sklearn.model_selection import validation_curve param_range = [0.001, 0.01, 0.1, 1.0, 10.0, 100.0] train_scores, test_scores = validation_curve( estimator=pipe_lr, X=X_train, y=y_train, param_name='clf__C', param_range=param_range, cv=10) train_mean = np.mean(train_scores, axis=1) train_std = np.std(train_scores, axis=1) test_mean = np.mean(test_scores, axis=1) test_std = np.std(test_scores, axis=1) plt.plot(param_range, train_mean, color='blue', marker='o', markersize=5, label='training accuracy') plt.fill_between(param_range, train_mean + train_std, train_mean - train_std, alpha=0.15, color='blue') plt.plot(param_range, test_mean, color='green', linestyle='--', marker='s', markersize=5, label='validation accuracy') plt.fill_between(param_range, test_mean + test_std, test_mean - test_std, alpha=0.15, color='green') plt.grid() plt.xscale('log') plt.legend(loc='lower right') plt.xlabel('Parameter C') plt.ylabel('Accuracy') plt.ylim([0.8, 1.0]) plt.tight_layout() # plt.savefig('./figures/validation_curve.png', dpi=300) plt.show()
結果
C = 0.1あたりが最適だと見てとれる
グリッドサーチによるチューニング
機械学習にはデータから学習されるパラメータと個別に最適されるパラメータがある。後者はハイパーパラメータと呼ばれ、チューニングパラメータである。
グリッドサーチ:しらみつぶしの網羅的探索手法
from sklearn.svm import SVC if Version(sklearn_version) < '0.18': from sklearn.grid_search import GridSearchCV else: from sklearn.model_selection import GridSearchCV pipe_svc = Pipeline([('scl', StandardScaler()), ('clf', SVC(random_state=1))]) param_range = [0.0001, 0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0] param_grid = [{'clf__C': param_range, 'clf__kernel': ['linear']}, {'clf__C': param_range, 'clf__gamma': param_range, 'clf__kernel': ['rbf']}] gs = GridSearchCV(estimator=pipe_svc, param_grid=param_grid, scoring='accuracy', cv=10, n_jobs=-1) gs = gs.fit(X_train, y_train) print(gs.best_score_) print(gs.best_params_) clf = gs.best_estimator_ clf.fit(X_train, y_train) print('Test accuracy: %.3f' % clf.score(X_test, y_test))
結果
0.978021978022 {'clf__C': 0.1, 'clf__kernel': 'linear'} Test accuracy: 0.965
欠点としては、考えられるすべての組み合わせについて評価するためコストが高くつく。
sk-learnにはRandomizedSearchCVというランダムサーチの手法を用いることができる。
入れ子式の交差検証によるアルゴリズムの選択
5x2交差検証の例
gs = GridSearchCV(estimator=pipe_svc, param_grid=param_grid, scoring='accuracy', cv=2) # Note: Optionally, you could use cv=2 # in the GridSearchCV above to produce # the 5 x 2 nested CV that is shown in the figure. scores = cross_val_score(gs, X_train, y_train, scoring='accuracy', cv=5) print('CV accuracy: %.3f +/- %.3f' % (np.mean(scores), np.std(scores)))
結果
CV accuracy: 0.965 +/- 0.025
決定木分類器
from sklearn.tree import DecisionTreeClassifier gs = GridSearchCV(estimator=DecisionTreeClassifier(random_state=0), param_grid=[{'max_depth': [1, 2, 3, 4, 5, 6, 7, None]}], scoring='accuracy', cv=2) scores = cross_val_score(gs, X_train, y_train, scoring='accuracy', cv=5) print('CV accuracy: %.3f +/- %.3f' % (np.mean(scores), np.std(scores)))
結果
CV accuracy: 0.921 +/- 0.029
SVMのほうが性能が良いことがわかる
様々な性能評価指標
正解率以外にも様々な評価する指標が存在する
混同行列
真陽陰性(TP,TN)、偽陽陰性(FP,FN)を表現する行列
真偽:予測があたったかどうか
陽陰:予測されたクラス
ex: 偽陽性は予測は陽だが間違えた
sklearn -> confusion_matrix(y_true=
で作成できる
from sklearn.metrics import confusion_matrix pipe_svc.fit(X_train, y_train) y_pred = pipe_svc.predict(X_test) confmat = confusion_matrix(y_true=y_test, y_pred=y_pred) print(confmat) fig, ax = plt.subplots(figsize=(2.5, 2.5)) ax.matshow(confmat, cmap=plt.cm.Blues, alpha=0.3) for i in range(confmat.shape[0]): for j in range(confmat.shape[1]): ax.text(x=j, y=i, s=confmat[i, j], va='center', ha='center') plt.xlabel('predicted label') plt.ylabel('true label') plt.tight_layout() # plt.savefig('./figures/confusion_matrix.png', dpi=300) plt.show()
結果
誤分類率、正解率
ERR = (FP + FN) / (FP + FN + TP + TN)
ACC = 1 - ERR
真陽性、偽陽性
不均衡なクラスの問題に役立つ指標
FPR = FP / (FP + TN)
TPR = TP / (FN + TP)
適合率、再現率
PRE = TP / (TP + FP)
REC = TP / (FN + TP)
F1スコア
適合率や再現率を組み合わせた評価指標。よく使用される。
F1 = 2 * (PRE * REC) / (PRE + REC)
sklearnでの評価手法の実装
from sklearn.metrics import precision_score, recall_score, f1_score print('Precision: %.3f' % precision_score(y_true=y_test, y_pred=y_pred)) print('Recall: %.3f' % recall_score(y_true=y_test, y_pred=y_pred)) print('F1: %.3f' % f1_score(y_true=y_test, y_pred=y_pred))
結果
Precision: 0.976 Recall: 0.952 F1: 0.964
sk-learnではmake_scorerで自作の性能評価関数を作成できる
from sklearn.metrics import make_scorer scorer = make_scorer(f1_score, pos_label=0) c_gamma_range = [0.01, 0.1, 1.0, 10.0] param_grid = [{'clf__C': c_gamma_range, 'clf__kernel': ['linear']}, {'clf__C': c_gamma_range, 'clf__gamma': c_gamma_range, 'clf__kernel': ['rbf']}] gs = GridSearchCV(estimator=pipe_svc, param_grid=param_grid, scoring=scorer, cv=10, n_jobs=-1) gs = gs.fit(X_train, y_train) print(gs.best_score_) print(gs.best_params_)
結果
0.982798668208 {'clf__C': 0.1, 'clf__kernel': 'linear'}
ROC曲線
受信者操作特性:性能に基づいて分類モデルを選択するための便利なツール
AUC:曲面下面積
from sklearn.metrics import roc_curve, auc from scipy import interp pipe_lr = Pipeline([('scl', StandardScaler()), ('pca', PCA(n_components=2)), ('clf', LogisticRegression(penalty='l2', random_state=0, C=100.0))]) X_train2 = X_train[:, [4, 14]] if Version(sklearn_version) < '0.18': cv = StratifiedKFold(y_train, n_folds=3, random_state=1) else: cv = list(StratifiedKFold(n_splits=3, random_state=1).split(X_train, y_train)) fig = plt.figure(figsize=(7, 5)) mean_tpr = 0.0 mean_fpr = np.linspace(0, 1, 100) all_tpr = [] for i, (train, test) in enumerate(cv): probas = pipe_lr.fit(X_train2[train], y_train[train]).predict_proba(X_train2[test]) fpr, tpr, thresholds = roc_curve(y_train[test], probas[:, 1], pos_label=1) mean_tpr += interp(mean_fpr, fpr, tpr) mean_tpr[0] = 0.0 roc_auc = auc(fpr, tpr) plt.plot(fpr, tpr, lw=1, label='ROC fold %d (area = %0.2f)' % (i+1, roc_auc)) plt.plot([0, 1], [0, 1], linestyle='--', color=(0.6, 0.6, 0.6), label='random guessing') mean_tpr /= len(cv) mean_tpr[-1] = 1.0 mean_auc = auc(mean_fpr, mean_tpr) plt.plot(mean_fpr, mean_tpr, 'k--', label='mean ROC (area = %0.2f)' % mean_auc, lw=2) plt.plot([0, 0, 1], [0, 1, 1], lw=2, linestyle=':', color='black', label='perfect performance') plt.xlim([-0.05, 1.05]) plt.ylim([-0.05, 1.05]) plt.xlabel('false positive rate') plt.ylabel('true positive rate') plt.title('Receiver Operator Characteristic') plt.legend(loc="lower right") plt.tight_layout() # plt.savefig('./figures/roc.png', dpi=300) plt.show()
AUCの計算
pipe_lr = pipe_lr.fit(X_train2, y_train) y_labels = pipe_lr.predict(X_test[:, [4, 14]]) y_probas = pipe_lr.predict_proba(X_test[:, [4, 14]])[:, 1] # note that we use probabilities for roc_auc # the `[:, 1]` selects the positive class label only from sklearn.metrics import roc_auc_score, accuracy_score print('ROC AUC: %.3f' % roc_auc_score(y_true=y_test, y_score=y_probas)) print('Accuracy: %.3f' % accuracy_score(y_true=y_test, y_pred=y_labels))
多クラス分類の性能指標
sk-learnでは以下が実装されている
マクロ平均法
マイクロ平均法
マイクロ平均
各インスタンスまたは予測を平等に重みつけしたいとき
PREmicro = (TP1 + ... + TPk) / (TP1 + ... + TPk + FP1 + ... + FPk)
マクロ平均
最も出現するクラスラベルに過度に影響を受けることがないよう評価したいとき
PREmacro = (PRE1 + ... + PREk) / k
pre_scorer = make_scorer(score_func=precision_score, pos_label=1, greater_is_better=True, average='micro')