Skip to content

Sklearnの混同行列:分類モデルを評価する方法

Updated on

分類モデルが「精度(accuracy)95%」と報告したので本番投入した。ところが、実際に重要な「陽性ケース」――不正取引、疾病診断、不良品――の80%を見逃していることが判明する。accuracyだけでは、モデルがどこで・どのように失敗しているかという重要な情報が隠れてしまいます。

accuracyという単一の数値は、あらゆる種類の誤りを1つの指標に押しつぶします。たとえばスパムが全体の5%しかない場合、スパムをすべて素通りさせ、正規メールをすべて正しく分類するスパムフィルタでも高いaccuracyになり得ます。必要なのは全体像です。モデルがどれだけ陽性を捕捉できているか、陰性をどれだけ誤分類しているか、そして誤りが具体的にどこに集中しているかを把握しなければなりません。

混同行列(confusion matrix)は、モデル性能を4つの要素――真陽性(TP)、真陰性(TN)、偽陽性(FP)、偽陰性(FN)――に分解します。さらにprecision、recall、F1-scoreのような派生指標と組み合わせることで、モデルの「何が正しく、何が間違っているか」を行動可能な形で理解できます。Scikit-learnは、この分析を簡単にするために confusion_matrixclassification_reportConfusionMatrixDisplay を提供しています。

📚

混同行列とは?

混同行列は、分類モデルの「予測ラベル」と「実際のラベル」を比較する表です。二値分類の場合、2x2のグリッドになります。

Predicted PositivePredicted Negative
Actual PositiveTrue Positive (TP)False Negative (FN)
Actual NegativeFalse Positive (FP)True Negative (TN)

各セルは、そのカテゴリに該当するサンプル数を数えます。

  • True Positive (TP): モデルが陽性と予測し、実際にも陽性。正解。
  • True Negative (TN): モデルが陰性と予測し、実際にも陰性。正解。
  • False Positive (FP): モデルが陽性と予測したが、実際は陰性。第I種過誤(Type I error)。
  • False Negative (FN): モデルが陰性と予測したが、実際は陽性。第II種過誤(Type II error)。

Sklearnで基本の混同行列

from sklearn.metrics import confusion_matrix
import numpy as np
 
# Actual and predicted labels
y_true = [1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1]
y_pred = [1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1]
 
cm = confusion_matrix(y_true, y_pred)
print(cm)
# [[5 1]
#  [2 7]]

この出力の読み方:sklearnは、行0 = 実際の陰性、行1 = 実際の陽性、という順で行列を並べます。

Predicted 0Predicted 1
Actual 0TN = 5FP = 1
Actual 1FN = 2TP = 7

つまり、モデルは陰性を5件、陽性を7件正しく識別し、偽陽性が1件、偽陰性が2件発生しています。

個々の値を取り出す

from sklearn.metrics import confusion_matrix
 
y_true = [1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1]
y_pred = [1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1]
 
cm = confusion_matrix(y_true, y_pred)
tn, fp, fn, tp = cm.ravel()
 
print(f"True Negatives:  {tn}")
print(f"False Positives: {fp}")
print(f"False Negatives: {fn}")
print(f"True Positives:  {tp}")
# True Negatives:  5
# False Positives: 1
# False Negatives: 2
# True Positives:  7

Precision、Recall、F1-Score、Accuracy

これらの指標は混同行列から直接導出できます。

MetricFormulaWhat It Answers
Accuracy(TP + TN) / (TP + TN + FP + FN)全予測のうち、いくつ正解だったか?
PrecisionTP / (TP + FP)陽性と予測したもののうち、実際に陽性だった割合は?
Recall (Sensitivity)TP / (TP + FN)実際の陽性のうち、どれだけ捕捉できたか?
SpecificityTN / (TN + FP)実際の陰性のうち、どれだけ正しく陰性と判定できたか?
F1-Score2 * (Precision * Recall) / (Precision + Recall)precisionとrecallの調和平均
from sklearn.metrics import (
    accuracy_score, precision_score, recall_score, f1_score
)
 
y_true = [1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1]
y_pred = [1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1]
 
print(f"Accuracy:  {accuracy_score(y_true, y_pred):.4f}")
print(f"Precision: {precision_score(y_true, y_pred):.4f}")
print(f"Recall:    {recall_score(y_true, y_pred):.4f}")
print(f"F1-Score:  {f1_score(y_true, y_pred):.4f}")
# Accuracy:  0.8000
# Precision: 0.8750
# Recall:    0.7778
# F1-Score:  0.8235

PrecisionとRecallのどちらを優先すべきか

ScenarioPrioritizeWhy
スパム検知Precision偽陽性(正規メールがスパム扱い)はユーザーの不満につながる
疾病スクリーニングRecall偽陰性(病気の見逃し)は危険
不正検知Recall不正の見逃しは、誤検知の調査よりコストが高い
検索エンジンの結果Precision無関係な結果はユーザー体験を損なう
製造業の不良検知Recall不良品が顧客に届くコストが大きい
コンテンツ推薦Precision無関係な推薦はエンゲージメントを下げる

Classification Report

sklearnの classification_report は、各クラスごとのprecision、recall、F1-score、support(実際の出現数)を1回の呼び出しで計算します。

from sklearn.metrics import classification_report
 
y_true = [1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1]
y_pred = [1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1]
 
print(classification_report(y_true, y_pred, target_names=['Negative', 'Positive']))

Output:

              precision    recall  f1-score   support

    Negative       0.71      0.83      0.77         6
    Positive       0.88      0.78      0.82         9

    accuracy                           0.80        15
   macro avg       0.80      0.81      0.80        15
weighted avg       0.81      0.80      0.80        15
  • macro avg: クラス間の単純平均(重みなし)。すべてのクラスを同等に扱う。
  • weighted avg: supportで重み付けした平均。クラス不均衡を考慮する。
  • support: 各クラスに属する実サンプル数。

混同行列を可視化する

ConfusionMatrixDisplayを使う

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
 
# Load and split data
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=42
)
 
# Train model
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
 
# Plot confusion matrix
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(
    confusion_matrix=cm,
    display_labels=data.target_names
)
disp.plot(cmap='Blues')
plt.title('Breast Cancer Classification')
plt.tight_layout()
plt.savefig('confusion_matrix.png', dpi=150)
plt.show()

Seaborn Heatmapを使う

より細かくカスタマイズしたい場合は、seabornを直接使います。

from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
 
# Load, split, train
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=42
)
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
 
# Create confusion matrix
cm = confusion_matrix(y_test, y_pred)
 
# Plot with seaborn
plt.figure(figsize=(8, 6))
sns.heatmap(
    cm,
    annot=True,
    fmt='d',
    cmap='Blues',
    xticklabels=data.target_names,
    yticklabels=data.target_names,
    square=True,
    linewidths=0.5
)
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.title('Confusion Matrix - Breast Cancer Classification')
plt.tight_layout()
plt.savefig('confusion_matrix_seaborn.png', dpi=150)
plt.show()

正規化(Normalized)混同行列

生の件数は、クラスサイズが異なると誤解を招くことがあります。正規化すると「割合」を見られます。

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
 
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=42
)
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
 
# Normalized confusion matrix (by true labels)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
 
# Raw counts
ConfusionMatrixDisplay.from_predictions(
    y_test, y_pred,
    display_labels=data.target_names,
    cmap='Blues',
    ax=axes[0]
)
axes[0].set_title('Raw Counts')
 
# Normalized (rows sum to 1)
ConfusionMatrixDisplay.from_predictions(
    y_test, y_pred,
    display_labels=data.target_names,
    normalize='true',
    cmap='Blues',
    values_format='.2%',
    ax=axes[1]
)
axes[1].set_title('Normalized by True Label')
 
plt.tight_layout()
plt.savefig('confusion_matrix_normalized.png', dpi=150)
plt.show()

normalize パラメータは3つのオプションを受け取れます。

ValueNormalizationUse Case
'true'行の合計が1(実際のクラス件数で割る)クラスごとのrecallを見る
'pred'列の合計が1(予測クラス件数で割る)クラスごとのprecisionを見る
'all'全セルの合計が1(総数で割る)全体分布を見る

多クラス(Multi-Class)混同行列

混同行列は自然に2クラス以上へ拡張できます。各行が実際のクラス、各列が予測クラスを表します。

from sklearn.metrics import confusion_matrix, classification_report, ConfusionMatrixDisplay
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
 
# Load iris dataset (3 classes)
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.3, random_state=42
)
 
# Train and predict
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
 
# Confusion matrix
cm = confusion_matrix(y_test, y_pred)
print("Confusion Matrix:")
print(cm)
 
# Classification report
print("\nClassification Report:")
print(classification_report(
    y_test, y_pred,
    target_names=iris.target_names
))
 
# Visualize
disp = ConfusionMatrixDisplay(
    confusion_matrix=cm,
    display_labels=iris.target_names
)
disp.plot(cmap='Blues')
plt.title('Iris Classification - 3 Classes')
plt.tight_layout()
plt.savefig('multi_class_confusion_matrix.png', dpi=150)
plt.show()

多クラスの平均化戦略

多クラス問題でprecision / recall / F1を計算する際は、平均化方法を選ぶ必要があります。

from sklearn.metrics import precision_score, recall_score, f1_score
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
 
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.3, random_state=42
)
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
 
for avg in ['micro', 'macro', 'weighted']:
    p = precision_score(y_test, y_pred, average=avg)
    r = recall_score(y_test, y_pred, average=avg)
    f1 = f1_score(y_test, y_pred, average=avg)
    print(f"{avg:8s} -- Precision: {p:.4f}, Recall: {r:.4f}, F1: {f1:.4f}")
AverageMethodBest For
micro全クラスのTP/FP/FNを合算クラス不均衡が重要な場合
macroクラスごとのスコアを単純平均すべてのクラスが同等に重要な場合
weightedsupportで重み付け平均不均衡データセットのデフォルト選択

完全な例:End-to-Endの分類評価

from sklearn.metrics import (
    confusion_matrix, classification_report, ConfusionMatrixDisplay,
    accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
)
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
import numpy as np
 
# Load data
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=42, stratify=data.target
)
 
# Build pipeline
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', GradientBoostingClassifier(
        n_estimators=200, max_depth=3, random_state=42
    ))
])
 
# Train
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
y_prob = pipeline.predict_proba(X_test)[:, 1]
 
# Confusion matrix
cm = confusion_matrix(y_test, y_pred)
tn, fp, fn, tp = cm.ravel()
 
print("=" * 50)
print("MODEL EVALUATION REPORT")
print("=" * 50)
print(f"\nConfusion Matrix:")
print(f"  TP={tp}, FP={fp}")
print(f"  FN={fn}, TN={tn}")
print(f"\nAccuracy:  {accuracy_score(y_test, y_pred):.4f}")
print(f"Precision: {precision_score(y_test, y_pred):.4f}")
print(f"Recall:    {recall_score(y_test, y_pred):.4f}")
print(f"F1-Score:  {f1_score(y_test, y_pred):.4f}")
print(f"ROC-AUC:   {roc_auc_score(y_test, y_prob):.4f}")
print(f"\nDetailed Report:")
print(classification_report(y_test, y_pred, target_names=data.target_names))
 
# Visualize
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
ConfusionMatrixDisplay.from_predictions(
    y_test, y_pred, display_labels=data.target_names,
    cmap='Blues', ax=axes[0]
)
axes[0].set_title('Raw Counts')
 
ConfusionMatrixDisplay.from_predictions(
    y_test, y_pred, display_labels=data.target_names,
    normalize='true', values_format='.1%', cmap='Blues', ax=axes[1]
)
axes[1].set_title('Normalized')
plt.tight_layout()
plt.savefig('full_evaluation.png', dpi=150)
plt.show()

PyGWalkerで分類結果を探索する

混同行列を作ったら、誤分類をさらに深掘りするために、生データをインタラクティブに探索すると効果的です。PyGWalker (opens in a new tab) は、予測結果をJupyter上でドラッグ&ドロップのビジュアル分析UIに変換します。

import pandas as pd
import pygwalker as pyg
 
# Build results DataFrame with features and predictions
results = pd.DataFrame(X_test, columns=data.feature_names)
results['actual'] = y_test
results['predicted'] = y_pred
results['correct'] = y_test == y_pred
results['confidence'] = y_prob
 
# Launch interactive exploration
walker = pyg.walk(results)

誤分類サンプルでフィルタし、TP/FP/FN/TNグループ間で特徴量分布を比較し、モデルが苦手とする領域を説明するパターンを特定できます。

Jupyterで分類実験を反復する(しきい値調整、別モデルのテスト、特徴量組み合わせの探索など)場合は、RunCell (opens in a new tab) が実験ループを加速するAI agentを提供します。

FAQ

sklearnにおける混同行列とは?

混同行列は、各クラスについて「正しい予測」と「誤った予測」の件数を示す表です。sklearnでは confusion_matrix(y_true, y_pred) が2次元のnumpy配列を返し、行が実際のクラス、列が予測クラスを表します。二値分類では、真陽性・真陰性・偽陽性・偽陰性を示します。

混同行列はどう読めばよいですか?

sklearnの混同行列では、行が実際ラベル、列が予測ラベルです。二値分類の場合、左上が真陰性(TN)、右上が偽陽性(FP)、左下が偽陰性(FN)、右下が真陽性(TP)です。対角成分は正解予測を表します。

precisionとrecallの違いは何ですか?

precisionは「陽性と予測したもののうち、実際に陽性だった割合」(TP / (TP + FP))です。recallは「実際に陽性のうち、モデルが見つけられた割合」(TP / (TP + FN))です。precisionは「陽性と言ったときにどれだけ正しいか」、recallは「陽性をどれだけ取りこぼさないか」を表します。

accuracyではなくF1-scoreを使うべきなのはいつですか?

クラス不均衡があるときはF1-scoreを使います。たとえばサンプルの95%が陰性なら、常に陰性と予測するモデルはaccuracyが95%でも、陽性に対するrecallは0%です。F1-scoreはprecisionとrecallの調和平均なので、片方を犠牲にするモデルを強く罰します。

Pythonで混同行列をプロットするには?

最も簡単なのは ConfusionMatrixDisplay.from_predictions(y_true, y_pred) を使う方法です。よりカスタマイズしたい場合は confusion_matrix() で行列を計算し、seaborn.heatmap() で可視化します。どちらも正規化、カラーマップ、クラスラベルに対応しています。

ConfusionMatrixDisplayでnormalize='true'にするとどうなりますか?

normalize='true' は、各行をそのクラスの実サンプル数で割るため、各行の合計が1になります。結果としてクラスごとのrecallをパーセンテージで確認できます。normalize='pred' はprecisionの観点、normalize='all' は全体比率を見るのに使えます。

まとめ

混同行列は、分類モデル評価の基礎です。accuracyだけでは不十分で、モデルがどの種類の誤りをどれだけ起こしているかを把握する必要があります。sklearnの confusion_matrixclassification_report で全体像をつかみ、ConfusionMatrixDisplay やseabornのヒートマップで可視化して、プレゼンやレポートにも活用できます。クラスサイズが異なる場合は正規化を使いましょう。最重要指標は、誤りタイプごとのビジネスコストに合わせて選びます。偽陽性が高コストならprecision、偽陰性が危険ならrecall、両者のバランスが必要ならF1-scoreが適しています。

📚