サンプル事例

    Python 3.11.15
    OS macOS-26.5.1-arm64-arm-64bit
    pandas 3.0.2
    numpy 2.4.3
    matplotlib 3.10.9
    seaborn 0.13.2
    scikit-learn 1.8.0
    shap 0.51.0
    lightgbm 4.6.0
    

データの分割

機械学習では、データを役割に応じて3つに分けて使うことがある。

例えば、70, 15, 15に分けたり、まず80と20(Test用)に分けて、その80をさらにTrain 80, Validation 20に分けたりする。

Validation(検証用)を設ける意義とは?

このように、ValidationやTestを適切に分けることで、学習に使っていない未知のデータに対してもモデルがどの程度通用するのかを確認できる。

Validationを設けない場合もある
評価指標を計算するデータ

モデルの調整中に評価指標を見る場合は、基本的にはValidationデータを用いる。

# 検証データで評価
y_pred_val = model.predict_proba(X_val)[:, 1]

# モデルの調整がすべて終わり最終的な性能を確認するときはTestデータで評価
y_pred_test = model.predict_proba(X_test)[:, 1]

# 学習データ(Train)についても評価指標を計算することはできる
y_pred_train = model.predict_proba(X_train)[:, 1]

ただし、学習データでの評価結果は、モデルが実際に学習したデータに対する性能。未知のデータに対する性能を見るValidationやTestとは意味が異なる。

Trainの評価結果を見てモデルをチューニングすることは通常ない。学習データに対して良い結果になるよう調整してしまうと、過学習を見逃す可能性があるため。

分類性能の評価

モデルが最終的に「1」か「0」かを予測した結果を評価する。実際の結果と予測結果を組み合わせると、2×2の混同行列として整理できる。対角線上のセルが多いほど、正しく分類できた件数が多い。

予測: 負例 予測: 正例
実際: 負例 TN(True Negative) FP(False Positive)
実際: 正例 FN(False Negative) TP(True Positive)

混同行列(Confusion Matrix)の各象限

混同行列における配置は常に同じとは限らないので、場所で覚えちゃダメ

サンプルデータでの混同行列

サンプル事例での結果は、合計5,662人のうち実際の購入者は1,718人(出現率30%)、非購入者は3,944人。この4セルのどこに注目するか。

TP (True Positive):  1053 件
TN (True Negative):  2680 件
FP (False Positive): 1264 件
FN (False Negative): 665 件

Accuracy

すべての予測対象のうち、正しく予測できた割合(正解率)。

Accuracy = (TP + TN) / (TP + TN + FP + FN)

Accuracyは数値の意味がわかりやすく、モデルの基本的な予測性能比較など有用な場面もある。

Precision

正例と予測した対象のうち、実際に正例だった割合(適合率)。

Precision = TP / (TP + FP)

Recall

実際は正例の対象のうち、正しく正例と予測できた割合(再現率)。

Recall = TP / (TP + FN)

PrecisionとRecallのトレードオフ

2値分類モデルは基本的に、正例である確率(0.0〜1.0)を出力する。これを人間が判断しやすい二択のラベル(0/1)に振り分けてる。

しきい値(threshold)を0.5(一般的なデフォルト値)にするとは、すなわち、確率0.5以上は「1:購入する」と予測、確率0.5未満は「0:購入しない」と予測したものとみなす、という意味。なお0.5は「正例と予測する確率が半分以上」という直感的な(自然な)基準であり、常に最適なしきい値というわけではない。

しきい値を高くすると、かなり確信のある人だけを正例とするため、Precisionは高くなりやすい。そのぶん正例と予測する人数が減るので、Recallは低くなりやすい。

逆に、しきい値を低くすると、少しでも購入可能性があれば正例とするため、Recallは高くなりやすい。ただし実際には買わない人も多く含まれるため、Precisionは低くなりやすい。

例えば、予定しているクーポン配布施策の予算が潤沢にあるなどの理由で、買う確率が0.3ぐらいの人もクーポン配布対象にしたいなどのビジネス判断が先にある、検討余地がある場合には、しきい値を0.5ではなく0.3にして評価するという手もある(詳細後述)。

つまり、しきい値を動かすことでもPrecisionとRecallの値は変わるため、「何のための予測なのか」「何を重視したいのか」が明確になってないと、モデルの評価、判断もしにくい。

F1-score

PrecisionとRecallの両方を考慮したい場合に。

F1 = 2 * (Precision * Recall) / (Precision + Recall)


PrecisionとRecallからF1へのつながり

  1. PrecisionとRecallはトレードオフの関係にある。
  2. Precisionを1(100%)に近づけたいなら、予測確率の判定基準であるしきい値を1に極力近づけて、確実に買いそうな人だけを少数予測すればいい。代償として、Recallは低くなる。
  3. 逆に、Recallを1に近づけたいなら、しきい値を0側にレバー倒して、ほぼ全員買うと予測すればいい。ただしPrecisionはズタズタになる。
  4. こういう極端なことなしに、どちらもほどほどに両立した状態を1つの指標で表せられたらいいのに。。。

対処策1

  1. まず思いつくのは、PrecisionとRecallを足して2で割る、つまり平均化する発想。 (Precision + Recall) / 2
  2. でもこれだと、Precision0.95 Recall0.15のときも、Precision0.15 Recall0.95のときも、平均は0.55となる。
  3. 片方が極端でもまあまあな数値に見えてしまう。足して2で割る発想自体はよくても、欲しい状態を的確に表す指標にはなってない。。。

対処策2

  1. 目指したいのは、両方の値のバランスが良いときは高得点になり、いずれかが極端な値のときはペナルティが大きくなるような仕組み。
  2. どちらの値も0から1(0%から100%)の範囲内にある。であれば0に近いときは大きく減点すれば、欲しい状態を的確に表せるのではないか。
  3. そこで2つの値それぞれの逆数をとってから平均化する発想。 (1/Precision + 1/Recall) / 2
  4. これだと、Precision0.95 Recall0.15のときは、(1/0.95 + 1/0.15) / 2 = 3.68
  5. 逆数をとった値の平均値は元の単位と違ってて解釈しにくいので、改めて逆数をとって元の単位に戻す。1 / 3.68 = 0.27
  6. 同じ計算をPrecision0.75 Recall0.35でやると、(1/0.75 + 1/0.35) / 2 = 2.10 の逆数で 0.48
  7. 同じ計算をPrecision0.65 Recall0.45でやると、(1/0.65 + 1/0.45) / 2 = 1.88 の逆数で 0.53
  8. 同じ計算をPrecision0.60 Recall0.55でやると、(1/0.60 + 1/0.55) / 2 = 1.74 の逆数で 0.57
F1 score = 1 / { (1/Precision + 1/Recall) / 2 }
         = 2 / { (1/Precision) + (1/Recall) }
         = 2 / { (Recall + Precision) / (Precision * Recall) }
         = 2 * Precision * Recall / (Precision + Recall)

なお、PrecisionとRecallのどちらかを明確に重視したい場合には、F1-scoreを拡張したFβ-scoreという指標もある。βを大きくするとRecall、小さくするとPrecisionをより重視した評価になる。

ただし、どの程度の重み付けが適切かは用途によって異なる。実務では、Fβ-scoreという1つの数値にまとめるよりも、PrecisionとRecallを個別に確認し、施策コストや取りこぼしの損失を考慮して判断するほうがわかりやすい場面も多い。

MCC

「このモデルは、正例と負例を本当に区別できているのか?」を確認したいときに役立つ指標。

Precision, Recall, F1-scoreは主に正例に注目した指標。対してMCCは、混同行列の4セルすべてを使って、実際のクラスと予測クラスの一致関係を評価する指標(実際と予測の相関係数)。Matthews Correlation Coefficient

MCC = (TP × TN − FP × FN) / √{(TP + FP)(TP + FN)(TN + FP)(TN + FN)}

mcc = (tp * tn - fp * fn) / math.sqrt((tp + fp) * (tp + fn) * (tn + fp) * (tn + fn))
# 一気に計算する場合、モデルが全員を同じクラスに予測した時などに、分母が0になるケースがある

# 分子の計算: (TP * TN) - (FP * FN)
mcc_numerator = (tp * tn) - (fp * fn)
# 分母の計算: sqrt((tp+fp) * (tp+fn) * (tn+fp) * (tn+fn))
mcc_denominator = math.sqrt((tp + fp) * (tp + fn) * (tn + fp) * (tn + fn))
# 分母が0の場合はMCCを0とする(ライブラリ次第)
if mcc_denominator == 0:
    mcc = 0
else:
    mcc = mcc_numerator / mcc_denominator

print(f"MCC (Matthews Correlation Coefficient): {mcc:.3f}")

MCCのとる範囲は-1から1の間。

正例と負例の両方を考慮するため、クラス不均衡な場合にも、モデルの分類性能をシビアに評価しやすい(多数派のクラスで正答を稼いだ高Accuracyなモデルでも、MCCは高くなりにくい)。

Balanced Accuracy

クラス不均衡でも、正解率をシンプルかつ公平に評価したい場合に(均衡正解率)。

Balanced Accuracy = (Recall + Specificity) / 2

Specificity(特異度) : 実際は負例の対象のうち、正しく負例と予測できた割合(真陰性率) Specificity = TN / (TN + FP)

同じ重みとは? 例えば正例の出現率が1%で、かつ正例のみに関心がある場合に、1%しかない大事な貴重な正例を半分見逃すのと、99%の負例(不要なもの)を半分拾いすぎるのを、同程度にダメ出しするようなこと。

逆に言えば、AccuracyはRecallとSpecificityを出現率で重み付けしている(がために正例・負例の出現率次第で、数値の見え方、評価の基準が変わってくる)。

Accuracy = ( TP + TN ) / Total
         = ( TP / Total ) + ( TN / Total )
         = { TP / (TP+FN) * (TP+FN) / Total } + { TN / (TN+FP) * (TN+FP) / Total }
         = (Recall * 正例出現率) + (Specificity * 負例出現率)
つまりAccuracyは、データに多く存在するクラスの正解率ほど強く反映される。

その他の指標

4セルの組み合わせ次第で、他にもいろんな指標が算出可能。似たものにも見えるが、分母の捉え方がちょっとずつ異なる。

指標 意味 補足
NPV (Negative Predictive Value) TN / (TN + FN) 負例と予測した対象のうち、実際に負例だった割合(陰性的中率) Precisionの裏に相当。負例にあまり関心がない場面では影が薄い。精密機器の良品判定(不良品と予測して実際に不良品でした)などで有用。
Specificity TN / (TN + FP) 実際は負例の対象のうち、正しく負例と予測できた割合(真陰性率)。 Recallの裏に相当する。スパム判定(大事なメールを間違えてゴミ箱に入れない)などで有用。負例を精度高く予想したい、負例を正しく負例としたいときに。
FDR (False Discovery Rate) FP / (TP + FP) 正例と予測した対象のうち、実際は負例だった割合(偽発見率) 分母はPrecisionと同じ。Precisionの余事象に相当(FDR = 1 - Precision)。
FPR (False Positive Rate) FP / (TN + FP) 実際は負例の対象のうち、誤って正例と予測した割合(偽陽性率) 分母はSpecificityと同じ。Specificityの余事象に相当(FPR = 1 - Specificity)。のちほど登場する指標ROC曲線、ROC-AUCに関わってくる。
FNR (False Negative Rate) FN / (TP + FN) 実際は正例の対象のうち、誤って負例と予測した割合(偽陰性率) 分母はRecallと同じ。Recallの余事象に相当(FNR = 1 - Recall)。見逃し(False Negative)がクリティカルに直結する(命に関わる)ような医療現場などで有用。
Cohen's Kappa(カッパ係数)

MCC同様に実際のクラスと予測クラスの一致度を評価する指標。違いは、偶然による一致を考慮する点。実際と予測が一致したのは、モデルの実力なのか、それともたまたまなのか。たまたま当たったぶん(期待値)を全体から引いて、純粋な予測の重みを評価する。

クラスの出現率割合と予測割合から「偶然でもこれくらいは一致しそう」という一致率を考え、その分を差し引いて評価する。

Cohen's Kappaのとる範囲は-1から1の間。

ただし、クラスの偏りや予測割合によって値が影響を受ける場合があり、機械学習の分類性能評価では、MCCやBalanced Accuracy、Precision/Recall系の指標のほうが解釈しやすいことが多い。

# 1. 数値をセット
total = tp + tn + fp + fn

# 2. po (実際の正解率)
po = (tp + tn) / total

# 3. pe (偶然当たっちゃう確率)
# 「モデルの正例予測数 × 実際の正例数」 + 「モデルの負例予測数 × 実際の負例数」 を Totalの2乗で割る
pe = ((tp + fp) * (tp + fn) + (tn + fn) * (tn + fp)) / (total ** 2)

# 4. Kappaの計算
kappa = (po - pe) / (1 - pe)

# これでもOK
# from sklearn.metrics import cohen_kappa_score
# kappa = cohen_kappa_score(y_val, y_class_pred)

print(f"Cohen's Kappa: {kappa:.3f}")

ここまでの指標をどう使い分けるか

指標 何を見てるか
Accuracy 0.659 全体として、どれだけ正解したか
Precision 0.454 正例と予測した人を、どれだけ確実に当てられたか
Recall 0.613 実際の正例を、どれだけ取りこぼさずに拾えたか
F1-score 0.522 PrecisionとRecallを両立できているか
Balanced Accuracy 0.646 正例・負例を同じ重みで評価した正解率
MCC 0.273 正例・負例を含め、分類が全体としてどれだけ正しく対応しているか

ただし、これらの指標の多くは、どのしきい値で0と1を分けるかによって値が変わる。そのため「何のために予測するのか」「PrecisionとRecallのどちらを重視するのか」を決めずに、数値だけを比較してもモデルの良し悪しは判断しにくい。

しきい値によらない識別性能

ROC曲線

ROC曲線は、分類のしきい値を変化させたときの、

の関係を表した曲線。Receiver Operating Characteristic Curve)

これを2次元の平面に表現する。

ROC曲線のプロット手順
顧客 購入確率
A 0.95(実際:1)
B 0.88(実際:1)
C 0.75(実際:0)※間違い、空振り
D 0.60(実際:1)
… …(以下、確率が低い順に続く。データ10人分での例)
  1. 予測確率が高い順にデータを並べる。
  2. しきい値を上から下に「スライド」させる。
    1. 最初に、一番上に線を引く(しきい値1.0)
      • 誰も「1」とは予測しない、超ドケチな設定。
      • TPR(真陽性率:見落とさず当てた割合)=0 (誰も当ててない)
      • FPR(偽陽性率:間違えて空振りした割合)=0 (誰も予測してないから空振りもゼロ) グラフの座標は (0, 0) からスタート!
    2. 1番目のAの下に線を引く(しきい値0.90)
      • 「Aだけを1、残りを全員0」と予測する設定。
      • Aは実際に1で、見落とさずに当てられたから、TPR(縦軸)が上にピョコッと1歩上がる。
      • 空振り(実際は0なのに1と予測する)はしていないから、FPR(横軸)は動かない。
    3. 3番目のCの下に線を引く(しきい値0.70)
      • 「A、B、Cを1、残りを0」と予測する設定。
      • Cは実際には0(買わなかった)だから、モデルの予測はハズレ(空振り)。
      • このときは、FPR(横軸)が右にピョコッと1歩進む。(縦軸は動かない)
  3. 最後まで繰り返して線を結ぶ
ROC曲線における良いモデルと悪いモデル
ROC曲線の特性

ROC-AUC

ROC曲線を描いて、その下の面積を全部足し算(積分)した値。AUC(Area Under the Curve)

ROC-AUCは0から1の値をとり、

以下のような解釈も成り立つ。

過去に提案された教科書記載などから、AUCが 0.7以上なら実用的、0.8を超えたら優秀、といった目安はあるものの、機械的に適用するのは避けるべし。

なぜなら、どの程度の識別能力が必要なのかは、当然ながら用途やデータ、誤分類による損失によって変わるため。

ラテ購入などのように人間の気まぐれな行動に左右されやすい予測が困難な場面もあれば、品質管理や命に関わる場面などもある。

ROC-AUCの強みと注意点

だがビジネス場面においては、ROC-AUCだけでは判断材料不足な場合もある。

ビジネス場面では、

そこで、

となると、Recallは据え置きつつ、正例を正確に予測したい→Precisionも重視したい、との観点が台頭してくる。

PR曲線

PR曲線(Precision-Recall Curve)は、分類のしきい値を変化させたときの、

の関係を表した曲線。これを2次元の平面に表現する。上にいくほど、(買う予測はなるべく外さないことで)ムダ打ちが少なく、右にいくほど取りこぼしが少ない。

PR曲線の特性

PR-AUC

ROC曲線を描いて、その下の面積を全部足し算(積分)した値。正例を見つける能力に焦点を当てた識別性能を表す指標。

指標 意味 サンプル事例
ROC-AUC モデルの基本的な予測性能(純粋な識別能力) 購入者と非購入者を全体としてどれだけうまく識別できるか
PR-AUC 正例を見つける能力に焦点を当てた識別性能 購入者を見つけるときに、どれだけ効率よく購入者を拾えるか

※効率よく=正例を多く見つけながら、誤検出をどれだけ抑えられるか

PR-AUCとAverage Precision

PR曲線を描いて、その下の面積を全部足し算(積分)した値。その代表的な方法がPR-AUCだけども、「PR-AUC」と「Average Precision(AP)」は同じものとは限らないことに注意。

Precision-Recall曲線の面積を単純に台形公式などで計算する方法と、scikit-learnのaverage_precision_scoreのように、Recallの変化量を重みとしてPrecisionを平均する方法では、結果が異なる場合がある。

なので、PR-AUCという言葉だけで済ませず、どの方法で曲線を要約した値なのかを示す、確認するのも大事。

ROC-AUCとPR-AUCの使い分け

どちらが優れているというものではなく、注目するポイントが異なる。

ROC曲線とPR曲線
ROC-AUC PR-AUC
注目するもの 正例と負例の識別能力 正例をどれだけ正確に拾えるか
縦軸 Recall(TPR) Precision
横軸 FPR Recall
クラス不均衡 影響を受けにくい 正例割合の影響を受ける
向いている場面 全体的な識別能力を確認したい 正例が少なく、正例の予測精度を重視したい

サンプル事例のように「明日ラテを買う可能性が高い人を見つけ、その人たちに何らかの施策をおこなう」という用途なら、PrecisionとRecallのバランスは特に重要。そのため、ROC-AUCだけで「このモデルは良い」と判断するのではなく、PR曲線やPR-AUCも確認する必要がある。

さらに実際の施策では、

なども考慮して、最終的なしきい値を決めることになる。

評価指標は、モデルに「何点だったか」を付けるためだけのものではない。「このモデルを実際にどう使うのか」を考え、その目的に合った指標を選ぶことが重要。