目次 †
はじめに †
前回、Pythonの機械学習ライブラリーscikit-learnを用いて、決定木による分類を行いました。
- Pythonで決定木を使う - とうごろうぃき
前回はすべての訓練データを使って予測モデルを学習しましたが、実際のデータに対する分析では、訓練データから予測モデルを学習して、テストデータに対して予測をして終わり、ということにはなりません。
例えば、機械学習の手法を決定木に限定した場合でも、木の深さは3でいいのか、4がいいのか、それとも2でもいいのか、評価基準はジニ係数と情報量利得のどちらがいいのかなど、複数の学習パラメーターのうち、どれがいいのか検討しなければなりません。
ここでは、学習した予測モデルの評価と学習パラメーターの最適化について説明します。
環境やデータは前回と同じです。
学習した予測モデルの評価 †
前回は、訓練データから学習した予測モデルのスコア(ここでは、平均正解率)を、訓練データを用いて求めました。
clf.fit(X, y) clf.score(X, y)
実際の分析では、予測する対象は訓練データ以外のデータであるため、いくら訓練データに対して優れた予測モデルが学習できたとしても、それ以外のデータに対してうまく予測ができないと意味がありません。
そこで、訓練データを全て学習に使わずに、学習用データと検証用データに分割し、学習用データから学習した予測モデルのスコアを検証用データを用いて求めます。
sklearn.model_selectionのtrain_test_splitを用いて、訓練データを学習用データと検証用データに分割します。
from sklearn.model_selection import train_test_split X_train, X_valid, y_train, y_valid = \ train_test_split(X, y, test_size=0.2, random_state=0)
ここでは、検証用データのサイズ (test_size) を20% (0.2) としています。 random_stateは、乱数生成器のシードを表します。
次のようにすると、学習用データと検証用データに分割されたことを確認できます。
print(X_train.shape) print(X_valid.shape)
fit関数を用いて学習用データから学習し、score関数を用いて検証用データに基づいて評価します。
clf.fit(X_train, y_train) clf.score(X_valid, y_valid)
学習した予測モデルは、学習用データに対するスコアは 1.0 ですが、検証用データに対するスコアは 0.867 となりました。 つまり、実際の平均正解率は86.7%程度であると見込まれます。
今回学習された予測モデルはこちら(前回の続きなので、criterion が entropy、max_depth が 3 です)。
このように、どの学習パラメーターを用いるのが良いかについては、検証用データに対するスコアで評価しないといけません。
クロス・バリデーション †
上の評価では、sklearn.model_selectionクラスのtrain_test_split関数を用いて、訓練データをランダムに学習用データと検証用データに分割しました。
乱数を使っているため、偶然に、予測が難しいデータばかりが検証用データに分けられると、スコアが悪くなってしまいます。
そこで、何回か学習とスコアの計算を行い、その平均を求めます。
[math]k[/math]-フォールド・クロス・バリデーション †
[math]k[/math]-フォールド・クロス・バリデーション ([math]k[/math]-fold Cross Validation) は、訓練データを [math]k[/math] 等分し、[math]i[/math] 番目のグループを検証用データ、それ以外を学習用データとして学習とスコアの計算を行うことを、[math]i = 1[/math] から [math]k[/math] まで、 [math]k[/math] 回繰り返す評価方法です。
[math]k[/math]-フォールド・クロス・バリデーションを行うには、sklearn.model_selectionクラスのcross_val_score関数を用いて、cvオプションに分割数 [math]k[/math] を指定し、結果のスコア配列の平均を求めます。
from sklearn.model_selection import cross_val_score scores = cross_val_score(clf, X, y, cv=5) scores.mean()
ファイブ・フォールド・クロス・バリデーションを行なったところ、平均正解率は97.2%でした。
リーブ・ワン・アウト・クロス・バリデーション †
リーブ・ワン・アウト・クロス・バリデーション (Leave One Out Cross Validation) は、[math]i[/math] 番目のデータだけを検証用データ、それ以外を学習用データとして学習とスコアの計算を行うことを、[math]i = 1[/math] からデータの個数 [math]n[/math] まで、 [math]n[/math] 回繰り返す評価方法です。
リーブ・ワン・アウト・クロス・バリデーションは、[math]k[/math]-フォールド・クロス・バリデーションの分割数 [math]k[/math] がデータ数 [math]n[/math] に等しいときと同じで、データが少ないときに用いられます。
リーブ・ワン・アウト・クロス・バリデーションを行うには、sklearn.model_selectionクラスのcross_val_score関数を用いて、cvオプションにsklearn.model_selectionクラスのLeaveOneOut関数を用いて生成したクロス・バリデーション生成器を指定し、結果のスコア配列の平均を求めます。
from sklearn.model_selection import cross_val_score from sklearn.model_selection import LeaveOneOut loo = LeaveOneOut() scores = cross_val_score(clf, X, y, cv=loo) scores.mean()
リーブ・ワン・アウト・クロス・バリデーションを行なったところ、平均正解率は97.3%でした。
グリッド探索による学習パラメーターの最適化 †
今、検討しているのは、決定木の深さは、3がいいのか、4がいいのか、それとも2でもいいのか、評価基準はジニ係数がいいのか、情報量利得がいいのか、ということでした。
sklearn.model_selection.GridSearchCVは、複数の学習パラメーターを組み合わせてクロス・バリデーションを行い、最も良い学習パラメーターを探してくれます。
GridSearchCVには、学習器、学習パラメーターの辞書、[math]k[/math]-フォールド・クロス・バリデーションの分割数 [math]k[/math] を指定します(上と同じようにすれば、リーブ・ワン・アウト・クロス・バリデーションもできます)。
from sklearn.model_selection import GridSearchCV clf = DecisionTreeClassifier() params = {'criterion':('gini', 'entropy'), 'max_depth':[1, 2, 3, 4, 5]} gscv = GridSearchCV(clf, params, cv=5) gscv.fit(X, y)
fit関数を実行すると、すべての学習パラメーターの組み合わせでクロス・バリデーションを行い、最もスコアが高い予測モデルを採用します。 また、最もスコアが高いパラメーターを best_params_ に、そのときのスコアを best_score_ に格納します。
次のようにして、最も良い学習パラメーターを取り出すことができます。
print('%.3f %r' % (gscv.best_score_, gscv.best_params_))
すると、次のように出力されます。
0.973 {'criterion': 'gini', 'max_depth': 2}
評価基準はジニ係数、決定木の深さは2が最も良い学習パラメーターであることがわかりました。
この最もスコアが良かった予測モデルを採用する場合は、このオブジェクトをそのまま予測モデルとして使うことができます。
X = df_iris_test.values p = gscv.predict(X)
しかし、実は、この結果から「評価基準はジニ係数、決定木の深さは2が最も良い学習パラメーターである」と結論付けてしまうのは正しくありません。 すべての学習パラメーターの組み合わせと、そのスコアを出力してみましょう。
scores = gscv.cv_results_['mean_test_score'] params = gscv.cv_results_['params'] for score, param in zip(scores, params): print('%.3f %r' % (score, param))
すると、次のように出力されました。
0.720 {'criterion': 'gini', 'max_depth': 1} 0.973 {'criterion': 'gini', 'max_depth': 2} 0.973 {'criterion': 'gini', 'max_depth': 3} 0.973 {'criterion': 'gini', 'max_depth': 4} 0.973 {'criterion': 'gini', 'max_depth': 5} 0.720 {'criterion': 'entropy', 'max_depth': 1} 0.973 {'criterion': 'entropy', 'max_depth': 2} 0.973 {'criterion': 'entropy', 'max_depth': 3} 0.973 {'criterion': 'entropy', 'max_depth': 4} 0.973 {'criterion': 'entropy', 'max_depth': 5}
評価基準はジニ係数でも情報量利得でも変わらず、木の深さは1はダメだけど、2から5では変わらないことがわかります。
グリッド探索における最適化基準の変更 †
GridSearchCVでは、最適化の基準を指定することもできます。
何も指定しないと、学習器のデフォルトのスコア関数が使われます。 分類分析用の学習器の場合は正解率 (accuracy)、回帰分析用の学習器の場合は決定係数 [math]R^2[/math] であるものが多いです。
最適化の基準を指定するには、 scoring オプションに最適化基準の名前を指定します。 例えば、回帰分析において、最適化基準として決定係数 [math]R^2[/math] の代わりに平均二乗誤差平方根 (RMSE, root mean squared error) を使うには、次のようにします。
gscv = GridSearchCV(clf, params, scoring='neg_mean_squared_error', cv=5)
ここで、neg_mean_squared_error は、平均二乗誤差 (MSE, mean squared error) を[math]-1[/math]倍したものを表しています。
指定可能な最適化基準は、ドキュメントで確認できます。
- 3.3.1 The scoring parameter: defining model evaluation rules - scikit-learn