SGDClassifier.score()で失敗しない!よくあるエラーとトラブルシューティング【scikit-learn】

2025-05-26

linear_model.SGDClassifier.score() とは?

sklearn.linear_model.SGDClassifier は、確率的勾配降下法 (Stochastic Gradient Descent; SGD) を用いて線形分類モデル(例: 線形SVM、ロジスティック回帰など)を学習するクラスです。

この SGDClassifier オブジェクトの score() メソッドは、モデルの性能を評価するためのスコアを計算するために使われます。具体的には、分類問題における正解率 (Accuracy) を返します。

使い方

通常、モデルを訓練データで学習させた後、未知のデータ(テストデータなど)に対してどれくらいの精度で分類できるかを確認するために score() メソッドを使用します。

from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification # サンプルデータ作成用

# 1. データの準備
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 2. モデルのインスタンス化
# loss='hinge'で線形SVMとして動作します
clf = SGDClassifier(loss='hinge', random_state=42)

# 3. モデルの学習 (訓練データでfitする)
clf.fit(X_train, y_train)

# 4. モデルの評価 (テストデータでscoreを計算する)
accuracy = clf.score(X_test, y_test)

print(f"テストデータでの正解率 (Accuracy): {accuracy:.4f}")

score() メソッドの引数

score(X, y, sample_weight=None)

  • sample_weight: array-like, shape (n_samples,), default=None 各サンプルの重要度(重み)を指定するオプションの引数です。もし指定された場合、正解率の計算にこれらの重みが考慮されます。
  • y: array-like, shape (n_samples,) X に対応する正解ラベル(目的変数)です。
  • X: array-like, shape (n_samples, n_features) 評価に使用する特徴量データ(入力データ)です。
  • score: float 与えられたデータセット Xy に対するモデルの平均正解率(Accuracy)が返されます。つまり、正しく分類されたサンプルの割合です。


スコアが異常に低い(モデルの性能が悪い)

これは最も一般的な問題であり、score() メソッド自体がエラーを出すわけではありませんが、期待通りの性能が出ない場合に原因を探る必要があります。

原因と対策

  • 特徴量の質 (Feature Quality):

    • 原因: モデルが学習できる有用な情報が特徴量に不足している場合、どんなに良いモデルやハイパーパラメータを使っても性能は上がりません。
    • トラブルシューティング:
      • 特徴量エンジニアリング: 既存の特徴量から新しい有用な特徴量を作成する。
      • 特徴量選択: 不要な特徴量を除去する。
      • ドメイン知識の活用: 問題領域の専門知識を活かして、より適切な特徴量を見つける。
  • クラスの不均衡 (Class Imbalance):

    • 原因: データセット内のクラスの割合が大きく異なる場合(例: クラスAが90%、クラスBが10%)、モデルは多数派のクラスに偏って学習し、少数派のクラスに対する予測性能が悪くなることがあります。score() は単純な正解率なので、この不均衡が隠れてしまう可能性があります。
    • トラブルシューティング:
      • class_weight='balanced' パラメータを使用する: SGDClassifier(class_weight='balanced', ...)
      • 少数派クラスのオーバーサンプリング (SMOTEなど) や、多数派クラスのアンダーサンプリングを行う。
      • 正解率だけでなく、precision, recall, f1-score, ROC AUC など、不均衡データに適した評価指標を確認する。
  • 十分な訓練エポック数ではない (Insufficient max_iter):

    • 原因: max_iter の値が小さすぎると、モデルが収束する前に学習が終了してしまい、性能が低くなります。
    • トラブルシューティング: max_iter の値を増やしてみてください。また、tol (収束の閾値) を設定することで、損失が改善しなくなったときに自動的に停止させることもできます。
  • データのシャッフル不足 (Data Shuffling):

    • 原因: 確率的勾配降下法は、データがシャッフルされていることを前提としています。もしデータが特定の順序で並んでいる場合(例: 最初はクラスAのデータばかり、次にクラスBのデータばかり)、SGDは局所最適解に陥りやすくなります。
    • トラブルシューティング: SGDClassifier はデフォルトで shuffle=True になっていますが、fit メソッドに渡す前にデータセット全体をシャッフルすることをお勧めします。train_test_split はデフォルトでシャッフルを行います。
  • ハイパーパラメータの不適切な設定 (Hyperparameter Tuning):

    • 原因: SGDClassifier は、alpha (正則化の強さ)、max_iter (エポック数)、eta0 (初期学習率) など、多くのハイパーパラメータを持ちます。これらの値が不適切だと、モデルが過学習(訓練データに適合しすぎ)したり、未学習(十分に学習されていない)になったりします。
    • トラブルシューティング: GridSearchCVRandomizedSearchCV を使って、最適なハイパーパラメータの組み合わせを探索してください。
      from sklearn.model_selection import GridSearchCV
      param_grid = {
          'alpha': [0.0001, 0.001, 0.01],
          'max_iter': [500, 1000, 2000],
          'loss': ['hinge', 'log_loss']
      }
      grid_search = GridSearchCV(SGDClassifier(random_state=42), param_grid, cv=5, scoring='accuracy')
      grid_search.fit(X_train_scaled, y_train)
      
      best_clf = grid_search.best_estimator_
      accuracy = best_clf.score(X_test_scaled, y_test)
      print(f"最適なハイパーパラメータ: {grid_search.best_params_}")
      
  • データのスケーリング不足 (Feature Scaling):

    • 原因: SGDベースのモデルは、特徴量のスケール(値の範囲)に非常に敏感です。特徴量間でスケールが大きく異なる場合、最適化アルゴリズムが効率的に機能せず、モデルの学習がうまくいかないことがあります。
    • トラブルシューティング: データを前処理してスケーリングすることが非常に重要です。StandardScalerMinMaxScaler などを使用して、訓練データとテストデータの両方を同じ方法でスケーリングしてください。
      from sklearn.preprocessing import StandardScaler
      scaler = StandardScaler()
      X_train_scaled = scaler.fit_transform(X_train)
      X_test_scaled = scaler.transform(X_test) # テストデータにはfit_transformではなくtransformを使う
      
      clf = SGDClassifier(...)
      clf.fit(X_train_scaled, y_train)
      accuracy = clf.score(X_test_scaled, y_test)
      
    • パイプラインの使用: Pipeline を使うと、スケーリングとモデル学習を連結でき、一貫性を保ちやすくなります。
      from sklearn.pipeline import make_pipeline
      pipeline = make_pipeline(StandardScaler(), SGDClassifier(loss='hinge', random_state=42))
      pipeline.fit(X_train, y_train)
      accuracy = pipeline.score(X_test, y_test)
      

fit メソッドを呼び出していない

エラー例: NotFittedError: This SGDClassifier instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator.

原因: score() メソッドは、モデルが訓練データで学習済みであることを前提としています。fit() メソッドを呼び出す前に score() を呼び出すと、このエラーが発生します。

トラブルシューティング: SGDClassifier のインスタンスを作成した後、必ず訓練データ (X_train, y_train) を使って fit() メソッドを呼び出してください。

clf = SGDClassifier(...)
# clf.score(X_test, y_test) # ここで呼び出すとエラー
clf.fit(X_train, y_train) # まずfit()を呼び出す
accuracy = clf.score(X_test, y_test) # その後score()を呼び出す

データの次元不一致 (Shape Mismatch)

エラー例: ValueError: X has 10 features, but this SGDClassifier is expecting 20 features. ValueError: Found input variables with inconsistent numbers of samples: [N, M]

原因: fit() メソッドでモデルを学習させた訓練データ X_train と、score() メソッドに渡すテストデータ X_test の特徴量数(列数)が一致しない場合に発生します。また、Xy のサンプル数(行数)が一致しない場合にも発生します。

トラブルシューティング:

  • Xy の行数が常に一致することを確認してください。train_test_split を正しく使っていれば、通常この問題は発生しません。
  • 訓練データとテストデータで、使用する特徴量の数が同じであることを確認してください。前処理(特徴量選択、特徴量エンジニアリングなど)を行った場合、訓練データとテストデータで同じ変換が適用されていることを確認してください。

y に単一のクラスしか含まれていない

エラー例: ValueError: The number of classes has to be greater than one; got 1 class.

原因: 分類問題では、少なくとも2つの異なるクラスが存在する必要があります。score() メソッドに渡す y (正解ラベル)に、1種類のクラスしか含まれていない場合に発生します。これは、テストデータセットが非常に小さく、たまたま1つのクラスしか含まれていない場合や、データ前処理の誤りによってラベルが単一の値になってしまった場合に起こり得ます。

トラブルシューティング:

  • np.unique(y_test) を実行して、テストデータのクラスの数を確認してください。
  • テストデータ (X_test, y_test) が複数のクラスのサンプルを適切に含んでいるか確認してください。特に、train_test_splitstratify 引数を使用して、クラスの分布を保持するように分割することをお勧めします。
    from sklearn.model_selection import train_test_split
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
    

バージョンの不整合 (Version Mismatch)

エラー例: AttributeError: 'SGDClassifier' object has no attribute 'n_iter' (これは古いバージョンで max_iter の代わりに n_iter が使われていた時の例です)

原因: scikit-learn のバージョンが古すぎる、または使用しているコードが現在のバージョンと互換性がない場合に発生することがあります。

トラブルシューティング: scikit-learn を最新バージョンにアップデートしてください。

pip install --upgrade scikit-learn


例1: 基本的な使い方 - モデルの学習と評価

この例では、合成データセットを作成し、それを訓練データとテストデータに分割します。その後、SGDClassifier を訓練し、score() メソッドを使ってテストデータでの正解率を評価します。

import numpy as np
from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score # score()メソッドの内部と同じ計算

print("--- 例1: 基本的な使い方 ---")

# 1. サンプルデータの生成
# make_classificationで2クラス分類問題のデータを生成
# n_samples: サンプル数、n_features: 特徴量数、n_classes: クラス数
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)

# 2. データを訓練セットとテストセットに分割
# test_size=0.2 はデータの20%をテストに使用することを意味
# stratify=y はクラスの割合を訓練セットとテストセットで同じにするための設定(不均衡データに有効)
# random_state は結果の再現性を保証
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

print(f"訓練データの形状: X_train={X_train.shape}, y_train={y_train.shape}")
print(f"テストデータの形状: X_test={X_test.shape}, y_test={y_test.shape}")

# 3. データの前処理: 特徴量のスケーリング
# SGDClassifierは特徴量のスケールに敏感なため、StandardScalerで標準化(平均0、標準偏差1に変換)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # 訓練データでfit(平均と標準偏差を計算)し、transform(変換)
X_test_scaled = scaler.transform(X_test)     # テストデータは訓練データで計算された平均と標準偏差を使ってtransformのみ

# 4. SGDClassifierモデルのインスタンス化と訓練
# loss='hinge': 線形SVMとして動作
# max_iter: 最大エポック数(データセット全体を何回繰り返すか)
# tol: 損失の改善がこの値以下になったら収束とみなす閾値
# random_state: 結果の再現性を保証
clf = SGDClassifier(loss='hinge', max_iter=1000, tol=1e-3, random_state=42)

# モデルの訓練
clf.fit(X_train_scaled, y_train)

# 5. モデルの評価: score()メソッドを使用
# テストデータ(スケーリング済み)と対応する正解ラベルを渡す
accuracy_score_from_method = clf.score(X_test_scaled, y_test)

print(f"\nSGDClassifier.score() によるテストデータでの正解率: {accuracy_score_from_method:.4f}")

# 参考: 手動で正解率を計算する場合(score()メソッドが何をしているかを示す)
y_pred = clf.predict(X_test_scaled)
manual_accuracy = accuracy_score(y_test, y_pred)
print(f"手動で計算したテストデータでの正解率: {manual_accuracy:.4f}")

例2: fit メソッドを呼び出さなかった場合のエラー

score() メソッドを呼び出す前にモデルが訓練されていない場合に発生する NotFittedError の例です。

from sklearn.linear_model import SGDClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

print("\n--- 例2: fitメソッドを呼び出さなかった場合のエラー ---")

X, y = make_classification(n_samples=100, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

clf = SGDClassifier(random_state=42)

try:
    # fit() を呼び出す前に score() を試みる
    _ = clf.score(X_test_scaled, y_test)
except Exception as e:
    print(f"エラーが発生しました: {e}")
    print("解決策: score()を呼び出す前にclf.fit(X_train_scaled, y_train)を呼び出してください。")

# 正しい流れ
clf.fit(X_train_scaled, y_train)
correct_score = clf.score(X_test_scaled, y_test)
print(f"正しくfitした後のスコア: {correct_score:.4f}")

例3: パイプラインを使ったスケーリングと評価

Pipeline を使用することで、前処理(スケーリング)とモデル学習を結合し、コードをより簡潔かつエラーが少なく管理できます。

from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

print("\n--- 例3: パイプラインを使ったスケーリングと評価 ---")

X, y = make_classification(n_samples=500, n_features=10, n_classes=2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)

# make_pipeline で StandardScaler と SGDClassifier を連結
# これにより、fitが呼ばれたときにまずScalerがfit_transformされ、
# その後変換されたデータでSGDClassifierがfitされる
# scoreが呼ばれたときは、まずScalerがtransformされ、その後SGDClassifierがscoreを計算
pipeline = make_pipeline(StandardScaler(), SGDClassifier(loss='log_loss', max_iter=1000, tol=1e-3, random_state=42))

# パイプライン全体を訓練
pipeline.fit(X_train, y_train)

# パイプライン全体を評価
# X_test は生データ(スケーリング前)を渡しても、パイプライン内部で自動的にスケーリングされる
accuracy_from_pipeline = pipeline.score(X_test, y_test)

print(f"パイプラインによるテストデータでの正解率: {accuracy_from_pipeline:.4f}")

例4: クラスの不均衡と class_weight

データセットにクラスの不均衡がある場合、score() で算出される正解率は見かけ上高くても、少数派クラスの予測性能が悪い可能性があります。class_weight='balanced' オプションを使用することで、この問題を緩和できます。

from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report # より詳細な評価指標

print("\n--- 例4: クラスの不均衡と class_weight ---")

# 非常に不均衡なデータセットを生成 (クラス0が900個、クラス1が100個)
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, 
                           weights=[0.9, 0.1], random_state=42)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

print(f"訓練データでのクラス分布: クラス0: {np.sum(y_train == 0)}, クラス1: {np.sum(y_train == 1)}")
print(f"テストデータでのクラス分布: クラス0: {np.sum(y_test == 0)}, クラス1: {np.sum(y_test == 1)}")

# 1. class_weight なしで学習
clf_no_weight = SGDClassifier(loss='log_loss', max_iter=1000, tol=1e-3, random_state=42)
clf_no_weight.fit(X_train_scaled, y_train)
accuracy_no_weight = clf_no_weight.score(X_test_scaled, y_test)
print(f"\nclass_weight なしでの正解率: {accuracy_no_weight:.4f}")
y_pred_no_weight = clf_no_weight.predict(X_test_scaled)
print("class_weight なしでの分類レポート:")
print(classification_report(y_test, y_pred_no_weight)) # 少数派クラスのRecallが低い場合がある

# 2. class_weight='balanced' で学習
clf_balanced_weight = SGDClassifier(loss='log_loss', max_iter=1000, tol=1e-3, random_state=42, class_weight='balanced')
clf_balanced_weight.fit(X_train_scaled, y_train)
accuracy_balanced_weight = clf_balanced_weight.score(X_test_scaled, y_test)
print(f"\nclass_weight='balanced' での正解率: {accuracy_balanced_weight:.4f}")
y_pred_balanced_weight = clf_balanced_weight.predict(X_test_scaled)
print("class_weight='balanced' での分類レポート:")
print(classification_report(y_test, y_pred_balanced_weight)) # 少数派クラスのRecallが改善される傾向がある


score() メソッドが返すのは単一の正解率ですが、分類問題では、より多角的な視点からモデルの性能を評価することが重要です。

sklearn.metrics モジュール内の様々な評価指標

scikit-learnsklearn.metrics モジュールには、分類問題の評価に役立つ多くの関数が用意されています。これらを使うには、まず predict() メソッドで予測結果を得てから、評価関数に渡します。

a. 適合率 (Precision), 再現率 (Recall), F1スコア (F1-score)

  • 目的: 特にクラスが不均衡なデータセットにおいて、正解率だけでは見落とされがちなモデルの性能を評価します。
    • 適合率 (Precision): 陽性と予測されたもののうち、実際に陽性であった割合。「誤検知の少なさ」を示します。
    • 再現率 (Recall): 実際に陽性であるもののうち、陽性と予測できた割合。「見逃しの少なさ」を示します。
    • F1スコア (F1-score): 適合率と再現率の調和平均。両方のバランスが良いかを示します。

b. 混同行列 (Confusion Matrix)

  • 使い方:
    from sklearn.metrics import confusion_matrix
    import seaborn as sns
    import matplotlib.pyplot as plt
    
    cm = confusion_matrix(y_test, y_pred)
    print("\n混同行列:")
    print(cm)
    
    # 可視化
    plt.figure(figsize=(6, 5))
    sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", cbar=False,
                xticklabels=['予測 0', '予測 1'], yticklabels=['実際 0', '実際 1'])
    plt.xlabel('予測ラベル')
    plt.ylabel('真のラベル')
    plt.title('混同行列')
    plt.show()
    
  • 目的: 予測と実際のクラスの組み合わせを可視化し、モデルがどのクラスを正しく/誤って分類しているかを詳細に把握します。
  • 使い方:
    from sklearn.metrics import roc_curve, auc, roc_auc_score
    
    # predict_proba() はクラスの予測確率を返す (log_loss使用時のみ)
    # hinge loss の場合は decision_function() を使用し、ROC曲線には生の決定値を使う
    if hasattr(clf, 'predict_proba'): # 確率を出力できる場合
        y_pred_proba = clf.predict_proba(X_test_scaled)[:, 1] # 陽性クラスの確率
    else: # 決定関数値を使用する場合 (例: hinge loss)
        y_pred_proba = clf.decision_function(X_test_scaled) # 決定関数値
    
    fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
    roc_auc = auc(fpr, tpr)
    # または roc_auc_score(y_test, y_pred_proba) を直接使う
    
    print(f"\nROC AUCスコア: {roc_auc:.4f}")
    
    plt.figure(figsize=(7, 6))
    plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
    plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('偽陽性率 (False Positive Rate)')
    plt.ylabel('真陽性率 (True Positive Rate)')
    plt.title('Receiver Operating Characteristic (ROC) Curve')
    plt.legend(loc="lower right")
    plt.show()
    
  • 目的: 分類モデルが異なる閾値設定でどれだけうまくクラスを区別できるかを評価します。特に、陽性クラスの予測確率を返すモデル(例: loss='log_loss' を使った SGDClassifier)に適しています。AUCは、モデルがランダムに選んだ陽性サンプルを、ランダムに選んだ陰性サンプルよりも高い確率でランク付けする確率を示します。

クロスバリデーション (Cross-Validation)

  • 目的: データ分割(訓練/テスト)のランダム性による評価結果の偏りを避け、モデルの汎化性能をより堅牢に評価します。データセットを複数の「フォールド」に分割し、それぞれをテストセットとして使用しながら、残りを訓練データとしてモデルを評価します。

グリッドサーチ/ランダムサーチでの評価 (GridSearchCV/RandomizedSearchCV)

  • 使い方:
    from sklearn.model_selection import GridSearchCV
    from sklearn.linear_model import SGDClassifier
    from sklearn.datasets import make_classification
    from sklearn.preprocessing import StandardScaler
    from sklearn.pipeline import Pipeline
    
    print("\n--- GridSearchCVによるハイパーパラメータチューニングと評価 ---")
    
    X, y = make_classification(n_samples=500, n_features=10, n_classes=2, random_state=42)
    
    # パイプライン内でハイパーパラメータを定義する場合、ステップ名__パラメータ名とする
    pipeline = Pipeline([
        ('scaler', StandardScaler()),
        ('sgd', SGDClassifier(random_state=42))
    ])
    
    param_grid = {
        'sgd__loss': ['hinge', 'log_loss'],
        'sgd__alpha': [0.0001, 0.001],
        'sgd__max_iter': [500, 1000]
    }
    
    # scoring='accuracy' は score() メソッドと同じ正解率を評価基準とする
    # 他の指標(例: 'f1', 'roc_auc')も指定可能
    grid_search = GridSearchCV(pipeline, param_grid, cv=3, scoring='accuracy', n_jobs=-1) # n_jobs=-1 で全コア使用
    grid_search.fit(X, y)
    
    print(f"\n最適なハイパーパラメータ: {grid_search.best_params_}")
    print(f"ベストスコア (accuracy): {grid_search.best_score_:.4f}")
    
    # 最適なモデルを取得し、別途テストデータで評価することも可能
    # best_model = grid_search.best_estimator_
    # X_test, y_test を分割した場合
    # test_accuracy = best_model.score(X_test, y_test)
    # print(f"最適モデルのテストデータでの正解率: {test_accuracy:.4f}")
    
  • 目的: モデルのハイパーパラメータを最適化しながら、同時に最適な評価指標に基づいてモデルを選択します。scoring パラメータで評価指標を指定できます。

カスタムスコア関数 (Custom Scoring Function)

  • 使い方: sklearn.metrics.make_scorer を使って、独自の関数を scikit-learn が認識できるスコア関数に変換します。
    from sklearn.metrics import make_scorer
    from sklearn.linear_model import SGDClassifier
    from sklearn.model_selection import cross_val_score
    from sklearn.datasets import make_classification
    from sklearn.preprocessing import StandardScaler
    import numpy as np
    
    print("\n--- カスタムスコア関数 ---")
    
    X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)
    
    # 例: 誤分類のうち、特定のクラス (例: クラス1) を誤って予測した場合に大きなペナルティを与えるカスタム損失
    # この例では、スコアなので、高いほど良いとする関数を作成
    def custom_balanced_accuracy_scorer(y_true, y_pred):
        # クラス0の精度とクラス1の精度を個別に計算し、その平均を取る
        # これにより、不均衡なデータでもバランスの取れた評価が可能になる
        tp_0 = np.sum((y_true == 0) & (y_pred == 0))
        tn_0 = np.sum((y_true == 1) & (y_pred == 0)) # クラス0にとってのTN
        fp_0 = np.sum((y_true == 1) & (y_pred == 0)) # クラス0にとってのFP (実際1を0と予測)
        fn_0 = np.sum((y_true == 0) & (y_pred == 1)) # クラス0にとってのFN (実際0を1と予測)
    
        tp_1 = np.sum((y_true == 1) & (y_pred == 1))
        tn_1 = np.sum((y_true == 0) & (y_pred == 1)) # クラス1にとってのTN
        fp_1 = np.sum((y_true == 0) & (y_pred == 1)) # クラス1にとってのFP (実際0を1と予測)
        fn_1 = np.sum((y_true == 1) & (y_pred == 0)) # クラス1にとってのFN (実際1を0と予測)
    
        # クラス0の精度 (真陽性率)
        recall_0 = tp_0 / (tp_0 + fn_0) if (tp_0 + fn_0) > 0 else 0
        # クラス1の精度 (真陽性率)
        recall_1 = tp_1 / (tp_1 + fn_1) if (tp_1 + fn_1) > 0 else 0
    
        return (recall_0 + recall_1) / 2 # 平均再現率
    
    # make_scorerを使ってカスタムスコア関数を作成
    # greater_is_better=True は、このスコアが高いほど良いことを意味
    my_scorer = make_scorer(custom_balanced_accuracy_scorer, greater_is_better=True)
    
    clf = SGDClassifier(loss='log_loss', max_iter=1000, tol=1e-3, random_state=42)
    clf.fit(X_train_scaled, y_train)
    
    # score() メソッドはカスタムスコアを受け付けないため、cross_val_score などを使う
    # または手動で計算
    y_pred = clf.predict(X_test_scaled)
    custom_score_val = my_scorer(clf, X_test_scaled, y_test) # make_scorerで作ったscorerは(estimator, X, y_true)を受け取る
    
    print(f"\nカスタムスコア関数によるテストスコア: {custom_score_val:.4f}")
    
    # クロスバリデーションでカスタムスコアを使う場合
    # cv_scores_custom = cross_val_score(clf, X_scaled, y, cv=5, scoring=my_scorer)
    # print(f"5-Fold Cross-Validation (カスタムスコア): {cv_scores_custom.mean():.4f} (+/- {cv_scores_custom.std():.4f})")
    
  • 目的: scikit-learn の標準的な評価指標では対応できない、独自の評価基準でモデルを評価したい場合に使用します。