AIって結局何なのかよく分からないので、とりあえず100日間勉強してみた Day34
経緯についてはこちらをご参照ください。
■本日の進捗
●ネストした交差検証を理解
■はじめに
引き続き「Pythonではじめる機械学習(オライリー・ジャパン)」で学んでいきます。
交差検証を用いたグリッドサーチの更に向こうへ行ってみたいと思います。
■ネストした交差検証
前回までに単純なグリッドサーチに交差検証を付加して過剰適合を防いだ汎化性能の高いハイパーパラメータの最適化を行うことができるようになりました。
しかし、まだこれでも大きな問題点が残っています。それは交差検証を用いたグリッドサーチ(または単に交差検証)が常にテストデータに対してもっとも優れた最適解を導いていることです。
交差検証ではテストデータに対するバイアスを軽減するためにデータをk分割してテストデータを入れ替えていましたが、それはつまり常にテストデータを「モデルが見ている」ということに他なりません。
機械学習モデルに対してテストデータを知られることを「データのリーク」と言い、一度リークしたデータは既に学習されてしまった訓練データと考えられます。つまり本当の意味でのテストデータは評価するべき最後の最後まで機械学習モデルに知られてはいけないのです。
ここでtrain_test_splitを応用すれば、トレーニングデータ、最適化用検証データ、テストデータと3つに分けることが考えられますが、この場合再度データの偏りが発生します。つまりこの場合も交差検証を2段階で実施することを検討した方がいいでしょう。この考え方をデータ分割のネストといい、外側と内側に2層の交差検証を行います。
外側の交差検証(outer cross-validation)
モデルの最終的な評価を行う層で、汎化性能を公平に評価するためにここでテストデータを確保します。k分割交差検証であれば1つをテストデータとして残し、k-1個のFoldをトレーニングデータとして内側に渡します。
内側の交差検証(inner cross-validation)
ハイパーパラメータの最適化を行うための層で、外側の各Foldに対して内側のデータで最適化されたモデルを返します。外側から受け取ったFoldをさらにk個(外側のkとは独立しているk)に分割して交差検証を行います。
前回、ワインデータセットに適用した交差検証を用いたグリッドサーチをネストに拡張するため、外側にも内側にもk分割交差検証を適用してみます。
import numpy as np import matplotlib.pyplot as plt from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV, cross_val_score, KFold from sklearn.datasets import load_wine data = load_wine() X, y = data.data, data.target param_grid = { 'C': [0.1, 1, 10, 100], 'gamma': [1, 0.1, 0.01, 0.001] } inner_cv = KFold(n_splits=5, shuffle=True, random_state=8) grid = GridSearchCV(SVC(kernel='rbf'), param_grid, refit=True, cv=inner_cv, verbose=1) outer_cv = KFold(n_splits=5, shuffle=True, random_state=8) nested_scores = cross_val_score(grid, X, y, cv=outer_cv) print("Nested CV scores: {}".format(nested_scores)) print("Nested CV mean score: {}".format(np.mean(nested_scores)))
Fitting 5 folds for each of 16 candidates, totalling 80 fits
Fitting 5 folds for each of 16 candidates, totalling 80 fits
Fitting 5 folds for each of 16 candidates, totalling 80 fits
Fitting 5 folds for each of 16 candidates, totalling 80 fits
Fitting 5 folds for each of 16 candidates, totalling 80 fits
Nested CV scores: [0.77777778 0.83333333 0.69444444 0.74285714 0.82857143]
Nested CV mean score: 0.7753968253968254
外側と内側で交差検証手法を変えて、内側のみLeave-One-Out交差検証にしてみます。
import numpy as np import matplotlib.pyplot as plt from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV, cross_val_score, KFold, LeaveOneOut from sklearn.datasets import load_wine data = load_wine() X, y = data.data, data.target param_grid = { 'C': [0.1, 1, 10, 100], 'gamma': [1, 0.1, 0.01, 0.001] } inner_cv = LeaveOneOut() # 内側はLeave-One-Out grid = GridSearchCV(SVC(kernel='rbf'), param_grid, refit=True, cv=inner_cv, verbose=1) outer_cv = KFold(n_splits=5, shuffle=True, random_state=8) nested_scores = cross_val_score(grid, X, y, cv=outer_cv) print("Nested CV scores: {}".format(nested_scores)) print("Nested CV mean score: {}".format(np.mean(nested_scores)))
Fitting 142 folds for each of 16 candidates, totalling 2272 fits
Fitting 142 folds for each of 16 candidates, totalling 2272 fits
Fitting 142 folds for each of 16 candidates, totalling 2272 fits
Fitting 143 folds for each of 16 candidates, totalling 2288 fits
Fitting 143 folds for each of 16 candidates, totalling 2288 fits
Nested CV scores: [0.77777778 0.83333333 0.63888889 0.77142857 0.82857143]
Nested CV mean score: 0.77
このようにデータセットやモデルに対して交差検証のやり方を調整することが可能で、過剰適合や汎化性能を調整することができます。
■おわりに
通常のモデル評価に比べてかなり計算コストの高い交差検証を用いたグリッドサーチですが、ネストすることでさらに計算コストがあがります。また、交差検証のさせ過ぎ(例えばkの数を増やし過ぎるなど)も過剰適合を回避するための本来の目的から逸れて過学習してしまうリスクがあるので注意が必要です。
■参考文献
- Andreas C. Muller, Sarah Guido. Pythonではじめる機械学習. 中田 秀基 訳. オライリー・ジャパン. 2017. 392p.
- ChatGPT. 4o mini. OpenAI. 2024. https://chatgpt.com/
- API Reference. scikit-learn.org. https://scikit-learn.org/stable/api/index.html