こんにちは!ぼりたそです!
今回はクロスバリデーション(交差検証)についてわかりやすく解説していきます!
この記事は以下のポイントでまとめています。
それでは詳細に解説していきいます。
クロスバリデーションとは?
クロスバリデーションは、主に機械学習モデルなどの性能を評価するための手法です。
モデルの性能を客観的に評価し、適切なハイパーパラメータの選択やモデルの改善に役立ちます。
以下にクロスバリデーションの基本的な考え方についてわかりやすく説明します。
通常、学習モデルの性能評価を行う場合、データをトレーニングセットとテストセットに分割し、トレーニングセットでモデルを学習させて、テストセットでモデルの性能を評価します。しかし、この単純な分割では、データの選び方によって性能評価が偏る可能性があります。
一方、クロスバリデーションは機械学習モデルの性能評価法で、データを複数の部分に分割してトレーニングとテストを繰り返し、モデルの汎化性能を客観的に評価する手法です。
これにより、データの選び方による偏りを軽減し、より信頼性のある評価が得られます。
クロスバリデーションを用いることで以下のようなメリットが得られます。
■メリット
このようにクロスバリデーションはメリットも大きいですが、もちろんデメリットもあるのです。
以下が主なデメリットになります。
■デメリット
メリット、デメリットを理解して上で正しく使用したいですね。
クロスバリデーションの種類
クロスバリデーションについて何となく理解いただけたでしょうか?
次にクロスバリデーションの種類について説明していきたいと思います。
クロスバリデーションの種類としては主に以下の3つとなります。
以下の3つはデータセットの分割の方法が異なります。
- k分割交差検証 (k-Fold Cross-Validation)
- Leave-One-Out Cross-Validation (LOOCV)
- 層化k分割交差検証 (Stratified k-Fold Cross-Validation)
それでは順に説明していきます。
k分割交差検証 (k-Fold Cross-Validation)
k-Fold Cross-Validationとは上の図のようにデータをk個のフォールドに均等に分割し、各評価では1つのフォールドをテストセットとして、残るk-1個のフォールドをトレーニングセットとして使用します。
最終的な性能評価はk回の評価の平均として計算されます。この手法はクロスバリデーションの中でもよく使用される手法であり、過学習を避けつつデータを効率的に利用できるメリットがあります。
それでは実際にk分割交差検証を実行してモデルの評価をしてみます。
今回はpythonを使用して擬似的に作成した関数を5 fold Cross-ValidationでMSE(平均二乗誤差)を使用して評価していきたいと思います。
import numpy as np
from sklearn.model_selection import cross_val_score, KFold
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# サンプルデータの生成
np.random.seed(42)
X = np.random.rand(100, 3) # 100行3列の特徴行列
y = 2 * X[:, 0] + 3 * X[:, 1] + 4 * X[:, 2] + np.random.randn(100) # 重回帰式にノイズを加えた目標値
# 5フォールドのクロスバリデーションを設定
num_folds = 5
kf = KFold(n_splits=num_folds, shuffle=True, random_state=42)
# 重回帰分析モデルを設定
model = LinearRegression()
# 各フォールドごとの平均変数の係数を保存するためのリストを初期化
coeffs_per_fold = []
# クロスバリデーションで性能評価を実行
for fold, (train_index, test_index) in enumerate(kf.split(X), start=1):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
model.fit(X_train, y_train)
mse = mean_squared_error(y_test, model.predict(X_test))
coeffs_per_fold.append(model.coef_)
print(f"Fold {fold}: Mean Squared Error = {mse}, Coefficients = {model.coef_}")
# 最終的に平均した変数の係数を計算
average_coeffs = np.mean(coeffs_per_fold, axis=0)
print(f"Average Coefficients across {num_folds} folds = {average_coeffs}")
# 平均MSEを計算して表示
average_mse = -np.mean(cross_val_score(model, X, y, cv=kf, scoring='neg_mean_squared_error'))
print(f"Average Mean Squared Error across {num_folds} folds = {average_mse}")
'''
出力結果
Fold 1: Mean Squared Error = 2.0173924806103, Coefficients = [2.10589767 2.92460599 4.68605925]
Fold 2: Mean Squared Error = 0.6224586755912234, Coefficients = [2.19706932 2.82318627 4.53878099]
Fold 3: Mean Squared Error = 0.681337087852153, Coefficients = [2.39154581 2.90624531 4.44354592]
Fold 4: Mean Squared Error = 0.44423681259417125, Coefficients = [2.29699543 2.92552169 4.61495879]
Fold 5: Mean Squared Error = 1.4773918920339761, Coefficients = [2.45886327 2.6589557 4.62285239]
Average Coefficients across 5 folds = [2.2900743 2.84770299 4.58123947]
Average Mean Squared Error across 5 folds = 1.0485633897363646
'''
結果表示を見ると5 Fold Cross-Validationの平均としてMSE=1.04となっています。作成した関数なのでそれなりに良いモデルになっていますね。
それぞれのFoldに注目するとMSEが0.4から2.2までブレています。クロスバリデーションせずに評価してしまうと正しく性能を評価できない可能性があるということですね。
また、今回変数を3つ設定しており、それぞれの係数がそれぞれ2, 3, 4となるように設定していました。最終的に出力された計数を見ると、[2.2900743 2.84770299 4.58123947]となっており、そこそこあっているのではないかと思います。
以上がpythonを使用したk Fold Cross-Validationの実行となります。
Leave-One-Out Cross-Validation (LOOCV)
LOOCVは各評価で1つのサンプルをテストセットとし、残る全てをトレーニングセットとして使用します。
つまり、データセット内のすべてのサンプルに対して評価を行います。この手法のメリットとしてはデータ数が少ない場合でもモデルを評価できる点が挙げられます。逆にデータ数が多い場合(データが30以上)の場合は計算コストが大きくなってしまう可能性があります。
LOOCVについても実際にPythonを使用して擬似的な関数を作成し、評価していきましょう。
関数などは先ほどのk-Fold Cross-Validationと同様の設定にしてあります。
import numpy as np
from sklearn.model_selection import cross_val_predict, LeaveOneOut
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# サンプルデータの生成
np.random.seed(42)
X = np.random.rand(100, 3) # 100行3列の特徴行列
y = 2 * X[:, 0] + 3 * X[:, 1] + 4 * X[:, 2] + np.random.randn(100) # 重回帰式にノイズを加えた目標値
# Leave-One-Out Cross-Validation を設定
loo = LeaveOneOut()
# 重回帰分析モデルを設定
model = LinearRegression()
# LOOCV で性能評価を実行
y_pred = cross_val_predict(model, X, y, cv=loo)
# 各変数の係数を保存するためのリストを初期化
coefs = [[] for _ in range(X.shape[1])]
# LOOCV内でモデルをトレーニングし、各変数の係数を保存
for train_index, _ in loo.split(X):
model.fit(X[train_index], y[train_index])
for i, coef in enumerate(model.coef_):
coefs[i].append(coef)
# 各変数の平均係数を計算し出力
for i, coef_list in enumerate(coefs):
mean_coef = np.mean(coef_list)
print(f"Variable {i+1}: Mean Coefficient = {mean_coef}")
# 平均MSEを計算して表示
mse = mean_squared_error(y, y_pred)
print(f"Mean Squared Error = {mse}")
'''
出力結果
Variable 1: Mean Coefficient = 2.2780064787295164
Variable 2: Mean Coefficient = 2.842209463099327
Variable 3: Mean Coefficient = 4.570304776138533
Mean Squared Error = 0.9962216632720223
'''
データが100個あるため一つ一つのMSEと変数の係数は出力していませんでしたが、最終的な平均値を見ると、MSEは0.996とk-Fold Cross-Validationと大差ない結果になっていますね。
変数の係数についても[2.278, 2.842, 4.570]と実際の[2, 3, 4]と比較すると大体あっているのではないでしょうか。
以上がLOOCVについての説明とPythonによる評価結果になります。
層化k分割交差検証 (Stratified k-Fold Cross-Validation)
Stratified k-Fold Cross-Validationとはクラス分類の場合に特に有用です。
各フォールド内のクラスの割合を元のデータセットと近く保つようにデータを分割します。これにより、各フォールドが一様に異なるクラスを含むことが保証され、より正確な性能評価が行えます。
それでは実際に層化k分割交差検証を実行してモデルの評価をしてみます。
今回はpythonを使用して擬似的に作成した関数をStratified 5 fold Cross-ValidationでAccuracy(正答率)を使用して評価していきたいと思います。
関数については説明変数が20個で目的変数のクラスが二つになっています。学習モデルはロジスティック回帰を使用しています。
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import StratifiedKFold
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 仮想のデータ生成
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
# 層化k分割交差検証を設定
num_folds = 5
skf = StratifiedKFold(n_splits=num_folds, shuffle=True, random_state=42)
# ロジスティック回帰モデルを設定
model = LogisticRegression()
# 各フォールドでの性能評価を実行
fold_accuracy_scores = []
for fold, (train_index, test_index) in enumerate(skf.split(X, y), start=1):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
fold_accuracy_scores.append(accuracy)
print(f"Fold {fold}: Accuracy = {accuracy}")
# 平均精度を計算して表示
average_accuracy = np.mean(fold_accuracy_scores)
print(f"Average Accuracy across {num_folds} folds = {average_accuracy}")
'''
Fold 1: Accuracy = 0.87
Fold 2: Accuracy = 0.885
Fold 3: Accuracy = 0.87
Fold 4: Accuracy = 0.86
Fold 5: Accuracy = 0.86
Average Accuracy across 5 folds = 0.869
'''
出力結果を見ると平均正答率は0.869と85%以上正解しているようなモデルが構築できているのが分かります。
それぞれのFlodにおける正答率も見ると、基本的には0.86-0.88程度であり、そこまでブレていませんね。
以上が層化k分割交差検証の説明と実際の評価結果になります。
終わりに
以上がクロスバリデーションについての解説になります。構築したモデルの性能評価をする際にはよく使用する方法ですので、使いこなしたいですね。また、クロスバリデーションと言っても様々な手法があるので、場合によって使い分けられるようにしていきたいですね。