Pythonによる機械学習モデルの評価と選択:実践ガイド

機械学習プロジェクトにおいて、モデルの性能を客観的かつ再現性のある方法で評価することは、単なる精度向上を超えて、信頼性・一般化能力・業務適用可能性を担保する鍵となります。本稿では、scikit-learnを用いた代表的な評価戦略と指標を体系的に解説し、実装コードを再構成して提示します。すべての例はPima Indians Diabetesデータセット(8次元特徴+バイナリラベル)を対象とし、各手法の目的・制約・適切な使用場面を明示します。

データ分割戦略:過学習回避の基盤

訓練データをそのまま評価に用いると、モデルが「記憶」しただけの偽の高スコアを生むリスクがあります。真の汎化性能を測るには、独立した検証データが必要です。以下はその代表的手法です。

ホールドアウト法(67:33分割)

最も直感的で高速な手法。データを訓練用と検証用に固定比率で分割します。大規模かつバランスの取れたデータで有効ですが、分割のランダム性により結果がばらつく可能性があります。

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

# データ読み込み
df = pd.read_csv('pima.csv', names=['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class'])
X, y = df.iloc[:, :-1], df.iloc[:, -1]

# 乱数シードを固定して再現性を確保
X_train, X_val, y_train, y_val = train_test_split(
    X, y, test_size=0.33, random_state=42, stratify=y
)

# モデル訓練・評価
clf = LogisticRegression(max_iter=2000)
clf.fit(X_train, y_train)
accuracy = clf.score(X_val, y_val)
print(f"ホールドアウト精度: {accuracy:.3f}")

K-Fold交差検証(K=10)

データをK個のサブセットに均等分割し、各foldを1回ずつ検証セットとして使い、残りを訓練に用います。結果はK回のスコアの平均値で表され、分散も算出可能。小〜中規模データで推奨される標準手法です。

from sklearn.model_selection import StratifiedKFold, cross_val_score

cv_strategy = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
scores = cross_val_score(LogisticRegression(max_iter=2000), X, y, cv=cv_strategy, scoring='accuracy')
print(f"10-Fold CV 平均精度: {scores.mean():.3f} ± {scores.std():.3f}")

留一法(LOO-CV)

サンプル数Nに対してN回の検証を実行。各サンプルを1度だけ検証に使い、残りを訓練に用います。バイアスが極めて小さく、再現性も高い一方、計算コストがO(N)となるため、N > 1000程度では非現実的です。

from sklearn.model_selection import LeaveOneOut

loo = LeaveOneOut()
loo_scores = cross_val_score(LogisticRegression(max_iter=2000), X, y, cv=loo, scoring='accuracy')
print(f"LOO-CV 精度: {loo_scores.mean():.3f}")

シャッフル分割(ShuffleSplit)

指定回数(例:10回)だけ、毎回異なるランダムな分割を繰り返す手法。各分割は同じ割合(例:33%検証)で行われます。ホールドアウトの安定性を高めたい場合に有効です。

from sklearn.model_selection import ShuffleSplit

ss = ShuffleSplit(n_splits=10, test_size=0.33, random_state=42)
ss_scores = cross_val_score(LogisticRegression(max_iter=2000), X, y, cv=ss, scoring='accuracy')
print(f"ShuffleSplit 平均精度: {ss_scores.mean():.3f} ± {ss_scores.std():.3f}")

多角的な評価指標:精度だけでは足りない

分類タスクでは、単一の「正答率」では不十分です。特にクラス不均衡(例:糖尿病陽性率25%)下では、誤った判断を招きます。以下は補完的な指標群です。

混同行列と分類レポート

予測結果を細かく分解し、True Positive(TP)、False Negative(FN)などの構成要素を可視化。F1スコアはPrecisionとRecallの調和平均で、両者のバランスを重視します。

from sklearn.metrics import classification_report, confusion_matrix
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
clf = LogisticRegression(max_iter=2000).fit(X_train, y_train)
y_pred = clf.predict(X_test)

print("混同行列:")
print(confusion_matrix(y_test, y_pred))
print("\n詳細レポート:")
print(classification_report(y_test, y_pred))

ロジスティック損失(Log Loss)

確率予測の品質を評価。モデルが出力したクラス確率が真のラベルとどれだけ一致しているかを対数スケールで計測。値が小さいほど優れています。

from sklearn.model_selection import cross_val_score

logloss_scores = cross_val_score(
    LogisticRegression(max_iter=2000), 
    X, y, 
    cv=StratifiedKFold(10, shuffle=True, random_state=42),
    scoring='neg_log_loss'
)
print(f"Log Loss: {-logloss_scores.mean():.3f} ± {logloss_scores.std():.3f}")

AUC-ROCスコア

分類閾値を変化させたときのTrue Positive Rate(感度)とFalse Positive Rate(1−特異度)のトレードオフを曲線で表現。曲線下の面積(AUC)は0.5(ランダム)から1.0(完全分離)の範囲で、値が高いほど識別能力が優れています。

auc_scores = cross_val_score(
    LogisticRegression(max_iter=2000), 
    X, y, 
    cv=StratifiedKFold(10, shuffle=True, random_state=42),
    scoring='roc_auc'
)
print(f"AUC-ROC: {auc_scores.mean():.3f} ± {auc_scores.std():.3f}")

代表的分類アルゴリズムの比較

同一データセット上で複数のアルゴリズムを公平に比較するには、同一の交差検証設定を用いることが必須です。以下は6つの代表的分類器のベンチマーク例です。

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
import matplotlib.pyplot as plt

# アルゴリズム辞書(パラメータは適切に調整)
algorithms = {
    'Logistic Regression': LogisticRegression(max_iter=2000),
    'LDA': LinearDiscriminantAnalysis(),
    'KNN': KNeighborsClassifier(n_neighbors=5),
    'Decision Tree': DecisionTreeClassifier(max_depth=5, random_state=42),
    'Naive Bayes': GaussianNB(),
    'SVM (RBF)': SVC(kernel='rbf', gamma='scale', random_state=42)
}

# 各アルゴリズムを10-Fold CVで評価
results_dict = {}
for name, model in algorithms.items():
    scores = cross_val_score(model, X, y, cv=StratifiedKFold(10, shuffle=True, random_state=42), scoring='accuracy')
    results_dict[name] = scores

# 箱ひげ図で可視化
plt.figure(figsize=(10, 6))
plt.boxplot(list(results_dict.values()), labels=list(results_dict.keys()))
plt.title('Algorithm Comparison via 10-Fold CV (Accuracy)')
plt.ylabel('Accuracy')
plt.xticks(rotation=15)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# 数値結果の表示
for name, scores in results_dict.items():
    print(f"{name:20s}: {scores.mean():.3f} ± {scores.std():.3f}")

回帰タスク向けの指標(補足)

連続値予測では、以下の指標が主に用いられます:

  • MAE(平均絶対誤差):誤差の絶対値の平均。外れ値にロバスト。
  • MSE(平均二乗誤差):誤差の二乗の平均。大きな誤差を強くペナルティ。
  • R²(決定係数):モデルが説明できる応答変数の分散の割合。1に近いほど良い。

これらの指標は、cross_val_scorescoringパラメータに'neg_mean_absolute_error''r2'を指定することで同様に評価可能です。

タグ: Scikit-learn cross-validation classification-metrics log-loss roc-auc

5月30日 01:04 投稿