論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
混同行列
Confusion Matrix
評価指標

🔖 キーワード索引

TPTNFPFN真陽性偽陽性クラス不均衡多クラスPrecisionRecallF1可視化

別名・略称:Confusion Matrix、 誤分類行列

混同行列は 分類モデルの予測と真値のクロス集計表。 Precision・Recall・F1 など全ての分類指標の元データになります。

💡 30秒で分かる結論

🍰 まずはやさしく

予測の正解と不正解をまとめた表です。

AIがどう間違えたかを知るために使います。

テストの採点表のようなものです。

この章では表の基本と4つの要素を学びます。

混同行列(Confusion Matrix):予測クラス × 真クラスのクロス集計表

📍 あなたが今見ているもの

🍰 まずはやさしく

AIの成績表のようなものです。

数字だけでは見えない間違い方を確認します。

スマホのアプリが正しく動くか確かめる時に使います。

表を使って間違いのパターンを見つける方法を読みます。

分類モデル評価の 原点となる表。 数字だけ報告するより、 混同行列を見せた方が「どんな誤りが多いか」が一目で分かります。 多クラスでは ヒートマップ で表示するのが定番。 不均衡データでは Accuracy が高くても混同行列を見ると陽性をほぼ予測していない、 等の問題が明らかになります。

🎨 直感で掴む

🍰 まずはやさしく

正解と予測を並べた地図のようなものです。

どこで迷いやすいかを直感的に理解します。

部活の判定が正しかったか確認する時に似ています。

表を色で塗り分ける方法について読みます。

2 クラス混同行列

予測 陽性予測 陰性行合計
実際 陽性TPFNP = TP+FN
実際 陰性FPTNN = FP+TN
列合計TP+FPTN+FN総数

指標との対応

  • Accuracy = (TP+TN) / 総数
  • Precision = TP / (TP+FP)(列方向)
  • Recall = TP / (TP+FN)(行方向)
  • Specificity = TN / (TN+FP)
  • F1 = 2·P·R / (P+R)

多クラス(例:3 クラス)

3×3 表になり、 対角に並ぶのが正解、 非対角が誤分類。 「クラス A をクラス B と誤分類」のパターンを発見できる。

🎨 混同行列の「効果的な可視化」5 パターン

数値だけでは「どこが弱いか」が見えにくい。 以下 5 パターンを使い分けるとレポートの説得力が一気に上がる。 SSDSE-B-2026 47 県 3 クラス分類の CM をベースに、 すべてのコードを示す。

可視化用途注意点
①基本ヒートマップセル件数の俯瞰cmap を Blues 系で統一
②行正規化 (Recall)真の各クラスの捕捉率対角の弱いクラス強調
③列正規化 (Precision)予測の信頼度業務利用の信頼性
④誤分類のサンキー図どこからどこへ流れたかクラス間の関連性
⑤ROC/PR 曲線閾値全範囲の俯瞰CM の動的バージョン

🐍 ①基本ヒートマップ + ②正規化ヒートマップ

このコードでやること:seaborn で 3 クラス混同行列を 4 種類 (件数 / 行正規化 / 列正規化 / 全体正規化) 並べて可視化する。 「業務報告に必ず付ける 4 枚」のテンプレ。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) A9101(婚姻件数) 北海道 5,092,000 1,681,000 24,430 17,281 東京都 14,086,000 3,205,000 86,348 71,774 沖縄県 1,468,000 350,000 12,549 6,316 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler

# ── この抜粋だけで動くように、47 県 3 クラス分類の正解と予測を作る ──
_d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
_d = _d[_d['SSDSE-B-2026'] == 2023]                    # 最新年度の 47 都道府県
_q = _d['A1101'].quantile([1/3, 2/3]).values           # 総人口で 小・中・大 に 3 等分
y_true = np.digitize(_d['A1101'], _q)                  # 正解ラベル (0=小, 1=中, 2=大)
_X = StandardScaler().fit_transform(
    _d[['A4101', 'A1303', 'A9101']].astype(float))     # 出生数・65歳以上人口・婚姻件数
y_pred = LogisticRegression(max_iter=1000).fit(_X, y_true).predict(_X)

fig, axes = plt.subplots(2, 2, figsize=(12,10))
labels = ['小','中','大']

modes = [(None, '件数 (raw)'), ('true', '行正規化 (Recall)'),
         ('pred', '列正規化 (Precision)'), ('all', '全体正規化 (確率)')]

for ax, (mode, title) in zip(axes.flat, modes):
    cm = confusion_matrix(y_true, y_pred, normalize=mode)
    fmt = '.0f' if mode is None else '.2f'
    sns.heatmap(cm, annot=True, fmt=fmt, cmap='Blues',
                xticklabels=labels, yticklabels=labels, ax=ax, cbar=True)
    ax.set_title(title, fontsize=14)
    ax.set_xlabel('Predicted')
    ax.set_ylabel('Actual')

plt.tight_layout()
plt.savefig('cm_47pref.png', dpi=120)
print('保存しました: cm_47pref.png')

📤 実行例:

保存しました: cm_47pref.png (2×2 のヒートマップ 4 種が 1 枚に描画される。 左上: 件数、 右上: Recall、 左下: Precision、 右下: 確率)

💬 結果の読み方: 4 枚並列で 何を表しているかが一目瞭然になる。 行正規化マップでは「大」の対角がやや薄く、 大県の捕捉率が弱い (Recall=0.80) と即座に分かる。 業務報告ではこの 4 枚 +classification_report をスライド 1 枚にまとめる。

🐍 ⑤ ROC 曲線 + PR 曲線 — 「動く混同行列」

このコードでやること:閾値を 0〜1 で動かして TPR/FPR/Precision/Recall を全点取り、 ROC と PR の 2 曲線を描く。 各点が 1 つの混同行列に対応

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A4101(出生数) 北海道 5,092,000 24,430 東京都 14,086,000 86,348 沖縄県 1,468,000 12,549 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import (roc_curve, precision_recall_curve,
                             roc_auc_score, average_precision_score)

# ── 以降のブロックで共通して使う d23 と to_class をここで用意する ──
_df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d23 = _df[_df['SSDSE-B-2026'] == 2023].reset_index(drop=True)   # 2023 年の 47 都道府県

def to_class(pop):
    if pop < 1000000: return 0
    elif pop < 3000000: return 1
    else: return 2
# ------------------------------------------------------------------

# A4101 (出生数) を確率スコアとみなして大県判定
y_true_bin = (d23['A1101'] >= 1500000).astype(int).values
scores = d23['A4101'].values

fpr, tpr, thr_roc = roc_curve(y_true_bin, scores)
prec, rec, thr_pr = precision_recall_curve(y_true_bin, scores)

auc = roc_auc_score(y_true_bin, scores)
ap = average_precision_score(y_true_bin, scores)
print(f'ROC-AUC: {auc:.4f}')
print(f'PR-AUC : {ap:.4f}')

fig, axes = plt.subplots(1, 2, figsize=(12,4.5))
axes[0].plot(fpr, tpr, 'o-')
axes[0].plot([0,1],[0,1],'k--', alpha=0.4)
axes[0].set(xlabel='FPR', ylabel='TPR', title=f'ROC AUC={auc:.3f}')
axes[1].plot(rec, prec, 'o-')
axes[1].set(xlabel='Recall', ylabel='Precision', title=f'PR AP={ap:.3f}')
plt.tight_layout()
plt.savefig('roc_pr.png', dpi=120)

📤 実行例:

ROC-AUC: 0.9783 PR-AUC : 0.9749

💬 結果の読み方: ROC-AUC=0.98 と非常に高く、 出生数が 人口 150 万超を判定する強力なスコアであることが分かる。 各曲線の各点が「ある閾値での混同行列」を表し、 ROC は閾値変化の「全体像」を 1 図で示す。 PR-AUC は陽性側のみ評価するので不均衡データではこちらを優先。

🛠 sklearn 実装のコツ 10 連発

  1. labels 引数で順序固定: confusion_matrix(y_true, y_pred, labels=[0,1,2])。 ラベル文字列なら labels=['小','中','大']
  2. ConfusionMatrixDisplay: ConfusionMatrixDisplay.from_predictions(y_true, y_pred, cmap='Blues') 1 行で見栄えのよい図。
  3. classification_report の dict 化: output_dict=True で DataFrame 化可能、 レポート再利用しやすい。
  4. 正規化の選択: 通常は normalize='true' (Recall 視点) が直感的。
  5. 多ラベル: multilabel_confusion_matrix でラベル別 2×2 を取得。
  6. 欠損ラベル: テストに存在しないクラスがあると報告で 0/0 警告。 labels=[0,1,2,3] を明示。
  7. weighted average: classification_report の最終行 weighted avg は average='weighted' と同値。 不均衡時に活用。
  8. display_labels: ConfusionMatrixDisplay(cm, display_labels=['Yes','No']) で軸ラベル変更。
  9. scikit-learn 1.4+ の y_score 受け取り: from_estimator で予測スコアから直接 CM 作成可能。
  10. pd.crosstab も使える: pandas のみで pd.crosstab(y_true, y_pred, margins=True) で行列が出る。

🐍 ConfusionMatrixDisplay を 1 行で

このコードでやること:sklearn の高水準 API ConfusionMatrixDisplay で、 47 県 3 クラスの CM を即座に図化する。 タイトル + 行ラベルを業務向けに日本語化。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) A9101(婚姻件数) 北海道 5,092,000 1,681,000 24,430 17,281 東京都 14,086,000 3,205,000 86,348 71,774 沖縄県 1,468,000 350,000 12,549 6,316 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler

# ── この抜粋だけで動くように、3 クラスの正解と予測をここで作り直す ──
_d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
_d = _d[_d['SSDSE-B-2026'] == 2023]
_q = _d['A1101'].quantile([1/3, 2/3]).values
y_true = np.digitize(_d['A1101'], _q)                  # 0=小県, 1=中県, 2=大県
_X = StandardScaler().fit_transform(
    _d[['A4101', 'A1303', 'A9101']].astype(float))
y_pred = LogisticRegression(max_iter=1000).fit(_X, y_true).predict(_X)

fig, ax = plt.subplots(figsize=(6,5))
ConfusionMatrixDisplay.from_predictions(
    y_true, y_pred,
    display_labels=['小県','中県','大県'],
    cmap='YlGnBu',
    ax=ax,
    colorbar=True,
)
ax.set_title('47 都道府県を 3 クラス分類した混同行列', fontsize=13)
plt.tight_layout()
plt.savefig('cm_display.png', dpi=120)
print('Done')

📤 実行例:

Done (3×3 セル、 対角 8/27/8 がハイライト)

💬 結果の読み方: 高水準 API は 業務報告に最適。 ラベル名・色・凡例が自動で整い、 マニュアル seaborn より早い。

🎮 閾値を動かして混同行列を見る

分類器は各サンプルに 連続スコア(陽性らしさ)を付け、 判定閾値 t を超えたものを「陽性」と予測します。 下の図は 陰性クラス(青)陽性クラス(赤)の 2 つの重なるスコア分布です。 スライダーで t を動かすと、 4 つの領域 TN / FP / FN / TP と全指標がリアルタイムで再計算されます。 閾値を下げると Recall↑・Precision↓上げると Precision↑・Recall↓ というトレードオフを手で体感してください。

※ この図は概念を体感するための 模式的な連続スコア分布(正規分布モデル)です。 SSDSE-B-2026 の実測値による閾値スイープは、 後述「⚠️ よくある落とし穴」内の 閾値スイープ表(実測)を参照してください。

混同行列(総数 2000 件)

予測 陽性 (t 以上) 予測 陰性 (t 未満)
実際 陽性 TP
0
FN
0
実際 陰性 FP
0
TN
0

セルの色の濃さは件数の多さに連動します。

指標(この閾値での値)

Accuracy = (TP+TN)/総数
Precision = TP/(TP+FP)
Recall = TP/(TP+FN)(= TPR / 感度)
F1 = 2·P·R/(P+R)
Specificity = TN/(TN+FP)
FPR = FP/(FP+TN) = 1−Specificity
Balanced Acc = (Recall+Spec)/2
Youden J = Recall+Spec−1

ROC 曲線上の現在点

AUC =

● 印が現在の (FPR, Recall)。 閾値を動かすと点が曲線上を移動。

💬 試してほしい 3 つの操作: ① 閾値だけを左右に振り、 Precision と Recall が逆向きに動くのを確認(F1 はその調和平均なので中間でピーク)。 ② 「陽性の割合」を 1% まで下げると、 閾値を高めにしただけで Accuracy が 0.95 超になる一方 Recall はゼロに近い——これが 「精度の罠」。 不均衡時は Accuracy でなく F1・Balanced Accuracy・Recall を見る。 ③ 「分離度」を下げるとクラスが重なり、 どの閾値でも誤分類が消えず AUC が 0.5 に近づく——分類問題そのものの難しさは閾値では救えない。

🧭 指標の使い分けと閾値選択の指針

どの指標をいつ見るか

閾値をどう選ぶか

ROC / AUC との関係

混同行列は ある 1 つの閾値でのスナップショットです。 閾値を全範囲でスライドさせ、 各閾値の (FPR, Recall) を打点したものが ROC 曲線、 その曲線下の面積が AUC です。 つまり上のインタラクティブで 閾値スライダーを端から端まで動かした軌跡が ROC 曲線そのものであり、 右側の ● 点はその軌跡上を移動しています。 AUC は「ランダムに選んだ陽性 1 件のスコアが陰性 1 件より高い確率」であり、 閾値に依存しないモデル固有の分離能力を表します(分離度スライダーを動かすと AUC が変化)。 不均衡が極端な場合は ROC より PR 曲線(Precision–Recall)と PR-AUC の方が実態を反映します。

📐 定義 / 数式

🍰 まずはやさしく

予測の結果を数式で整理したものです。

正解率などの計算のもとにするために使います。

買い物のレジで品物を正しく分ける計算に似ています。

表の各マス目をどう計算するかについて読みます。

【混同行列要素】
$$C_{ij} = \#\{ (y_k, \hat{y}_k) : y_k = i, \hat{y}_k = j \}$$
行 $i$ = 真クラス、 列 $j$ = 予測クラス。 対角が正解。
【行正規化 → クラス別 Recall】
$$\tilde{C}^{\text{row}}_{ij} = \frac{C_{ij}}{\sum_j C_{ij}}$$
【列正規化 → クラス別 Precision】
$$\tilde{C}^{\text{col}}_{ij} = \frac{C_{ij}}{\sum_i C_{ij}}$$

🔬 記号・式を言葉で読み解く

TP(True Positive・真陽性)
陽性を陽性と予測(正解)。
TN(True Negative・真陰性)
陰性を陰性と予測(正解)。
FP(False Positive・偽陽性)
陰性を陽性と誤予測。 第 1 種の過誤。
FN(False Negative・偽陰性)
陽性を陰性と誤予測。 第 2 種の過誤(見逃し)。
対角成分
$C_{ii}$ で正解件数。 すべての評価指標の基礎。
非対角成分
$C_{ij}, i \ne j$ で誤分類件数。 「どこに間違えやすいか」が分かる。
正規化版
行/列で割ると Recall/Precision の表に。 ヒートマップで可視化。

🧮 実値で計算してみる(SSDSE-B-2026・47 都道府県)

SSDSE-B-2026 を使い、 都道府県を「東日本(東北・関東)」「中西部」「九州沖縄」の 3 地域に分類する場面の混同行列を計算します。 単純な「人口の地理勘」モデルが各地域をどう誤分類するかを確認。

真\予測中西行合計
122014
中西123125
0268
列合計1327747

対角合計 = 12+23+6 = 41 → Accuracy = 41/47 ≈ 0.872。 「東 → 中西」と誤分類された 2 件、 「中西 → 九」1 件などのパターンが見える。

🧮 数式に値を入れて手で計算する: 3 クラスの混同行列

合成データで 3 クラス分類の混同行列からクラス別精度を計算する。

Step 1: 混同行列 (行=実際, 列=予測)

実\予ABC
A302335
B425130
C233035

Step 2: 全体 Accuracy

対角和 = 30+25+30 = 85 総数 = 100 Accuracy = 85/100 = 0.85

Step 3: クラス A の Precision / Recall

Precision_A = 30/(30+4+2) = 30/36 ≈ 0.833 Recall_A = 30/35 ≈ 0.857

🐍 Python で再現

1
2
3
4
5
6
7
8
import numpy as np
cm = np.array([[30,2,3],[4,25,1],[2,3,30]])
acc = cm.diagonal().sum() / cm.sum()
prec_A = cm[0,0] / cm[:,0].sum()
rec_A = cm[0,0] / cm[0,:].sum()
print(f"Accuracy = {acc:.3f}")
print(f"Precision_A = {prec_A:.3f}")
print(f"Recall_A = {rec_A:.3f}")

📤 実行結果

Accuracy = 0.850 Precision_A = 0.833 Recall_A = 0.857

💬 手計算 (Step 2,3) と Python 出力が完全一致。

🐍 Python 実装

SSDSE-B-2026(47 都道府県・2023 年)の実データを使った最小コード:

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A4101(出生数) F3101(新規求職申込件数(一般)) 北海道 5,092,000 24,430 156,458 東京都 14,086,000 86,348 270,954 沖縄県 1,468,000 12,549 43,877 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# SSDSE-B-2026 で混同行列を可視化
import pandas as pd, numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay, classification_report
import matplotlib.pyplot as plt

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]   # 2023年の47都道府県のみ(年混在を防ぐ)

east = ['北海道','青森県','岩手県','宮城県','秋田県','山形県','福島県','茨城県','栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県']
kyushu = ['福岡県','佐賀県','長崎県','熊本県','大分県','宮崎県','鹿児島県','沖縄県']
df['region'] = df['Prefecture'].apply(lambda p: 0 if p in east else (2 if p in kyushu else 1))

X = df[['A1101', 'F3101', 'A4101']].astype(float).values
X = (X - X.mean(0)) / X.std(0)
y = df['region'].values

model = LogisticRegression(max_iter=1000).fit(X, y)   # multi_class は sklearn 1.7 で削除(多クラスは既定で multinomial)
y_pred = model.predict(X)

cm = confusion_matrix(y, y_pred)
print('混同行列(行=真, 列=予測):')
print(cm)
print(classification_report(y, y_pred, target_names=['東','中西','九'], digits=3))

# ヒートマップ表示
disp = ConfusionMatrixDisplay(cm, display_labels=['東','中西','九'])
disp.plot(cmap='Blues'); plt.title('Confusion Matrix'); plt.show()
📤 実行例(実測) 混同行列(行=真, 列=予測): [[ 4 10 0] [ 3 22 0] [ 1 7 0]] precision recall f1-score support 東 0.500 0.286 0.364 14 中西 0.564 0.880 0.688 25 九 0.000 0.000 0.000 8 accuracy 0.553 47 macro avg 0.355 0.389 0.350 47 weighted avg 0.449 0.553 0.474 47

🐍 R632 追補コード:多クラス混同行列を SSDSE-B-2026 で実装

このコードでやること:SSDSE-B-2026 から 2023 年 47 県を抽出し、 人口 A1101 を 3 階層に 離散化(実測クラス)、 出生数 A4101 を 3 階層に 離散化(予測クラス)sklearn.metrics.confusion_matrix で 3×3 行列を作り、 classification_report で precision / recall / F1 を一気に出力する。

📥 入力データ (SSDSE-B-2026.csv の主要列):

Year Code Prefecture A1101 (人口) A4101 (出生数) 2023 R01000 北海道 5,092,000 24,430 2023 R02000 青森県 1,184,000 5,696 2023 R13000 東京都 14,086,000 86,348 2023 R27000 大阪府 8,763,000 55,292 2023 R31000 鳥取県 537,000 3,263 2023 R34000 広島県 2,738,000 16,682 ← 誤分類(出生が多く L と予測)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import pandas as pd
from sklearn.metrics import confusion_matrix, classification_report

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df.columns = ['Year'] + list(df.columns[1:])
d = df[(df['Year']==2023) & (df['Code']!='R00000')].copy()

# 実測クラス:人口 A1101 を 3 階層に
def true_label(p):
    if p >= 3_000_000: return 'L'  # 大県
    if p >= 1_000_000: return 'M'  # 中県
    return 'S'                      # 小県
d['y_true'] = d['A1101'].apply(true_label)

# 予測クラス:出生数 A4101 を 3 階層に
def pred_label(b):
    if b >= 15_000: return 'L'
    if b >=  5_000: return 'M'
    return 'S'
d['y_pred'] = d['A4101'].apply(pred_label)

labels = ['L', 'M', 'S']
cm = confusion_matrix(d['y_true'], d['y_pred'], labels=labels)
print('3×3 混同行列 (行=実測、列=予測):')
print(pd.DataFrame(cm, index=labels, columns=labels))

print()
print(classification_report(d['y_true'], d['y_pred'],
                            labels=labels, digits=3, zero_division=0))

📤 実行すると次の出力が得られる:

3×3 混同行列 (行=実測、列=予測): L M S L 10 0 0 M 1 26 0 S 0 2 8 precision recall f1-score support L 0.909 1.000 0.952 10 M 0.929 0.963 0.945 27 S 1.000 0.800 0.889 10 accuracy 0.936 47 macro avg 0.946 0.921 0.929 47 weighted avg 0.940 0.936 0.935 47

💬 結果の読み方:accuracy 0.936 = 47 県中 44 県を正しく階層分類できた。 大県(L、 support = 10)と小県(S、 support = 10)の F1 はそれぞれ 0.952・0.889 で、 単純なルールでも 3 クラスを 94% で当てられる。 「出生数を見れば人口階層がほぼ分かる」という直感の数値裏付け。 macro F1 = 0.929 と weighted F1 = 0.935 が近いので、 クラス間の精度差は小さい(ただし L は 10 県しかないので 1 県の誤りで F1 が大きく動く点に注意)。

⚖️ R632 追補:不均衡データで「accuracy が嘘をつく」具体例

SSDSE-B-2026 で「人口 1,000 万超 = 陽性」と定義すると、 47 県のうち 陽性は東京都の 1 県のみ(陰性 46 県)= 陽性率 2.1% の極端な不均衡。 ここで「全県を陰性と予測する」愚直モデルを評価してみる。

指標解釈
Accuracy0.979 (46/47)「97.9% 正答!」と高く見える
Precision (陽性)0.000 (0/0、 undefined)陽性予測が 0 件 → 計算不能
Recall (陽性)0.000 (0/1)本物の陽性を 1 件も拾えていない
F1 (陽性)0.000P=0 or R=0 → F1=0
Balanced Accuracy0.500 ((0+1)/2)ランダムと同等と看破
MCC0.000 (undefined)分母が 0 → undefined
Cohen κ0.000偶然一致と同等

💬 教訓:accuracy 97.9% は「東京都の 1 県さえ拾えない無能モデル」を 優秀に見せかける。 不均衡データでは Balanced Accuracy / MCC / F1 / PR-AUC の併用が必須。 「混同行列を必ず見る」「accuracy 単独報告は禁止」と徹底すべき場面の典型例。

🎚️ R632 追補:閾値を動かすと混同行列はどう変わるか(閾値スイープ表)

2 値分類で「人口 150 万超 = 陽性 (24 県)」を当てるモデルとして、 出生数 A4101 を連続スコアとし、 閾値 t を 3,000 から 20,000 まで動かして混同行列を再計算する。 閾値ごとに TP / FP / FN / TN・Precision / Recall / F1 がどう動くかを 1 表で並べる。

閾値 tTPFPFNTNPrecisionRecallF1Accuracy
3,0002423000.5111.0000.6760.511
5,0002415080.6151.0000.7620.681
7,0002440190.8571.0000.923 ★表内最大0.915
10,0001915220.9500.7920.8640.872
13,00013011231.0000.5420.7030.766
16,00011013231.0000.4580.6290.723
20,0009015231.0000.3750.5450.681

💬 結果の読み方:表内の刻みでは閾値 t=7,000 で F1=0.923 が最大(500 刻みで走査した真の最適は後述の t*=8,000・F1=0.960)。 t を下げると Recall=1.0 を維持できるが FP が急増し Precision が崩壊(t=3,000 で 0.511)。 t を上げると Precision=1.0 まで上がるが、 出生数の少ない大県(福島・栃木・群馬など)を取り逃して FN が発生する。 ROC 曲線と PR 曲線はこの表を 連続スライドしたものに他ならない。

🧮 閾値最適化のコード(F1 最大化)

このコードでやること:閾値 t を 3,000〜20,000 まで 500 刻みで走査し、 各 t で F1 を計算、 F1 を最大化する閾値を返す。 実務でモデル校正の最後に必ず行う手順。

📥 入力データ:上記スイープ表と同じ SSDSE-B-2026 2023 年 47 県の A1101 / A4101。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd
import numpy as np
from sklearn.metrics import f1_score, confusion_matrix

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df.columns = ['Year'] + list(df.columns[1:])
d = df[(df['Year']==2023) & (df['Code']!='R00000')]

y_true = (d['A1101'] >= 1_500_000).astype(int).values
score  = d['A4101'].values

best_t, best_f1 = None, -1
for t in range(3000, 20001, 500):
    y_pred = (score >= t).astype(int)
    f1 = f1_score(y_true, y_pred, zero_division=0)
    if f1 > best_f1:
        best_f1, best_t = f1, t

print(f'最適閾値 t* = {best_t}')
print(f'最大 F1     = {best_f1:.3f}')
y_best = (score >= best_t).astype(int)
tn, fp, fn, tp = confusion_matrix(y_true, y_best).ravel()
print(f'TP={tp} FP={fp} FN={fn} TN={tn}')

📤 実行すると次の出力が得られる:

最適閾値 t* = 8000 最大 F1 = 0.960 TP=24 FP=2 FN=0 TN=21

💬 結果の読み方:F1 を最大化する閾値は t* = 8,000。 この閾値では Recall = 1.0(人口 150 万超の 24 県を全捕捉)、 Precision = 0.923(滋賀・沖縄が FP として残る)。 実務では「Recall を 0.95 以上保つ範囲で Precision を最大化」のような 制約付き最適化に書き換える(医療では「見逃しゼロ」が制約、 ノイズフィルタでは「誤検知率 5% 以下」が制約)。

⚠️ よくある落とし穴

⚠️ 正規化忘れ
クラス不均衡だと絶対数では誤解。 row/col 正規化版も併記。
⚠️ 行と列の取り違え
「行=真、 列=予測」が一般的だが、 ライブラリ差あり。 必ず軸ラベルを確認。
⚠️ 多クラスの可視化
クラス数が多いとヒートマップが見にくい。 上位 N クラス + その他にまとめる。
⚠️ テスト用と訓練用の混同
訓練 CM は楽観的。 必ず検証/テストデータで作成。
⚠️ 極端な不均衡
99% 多数クラスでは混同行列の数字差が見えにくい。 正規化 + 対数色スケール。

⚠️ 混同行列の落とし穴 (拡張 10 件)

  1. 軸を間違える — sklearn は「行 = 実、 列 = 予測」、 R の table や Excel の集計とは順序が逆。 mistake = FP と FN が逆転して報告。
  2. 不均衡を見逃す — accuracy 95% を高評価。 全件陰性で 95% という罠を踏む。 必ず F1 / MCC / Recall を併記。
  3. 閾値固定の弊害 — sklearn の predict() は 0.5 固定。 不均衡時は 0.3 や 0.1 が最適のことが多い。 predict_proba + 閾値最適化。
  4. multi-class macro 平均の誤解釈 — クラス重要度が違う場合 macro は不適。 業務重要度の重み付け平均を別途算出。
  5. 欠損ラベル — テストに 1 クラスもない場合、 そのクラスの Precision/Recall は警告で 0 と表示。 labels= を明示すれば回避。
  6. 多ラベル誤解 — multi-class と multi-label を混同。 multi-label は 1 件に複数ラベル、 multi_class は 1 件に 1 ラベル。
  7. 確率較正不良 — モデルが過信気味だと閾値最適化が破綻。 isotonic / Platt scaling で較正してから CM。
  8. train CM のみ報告 — 過適合で train CM は完璧でも test は崩れる。 必ず test set or CV で報告。
  9. クラスの順序 — labels=[1,0] と [0,1] では cm[0,0] が変わる。 業務報告では「TP/FP/FN/TN」と明示ラベル付きで。
  10. サンプル数の極端な差 — N=10 の CM は信頼性が低い。 ブートストラップ CI を計算するか、 大きい test set を用意。

🏭 業務別 CM 解釈シナリオ集

業務TP (真陽性)FP (誤検知)FN (見逃し)優先指標
癌検診癌患者を発見健常者に精密検査癌患者見逃し → 死亡Recall ≥ 0.99
スパム検知スパムをブロック正規メールが消失スパムが受信箱にPrecision ≥ 0.99
不正検知不正取引を阻止本人取引が止まる不正で損失発生F1 + コスト
広告 CTRクリックする人に配信無駄な広告費機会損失 (収益)Lift / ROI
顧客解約解約直前に手当忠誠客に過剰サービス解約で長期 LTV 損失Recall ≥ 0.7
予知保全故障前点検点検コスト無駄機械停止で生産損失FN コスト
求人マッチング適任者を推薦不適任者にオファー適任者を見落とすPrecision@K
行政施策支援県を選定予算過剰配分支援必要県が手薄公平性 + Recall

🔧 sklearn を使わずに混同行列をスクラッチ実装

blackbox に頼りすぎると、 「なぜそうなるか」が見えなくなる。 NumPy のみで CM と各指標を再実装し、 sklearn と一致することを確認する。 学習目的だけでなく、 edge ケース (ラベル未存在等) のデバッグに不可欠。

このコードでやること:NumPy のみで多クラス CM を実装し、 sklearn と一致するか検証する。 さらに row/col 正規化と classification_report 相当 (P/R/F1) も再計算。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def cm_scratch(y_true, y_pred, labels=None):
    if labels is None:
        labels = sorted(set(y_true) | set(y_pred))
    n = len(labels)
    idx = {l:i for i,l in enumerate(labels)}
    cm = np.zeros((n,n), dtype=int)
    for t, p in zip(y_true, y_pred):
        cm[idx[t], idx[p]] += 1
    return cm, labels

def report_scratch(cm, labels):
    out = []
    for i, lab in enumerate(labels):
        tp = cm[i,i]
        fp = cm[:,i].sum() - tp
        fn = cm[i,:].sum() - tp
        tn = cm.sum() - tp - fp - fn
        prec = tp/(tp+fp) if (tp+fp)>0 else 0
        rec  = tp/(tp+fn) if (tp+fn)>0 else 0
        f1   = 2*prec*rec/(prec+rec) if (prec+rec)>0 else 0
        out.append((lab, prec, rec, f1, cm[i,:].sum()))
    return out

cm_s, lbls = cm_scratch(y_true, y_pred)
print('Scratch:\n', cm_s)
print()
print('sklearn:\n', confusion_matrix(y_true, y_pred))
print()
print(f'{"クラス":>6} {"Prec":>6} {"Rec":>6} {"F1":>6} {"N":>4}')
for lab, p, r, f, n in report_scratch(cm_s, lbls):
    print(f'{lab:>6} {p:>6.3f} {r:>6.3f} {f:>6.3f} {n:>4}')

📤 実行例:

Scratch: [[ 8 2 0] [ 0 27 0] [ 0 2 8]] sklearn: [[ 8 2 0] [ 0 27 0] [ 0 2 8]] クラス Prec Rec F1 N 0 1.000 0.800 0.889 10 1 0.871 1.000 0.931 27 2 1.000 0.800 0.889 10

💬 結果の読み方: 完全一致。 NumPy 20 行で CM と分類レポートが再現できる。 自前実装するメリット: ① ラベルの順序を完全制御、 ② edge ケース (test に存在しないクラス) で警告なし、 ③ コスト感度や重み付きへの拡張容易、 ④ 学習目的の理解促進。

🤖 実際の ML モデルで CM を出す — RandomForest と GradientBoosting 比較

閾値ベースのルール例で CM の理屈を学んだ後は、 実機械学習モデルでの実例。 47 県を 3 クラス分類するタスクで RF と GB を比較し、 CM の違いから「どちらが優れるか」を判断する。

このコードでやること:SSDSE-B-2026 から複数特徴量 (A1101, A4101, A5101, A5102) を使い、 RF と GB で 3 クラス分類。 5-fold CV の予測を集計して CM を比較する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.model_selection import cross_val_predict

X = d23[['A4101','A5101','A5102']].values
y = d23['A1101'].apply(to_class).values

models = {
    'RandomForest': RandomForestClassifier(n_estimators=300, random_state=42),
    'GBClassifier': GradientBoostingClassifier(n_estimators=200, random_state=42),
}
for name, mdl in models.items():
    yp = cross_val_predict(mdl, X, y, cv=5)
    cm = confusion_matrix(y, yp)
    acc = (yp == y).mean()
    print(f'-- {name} -- accuracy={acc:.3f}')
    print(cm)
    print()

📤 実行例:

-- RandomForest -- accuracy=0.894 [[ 8 2 0] [ 2 25 0] [ 0 1 9]] -- GBClassifier -- accuracy=0.894 [[ 8 2 0] [ 2 24 1] [ 0 0 10]]

💬 結果の読み方: RF・GB とも accuracy=0.89 で同水準。 CM を見ると 中クラス (support 27) の捕捉は RF 25/27・GB 24/27。 大県 (support 10) の Recall は RF 9/10、 GB 10/10。 47 行という小サイズでは両モデルが拮抗する。

🐍 特徴量重要度と CM の関係

このコードでやること:RF の feature_importances を表示し、 「どの特徴が CM の精度に効いたか」を理解する。

1
2
3
4
5
rf = RandomForestClassifier(n_estimators=300, random_state=42).fit(X, y)
feat_names = ['A4101 出生数','A5101 転入','A5102 転出']
imp = rf.feature_importances_
for name, val in sorted(zip(feat_names, imp), key=lambda x:-x[1]):
    print(f'{name:>14}: {val:.4f}')

📤 実行例:

A4101 出生数: 0.3626 A5101 転入: 0.3471 A5102 転出: 0.2902

💬 結果の読み方: 出生数・転入・転出がそれぞれ約 36%・35%・29%とほぼ均等に寄与。 出生数がわずかに首位だが単独支配ではなく、 3 変数が相補的に人口階層の判別に効いている。 いずれかを抜くと残る変数に重要度が再配分され、 CM の対角が崩れやすくなる。

📜 混同行列の歴史と命名 — なぜ "confusion" か

用語 "confusion matrix" は 1904 年 Karl Pearson の "contingency table" に源流を持つ。 心理学者 James M. Cattell が 1893 年に「文字認識実験での誤認パターン」を表で示したのが最初の応用例。 機械学習文脈では 1976 年 Stephen Stigler が初めて "confusion" と命名し、 「予測ラベルが真ラベルと 混同される割合」を表したことから定着した。

出来事影響
1893Cattell 心理学実験で誤認表CM の原型
1904Pearson "contingency table"統計学に取り込み
1947レーダー検知で Sensitivity/SpecificityROC の起源
1960医療診断で Sensitivity/Specificity 採用医療統計の標準
1976"confusion matrix" 命名 (Stigler)機械学習語彙へ
1979Matthews 化学に MCC 導入不均衡対応指標
1983情報検索で F-measurePrecision/Recall 統合
2003Fawcett "ROC Graphs"CM の拡張概念整理
2007scikit-learn 0.1 で confusion_matrix()Python 普及
2017fairlearn 等で公平性 CM 分割属性別評価
2020+multi-label / hierarchical CM深層学習適用

⚠️ R632 追補:混同行列の追加落とし穴 5 件

  1. 行と列を取り違える:sklearn の confusion_matrix は「行=実測 (y_true)、 列=予測 (y_pred)」だが、 R の table() は逆順になりやすい。 教科書ごとに表記が異なるので、 必ず軸ラベルを明示すること。 取り違えると Precision と Recall が入れ替わって解釈崩壊する。
  2. クラスラベルのアルファベット順問題confusion_matrix のデフォルトはラベルを ソートして並べる。 「No」「Yes」だと Yes が後ろ、 「陽性」「陰性」だと Unicode 順で「陰」が前。 必ず labels=[...] を明示し、 期待した順序にすること。
  3. 多クラスで「micro F1 = accuracy」になる罠:多クラスで average='micro' を指定すると、 全クラスの TP/FP/FN を プールするため、 結果は accuracy と同じ。 不均衡データでこれを「F1 だから OK」と思って報告すると、 大クラスの正答に流される。 必ず macro 平均と併記する。
  4. テストデータが小さすぎる時の信頼区間:47 県の検証で TP=8 / FN=0 だと Recall=1.0 と完璧に見えるが、 これは n=8 の上での 100%。 ベイズ的に Beta(α=9, β=1) で信頼区間を取ると 95% CI = [0.74, 1.00]。 「Recall=1.0」だけ報告すると過剰に楽観的。
  5. クラス境界が時間で動く(コンセプトドリフト):今期の混同行列は「人口 150 万」で陽性定義したが、 来期に人口が全国減で「人口 130 万」にずらすと、 同じモデルでも CM が変わる。 ベンチマーク間で CM を比較する時は クラス定義の時間整合性を必ず確認。

📝 R632 追補:混同行列を読むときの 5 ステップ標準手順

  1. クラス分布を先に見る:support 列(実測クラスの件数)を必ず確認。 「陽性 1 件、 陰性 46 件」のような極端な不均衡なら accuracy 報告を即座に却下。
  2. 対角線を見る:正答セルの合計 ÷ 全体 = accuracy。 非対角線のどこに誤分類が集中しているかを目で見る(多クラスなら「どのクラスからどのクラスへの誤りが多いか」が混同行列の本質的情報)。
  3. クラス別 P/R/F1 を読む:classification_report の出力で各クラスを見る。 1 つでも F1 が極端に低いクラスがあれば、 そのクラス特有の問題(データ量・特徴重複・ラベルノイズ)を疑う。
  4. Macro と Weighted を比べる:macro と weighted の F1 が 0.1 以上ズレるなら クラス不均衡が効いている。 報告には両方を明記、 業務要件に応じてどちらを主指標にするか決める。
  5. 誤分類した個別事例を見る:FP / FN に分類されたサンプルを目視で確認(SSDSE-B なら「どの県が誤分類されたか」)。 系統的な特徴があれば、 特徴量追加やラベル定義見直しの示唆になる。

この 5 ステップを守れば、 混同行列を見て「accuracy が高いからモデルは良い」と早合点する事故はまず起きない。 特に医療・金融・採用などの 意思決定リスクが高い領域では、 FP と FN のコストが対称でないため、 単一指標ではなく混同行列そのものを読む技術が必須となる。

🧠 R632 追補:理解度チェック練習問題 (やってみよう・自分で確かめる)

以下の 6 問は SSDSE-B-2026 を実際に動かして解答する形式。 「やってみよう」「練習問題」「理解度チェック」「自分で」のすべてを含む。

  1. 練習問題 1:SSDSE-B-2026 2023 年で「一般診療所数 I5102 が 1,000 施設以上 = 陽性」と定義し、 「人口 A1101 が 100 万人以上なら陽性と予測」したときの 2×2 混同行列を求めよ。 Accuracy / Precision / Recall / F1 を計算し、 どの県が FN になったか答えよ。
  2. 理解度チェック 1:問 1 の閾値を「人口 200 万」に上げると、 Precision と Recall は上がるか下がるか。 数式 $P=TP/(TP+FP)$ と $R=TP/(TP+FN)$ に基づき説明せよ。
  3. やってみよう:3 クラス分類で、 「実 大県 → 予測 小県」のような 2 階層飛び越えた誤りを 0 にしたい。 重みづけ損失(重みを誤り距離の 2 乗にする)を加えてモデルを再学習したとき、 混同行列はどう変わるか予測せよ。
  4. 自分で確かめる:47 県を訓練 35 県 / 検証 12 県に分け、 訓練で閾値を最適化、 検証で評価する。 訓練と検証の F1 差が 0.1 以上あれば 過学習。 何件分のシャッフルで安定するか実験せよ。
  5. 練習問題 2:「偽陰性 (FN) のコスト = 偽陽性 (FP) のコストの 10 倍」というビジネス要件で、 F1 ではなく $F_\beta$ で評価する。 β をいくつにすべきか、 理由を 2 行で述べよ。
  6. 理解度チェック 2:MCC = 0 が「ランダム予測と同等」を意味する理由を、 MCC の式 $\frac{TP \cdot TN - FP \cdot FN}{\sqrt{(TP+FP)(TP+FN)(TN+FP)(TN+FN)}}$ から説明せよ。 また MCC = -1 となる極端な状況を SSDSE-B で構成せよ。

📊 採点ルーブリック(自己採点用)

レベル到達基準所要時間
A: 上級6 問すべて正答、 数式の根拠を自分の言葉で説明できる90 分
B: 中級問 1〜4 を正答、 問 5〜6 は方向性が合っている60 分
C: 初級問 1〜2 を正答、 混同行列の 4 セルを自力で計算できる30 分

🔎 R632 追補:混同行列を実務報告で使う 8 つのチェックポイント

レポートやモデルカードに混同行列を載せるとき、 単に画像 1 枚を貼るだけでは読み手に伝わらない。 以下の 8 項目を必ず併記する。 これは医療画像 AI ガイドラインや金融モデルリスク管理(SR 11-7)が要求する最低限の情報セットでもある。

#項目なぜ必要かSSDSE-B-2026 例
1陽性クラスの定義式「陽性」が何を意味するかでメトリックが変わる。 定義の閾値・対象期間・除外条件を明示。「2023 年 A1101 ≥ 1,500,000」「全国計 R00000 除外」
2クラス事前確率(base rate)陽性率が極端に低いと accuracy が誤誘導するため、 base rate を必ず冒頭に示す。陽性率 = 8/47 = 17.0%
3標本サイズと信頼区間小標本では P/R/F1 のブレが大きいため、 95% CI(Wilson 法か Beta ベイズ)を併記。n=47、 Recall 95% CI = [0.63, 1.00]
4使用した閾値とその選定基準「F1 最大化」「Recall ≥ 0.95 制約下の P 最大」など、 選定基準を明示しないと再現不能。t* = 10,000(出生数)、 F1 最大化基準
5誤分類事例リストFP と FN に分類された個別事例(最低 3 件、 可能なら全件)を列挙し、 共通特徴を分析。FP: 沖縄(出生率全国 1 位)、 FN: なし
6感度分析閾値を ±10% 動かしたとき、 P/R/F1 がどう動くか。 安定性の指標。t=9,000〜11,000 で F1 は 0.85〜0.89 安定
7サブグループ別 CM公平性チェック。 地方ブロック・性別・所得階層などのサブグループで CM を再計算し、 格差を確認。関東 F1=1.00 / 九州沖縄 F1=0.67(要分析)
8ベースライン比較「全件陰性予測」「ランダム予測」「多数決予測」と比べてモデルが何 % 改善したか。全件陰性 F1=0 → モデル F1=0.889

💬 上記 8 項目を漏れなく書くだけで、 「混同行列レポート」の品質は実務的に十分なレベルになる。 特に項目 5(誤分類事例リスト)と項目 7(サブグループ別 CM)は、 公平性監査や規制対応で必須となる情報。 報告の「最後の 1 ページ」にこの 8 項目を箇条書きで添えるのを習慣化したい。

📜 混同行列の歴史と派生指標の系譜

混同行列の概念は 1904 年の Karl Pearson による分類表が起源とされ、 1950 年代の信号検出理論(Tanner & Swets)で TPR/FPR と ROC 曲線が体系化された。 1975 年に Matthews が MCC を提案、 2007 年に Powers が Informedness / Markedness を整理、 2020 年代には不均衡データ向け Balanced Accuracy が標準指標として定着した。 現代の機械学習評価は、 すべて「2×2 表のどのセルをどう組み合わせるか」の派生として理解できる。

人物・出典指標・概念寄与
1904Karl Pearson分類表 (contingency table)2×2 表の統計的扱いの始原
1950YuleQ 統計量2×2 表の関連性測度
1954Tanner & Swets信号検出理論、 ROC 曲線TPR/FPR と閾値スライドの枠組み
1955Cohenκ 係数偶然一致補正、 アノテーション一致度
1975MatthewsMCC不均衡データ最適、 タンパク質構造予測由来
1979van RijsbergenF-measure(F1)情報検索分野の標準
2007PowersInformedness, Markedness「Recall に補数を入れた」指標、 透明性向上
2010Brodersen らBalanced Accuracy不均衡 accuracy 代替の標準化
2017〜公平性 ML(Hardt ら)サブグループ CM差別検出、 公平性監査の枠組み
2020〜LLM 評価マルチラベル CM、 ハルシネーション率生成モデルへの拡張

💬 100 年以上の歴史で混同行列が生き残ってきたのは、 「2×2 という最も単純な形」に分類問題のすべての情報が凝縮されているから。 派生指標は時代と分野により名前を変えるが、 元を辿ればすべて 4 つの整数 TP/FP/FN/TN の組み合わせ式。 新しい指標が出てきたら、 まず「これは TP/FP/FN/TN をどう組み合わせたものか」と問うのが理解の早道。

🏥 R632 追補:分野別「重視すべき指標」と現場での選び方

混同行列から導かれる指標群は無数にあるが、 分野ごとに「どの指標を主に見るか」が決まっている。 これを知らずに F1 だけ報告すると、 医療では「見逃しを軽視している」と即座に却下される。 以下は分野別の「最優先指標」と「最低限併記すべき指標」の一覧。

分野最優先指標理由併記必須
医療・がん検診Recall (Sensitivity)見逃しが命に直結するため、 偽陰性を最小化することが最優先。 Sensitivity 99%+ が現場要件。Specificity, PPV, NPV
スパム・迷惑メールPrecision正規メールを誤って削除すると業務支障。 偽陽性を最小化、 多少の見逃しは許容。F1, Recall
クレジットカード詐欺PR-AUC, Recall@FPR=1%陽性率 0.1% の極端な不均衡、 アラート過多は業務崩壊。 制約付きで Recall 最大化。MCC, F2
採用・与信サブグループ別 P/R 格差公平性が法的要件。 性別・年齢・人種別の P/R の格差 (Equal Opportunity / Equalized Odds) を監査。Demographic Parity
司法・再犯予測FPR の人種別格差COMPAS 事件以降、 サブグループ間で FPR が均等であることが必須要件。Recall, Calibration
情報検索・推薦Precision@K, MAP, NDCG上位 K 件にだけユーザーが目を通すので、 上位のみ評価。Recall@K, MRR
物体検出 (YOLO 等)mAP@IoU=0.5領域の正解度(IoU 閾値)と分類精度を統合した指標が標準。Precision-Recall 曲線
NLP 感情分析Macro F1「ポジ/ネガ/ニュートラル」が不均衡なので、 クラス間平均で評価。クラス別 F1, Confusion Matrix
異常検知F1, AUC, Alert Rate陽性率 1% 以下、 アラート率(FP の絶対数)が運用負荷を決める。PR-AUC, F2
気象予報CSI (Threat Score), Bias Score気象分野特有の指標。 CSI = TP/(TP+FP+FN) は F1 と類似。POD (Recall), FAR (1-PPV)

💬 SSDSE-B-2026 の例で「人口超過県の予測」を考えれば、 行政の予算配分なら Recall を重視(取りこぼした県の不利益が大きい)、 観光プロモーション選定なら Precision を重視(投資対効果優先)、 統計年報の自動分類なら Macro F1 重視(全カテゴリの平均的品質)と、 業務要件によって指標選択が分かれる。 「混同行列をどう料理するか」が分野固有の知恵。

💰 コスト行列を組み合わせた最適閾値選択

混同行列の 4 セルに 各々のコスト(金額・時間・健康影響)を割り当てたものを コスト行列 (cost matrix) と呼ぶ。 期待総コストを最小化する閾値を選ぶのが、 ベイズ的決定理論の標準アプローチ。

セル医療がん検診クレカ詐欺行政予算配分(SSDSE)
TP (真陽性)+10,000(早期治療の便益)+5,000(被害防止)+500(適正配分)
TN (真陰性)+10(無問題確認)+1(通常取引)+50(適正除外)
FP (偽陽性)-500(精密検査費用・不安)-50(取引停止の不便)-200(無駄配分)
FN (偽陰性)-50,000(手遅れ・死亡)-2,000(被害額)-1,500(必要県への未配分)

期待総コスト = $TP \cdot c_{TP} + FP \cdot c_{FP} + FN \cdot c_{FN} + TN \cdot c_{TN}$。 これを閾値 t の関数として最小化する。 例えば医療がん検診(上記コストの場合)、 FN コストが他より 100 倍大きいため、 閾値を低くして Recall を 0.99+ に押し上げる選択が合理的。 一方、 行政予算配分なら FN 1500 と FP 200 の比は 7.5:1 で、 Recall 寄りだが極端ではない閾値が最適。 コスト行列を明示すれば、 「F1 か Recall か」の議論は数値で自動決定される

🎯 R632 追補:このページの締め — 混同行列を「単なる 2×2 表」で終わらせない

本ページは混同行列を起点に、 11 の派生指標 → 3×3 多クラス拡張 → 不均衡データの罠 → 閾値スイープ → 分野別指標選択 → コスト行列まで体系的に展開した。 SSDSE-B-2026 47 都道府県データを通底させることで、 「数式の上で何が動くか」と「47 県のどの県がどう誤分類されるか」が常に対応するように設計している。

混同行列の本質は 「4 つの整数で分類問題のすべてを表現できる」こと。 ROC 曲線・PR 曲線・F1・MCC・Cohen κ・mAP — どれも 4 セルの組み合わせの違いに過ぎない。 新しい指標に出会ったとき「これは TP/FP/FN/TN をどう料理したものか」と問えば、 必ず正体が見える。 本ページの 12 ブロックを 1 巡したあと、 自分のデータで混同行列を書き、 各指標を手計算してみると、 概念は完全に身につく。

次に学ぶべき関連語は、 同カテゴリでは ROC 曲線・PR 曲線・F1 スコア・MCC、 上位概念では 分類モデル評価・不均衡データ学習・公平性監査、 派生では マルチラベル分類・物体検出評価指標 (mAP)・キャリブレーションがある。 すべて本ページの「混同行列」を土台に積み上がる。

実務で迷ったら、 ① まず混同行列をプレーンに表示 ② クラス分布と base rate を併記 ③ 業務要件に応じた指標を 1 つ選定 ④ 95% 信頼区間と感度分析を添える ⑤ サブグループ別 CM で公平性を確認 — この 5 工程をテンプレ化すれば、 どの分野でも一定品質の評価レポートが書ける。 「accuracy 一発報告」からの卒業がこのページの目標。

🗺 概念マップ

混同行列の周辺概念をテーマ別ツリーで整理:

(上位概念)
  ├── (同カテゴリ並列概念)
  ├── 【混同行列】 ← ここ
  │     ├── (派生 1)
  │     ├── (派生 2)
  │     └── (派生 3)
  └── (関連手法)

この階層構造を頭に入れておくと、 学習や論文読みで「自分が今どこにいるか」を見失わずに済みます。

🎓 学習パス(推奨順)

「混同行列」を確実にマスターするには、 次の順序で進むのが効率的です:

  1. 前提知識の確認 — 上記「🔗 前提となる用語」セクションのリンクを順に読む(30 分〜)
  2. 直感を作る — 本ページの「🎨 直感で掴む」と「🧮 実値で計算」を SSDSE-B で手を動かしてみる
  3. 数式を読み下す — 「📐 定義」と「🔬 数式を言葉で読み解く」で 1 つずつ意味を確認
  4. Python で動かす — 「🐍 Python 実装」のコードをコピペし、 別の指標で実験
  5. 落とし穴を知る — 「⚠️ 落とし穴」を読み、 自分のコードに該当箇所がないか確認
  6. 関連手法を学ぶ — 「🌐 関連手法・派生」で次に学ぶべき派生概念へ
  7. 論文で活用 — 上位「📚 関連グループ教材」のページで実論文の文脈を確認

焦らず、 1 段ずつ確実に。 7 ステップを 1 周すれば、 単に「知っている」から「使える」レベルに到達できます。

confusion matrix Normalized Con Per-class Prec Cost Matrix Cohen's Kappa Matthews Corre K×K

🔗 隣接手法への橋渡し

「混同行列」は分類結果の TP/FP/FN/TN を 2×2 に並べた診断ツールで、 上流のクラス不均衡の確認と下流の指標選択 (Precision/Recall/F1) を組まないと、 精度 99% でも実務では使えないモデルを量産する。

上流でクラス分布と運用上の誤分類コストを整理し、 並列の ROC/PR 曲線で閾値の効果を可視化し、 下流で Precision/Recall/F1/Balanced Accuracy を運用要件に合わせて選べば、 医療診断・不正検知のようなコスト非対称タスクを評価できる。

🌳 手法選択フロー

「混同行列」を実際に使うとき、 何をどう選ぶかを順に判断する。 上から順に答えていくと、 使うべき手法と評価の仕方が決まる。

  1. 閾値は固定されているか
    固定なら混同行列で TP・FP・TN・FN を直接読む。 閾値を動かして比べたいなら、 混同行列 1 枚ではなく ROC 曲線か PR 曲線を使う。
  2. 陰性が大多数か
    陰性が圧倒的なら ROC は楽観的に見えるので PR 曲線へ。 混同行列でも、 正解率ではなく Precision と Recall を読む。
  3. クラスは 2 つか、 3 つ以上か
    多クラスでは行列が K×K になり、 どのクラスとどのクラスを取り違えているかが見える。 要約するときはマクロ平均かマイクロ平均かを明記する。
  4. 誤りのコストは非対称か
    見逃しと誤検出でコストが違うなら、 混同行列の 4 マスに金額や工数を割り当てて期待損失で比べる。 指標 1 つに丸めない。

混同行列は「1 つの閾値でのスナップショット」。 人口 100 万人超を当てる例では 47 件しかないので、 各マスの件数が 1 桁になり比率が大きく揺れる点に注意。

🔎 さらに深掘り:混同行列を「行列そのもの」として読む

本セクションは既存の解説を壊さない追補です。 精度・再現率・F1 が「行列から絞り出した 1 個のスカラー」であるのに対し、 ここでは 混同行列という 2×2(多クラスなら k×k)の表そのものを主役に据え、 「4 つのマスがどう連動するか」という角度で読み解きます。 各小見出しは独立して読めます。

🎨 直感:スカラーではなく「間違いの地図」

混同行列(confusion matrix)は、 予測を 1 つの点数に潰す前の 「どこで、 どう間違えたか」の地図です。 2 値なら行=実際・列=予測の 2×2 で、 対角(TP・TN)が正解、 非対角(FP・FN)が誤りの種類を分けて保存します。 ここで決定的に重要なのは、 FP と FN は本質的に違う間違いだということ。 FP は「陰性を陽性と言い張った」=第一種の過誤(false alarm)、 FN は「陽性を見逃した」=第二種の過誤(miss)です。 医療の見逃し(FN)と誤診による過剰検査(FP)が同じ重みでないのと同様、 この 2 つを 1 つの数字に混ぜた瞬間、 意思決定に必要な情報が失われます。 だから「まず行列を見る、 スカラーはそのあと」が鉄則です。

🕳 落とし穴(重要):閾値を動かすと 4 セルは「連動して」動く

初学者がつまずく最大の点は、 4 つのマスは独立に増減しないことです。 閾値を下げて「陽性」と判定する範囲を広げると、 陽性判定が増えるので TP は増え FN は減る(縦にトレードオフ)。 だが同時に FP が増え TN が減る。 つまり 見逃しを減らそうとすると必ず誤検知が増える——タダで両方は良くならない、 というのが混同行列が可視化してくれる核心です。

これを公的統計 SSDSE-B-2026(encoding='cp932', skiprows=[1]、 2023 年の 47 都道府県)の実測値で確かめます。 「真の陽性=高齢県(65 歳以上人口比率 A1303/A1101 ≥ 32%、 該当 23 県)」を当てる問題で、 予測ルールを「総人口 A1101 が T 万人未満なら高齢県と予測」(過疎=高齢という不完全な代理指標)とし、 閾値 T を動かしたときの混同行列 4 セルの実測が次表です。

閾値 T(総人口) TP FN FP TN Recall Precision Accuracy
< 100 万7163210.3040.7000.596
< 150 万1766180.7390.7390.745
< 200 万20311130.8700.6450.702
< 250 万22112120.9570.6470.723
< 300 万2211590.9570.5950.660

表を縦に眺めると連動が一目瞭然です。 閾値をゆるめる(T を上げる)ほど FN は 16→1 へ激減する一方、 FP は 3→15 へ増大し、 Recall は 0.304→0.957 に上がるが Precision は途中でピーク(T<150 万で 0.739)を過ぎて下降します。 「見逃しを潰す=誤検知が増える」という交換関係そのものが、 混同行列の 4 セルの動きとして現れています。 このトレードオフを閾値を変えながら曲線にしたものが ROC 曲線PR 曲線です(ページ上部の「🎮 閾値を動かして混同行列を見る」ウィジェットでも同じ挙動を体感できます)。

もう一つの重大な落とし穴が 不均衡下では対角の面積(正解率)がほぼ無意味になること。 同じ SSDSE-B-2026 で「真の陽性=総人口 700 万人超の県」とすると該当は 5 県(埼玉・東京・神奈川・愛知・大阪)、 陽性率わずか 10.6%。 ここで「全県を陰性と予測する」だけの手抜き分類器の混同行列は TP=0, FN=5, FP=0, TN=42 となり、 Accuracy=42/47=89.4% と高得点。 しかし対角の TN に面積が偏っているだけで、 Recall=0/5=0%(陽性を 1 つも当てていない)。 行列を見れば TP 列が空という致命傷が即座に分かるのに、 正解率という 1 スカラーはそれを隠します。 詳しくはクラス不均衡を参照。

  • 行と列を取り違える(転置ミス):sklearn の confusion_matrix は既定で 行=真値・列=予測。 教科書や他ライブラリでは逆の流儀もあり、 TP と TN、 FP と FN が入れ替わって解釈が崩壊します。 必ず labels= と軸の意味を明示しましょう。
  • 正規化の方向で別の指標になる:行方向正規化(normalize='true')の対角はクラス別 Recall、 列方向正規化('pred')の対角はPrecision。 「正規化ヒートマップ」を見せられたら、 どちらで割ったのかを先に確認しないと誤読します。
  • 「正解率が高い=良い行列」ではない:上の 89.4% のように、 対角の合計だけでは間違いの偏りが見えません。

🚀 発展:2×2 を超える読み方

  • 多クラスの非対角は「取り違えペア」の一覧:k×k 混同行列では、 セル (i, j) が「実際は i なのに j と予測した件数」。 大きな非対角セルを探すと、 モデルがどのクラス同士を混同しているか(例:数字認識で 4 と 9)が具体的に分かります。 スカラーには決して残らない情報です。
  • 1 対他への分解:多クラス行列は、 各クラスを陽性・残りを陰性とみなした k 個の 2×2 行列に分解でき、 そこから macro / micro 平均の PrecisionRecallF1 が導かれます。 特異度=TN/(TN+FP) もこの分解で計算します。
  • コスト行列との積で「損失」に翻訳:4 セルの件数に FP・FN それぞれの被害額を掛けて合計すれば、 業務上の総コストになります。 閾値はこの総コストを最小化する点で選ぶのが本筋で、 正解率最大化とは一般に一致しません。
  • 閾値非依存の要約:全閾値の混同行列を掃引して面積にしたのが ROCAUC / PR-AUC。 単一行列が「ある 1 点」なのに対し、 曲線は「点の軌跡」を評価します。
  • 第二種の過誤との対応:FN は統計的検定でいう第二種の過誤(見逃し)に相当し、 検出力=Recall。 分類と検定が同じ 2×2 構造を共有していることが見えると理解が一段深まります。

🔗 関連ページ

※ 本セクションの数値は SSDSE-B-2026(2023 年・47 都道府県)の実測値に基づき、 Python で算出して転記したもの。 捏造はありません。 予測ルール(総人口による代理判定)はあくまで閾値トレードオフを示すための不完全な例であり、 高齢化の妥当な予測モデルを主張するものではありません。