二値分類モデルの良し悪しを、たった一つの数字で表したい — そう思ったとき多くの人がまず思い浮かべるのが「正解率(accuracy)」です。ところが正解率には落とし穴があります。たとえば病気の人が1%しかいないデータで「全員を健康と判定する」モデルを作ると、正解率は99%にもなります。何も予測していないのに、です。この問題を回避し、しきい値の選び方によらずにモデルの「順位付けの良さ」を測るのがROC曲線とAUCです。
ROC曲線とAUCを理解すると、次のような場面で役立ちます。
- 医療診断や異常検知 — 見逃し(偽陰性)と誤報(偽陽性)のバランスを、しきい値を動かしながら一望できる
- モデル選択とコンペ — 不均衡データでもしきい値に左右されずにモデルを比較でき、scikit-learnの
roc_auc_scoreの中身がわかる
本記事の内容
- 「しきい値を動かす」という発想からROC曲線を導入
- TPR・FPRの定義と混同行列との対応
- ROC曲線が描かれる様子をステップバイステップで可視化
- AUC=ランダムな正例が負例より高スコアになる確率、という意味の証明
- 不均衡データでROCが楽観的になる問題とPR曲線の使い分け
- 最適しきい値の選び方(Youden指数・コスト考慮)とPythonスクラッチ実装
- 多クラスへの拡張(One-vs-Rest・マクロ/ミクロ平均)
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
出発点:分類器はスコアを出し、しきい値で判定する
多くの分類器は、最終的に0か1かを直接出すのではなく、まず「その入力が陽性である度合い」を表すスコア(確率や決定関数の値)を出します。そのスコアがしきい値 $t$ 以上なら陽性、未満なら陰性、と決めることで0/1の判定が生まれます。
ここで大事なのは、しきい値 $t$ は後から自由に選べる、ということです。$t$ を高くすれば「よほど自信があるものだけ陽性」と慎重になり、誤報は減りますが見逃しが増えます。$t$ を低くすれば逆に、見逃しは減りますが誤報が増えます。しきい値を動かすと判定が変わる — この発想がROC曲線のすべての出発点です。
下の図は、陰性(負例)と陽性(正例)のスコア分布が重なっている様子と、しきい値による判定の切り分けを示したものです。

陰性(青)と陽性(赤)の分布は完全には分かれず、重なっています。しきい値 $t$ より右を陽性と判定すると、赤の右側が正しい陽性(TP)、青の右側が誤報(FP)、赤の左側が見逃し(FN)になります。しきい値を右に動かせば誤報は減るが見逃しが増える — このトレードオフを一枚の曲線に描くのがROCの狙いです。
では、このトレードオフを数値化する2つの指標、TPRとFPRを定義しましょう。
TPRとFPRの定義:混同行列のどこを見るか
しきい値を一つ固定すると、予測と正解の組み合わせは4種類に分かれ、混同行列にまとめられます。ここから2つの率を定義します。
イメージとしては、TPRは「実際に陽性だった人を、どれだけ取りこぼさず拾えたか」、FPRは「実際は陰性なのに、間違って陽性と言ってしまった割合」です。前者は高いほど、後者は低いほど良い指標です。
$$ \mathrm{TPR} = \frac{TP}{TP + FN}, \qquad \mathrm{FPR} = \frac{FP}{FP + TN} $$
TPR(真陽性率、再現率・感度とも呼ぶ)の分母 $TP + FN$ は「実際の陽性の総数」です。つまりTPRは混同行列の陽性の行だけから計算します。FPR(偽陽性率)の分母 $FP + TN$ は「実際の陰性の総数」なので、陰性の行だけから計算します。これを図で確認しましょう。

この図のポイントは、TPRは陽性の行、FPRは陰性の行から別々に計算されるという点です。両者は異なるクラスの「中での割合」なので、陽性と陰性の件数比(クラスの不均衡)には直接は左右されません。この性質が、後で見る「不均衡データでROCが楽観的になる」問題の伏線になります。
数字で一度確かめておきましょう。実際の陽性が100件、陰性が900件のデータで、あるしきい値のもとで陽性のうち80件を正しく拾い(TP=80、FN=20)、陰性のうち90件を誤って陽性と判定した(FP=90、TN=810)とします。このとき $\mathrm{TPR}=80/(80+20)=0.80$、$\mathrm{FPR}=90/(90+810)=0.10$ です。ここで注意したいのは、この同じ分類器の性能(陽性の80%を拾い、陰性の10%を誤報する)を保ったまま陰性を9000件に増やしても、TPRもFPRも値は変わらない点です。比率で定義されているおかげで、クラスの件数を変えてもROC上の点は動きません。一方で「誤報の絶対数」は90件から900件へ10倍になります。率は不変でも、痛みの絶対量は不均衡で激変する — この食い違いが後半の主題になります。
TPRとFPRが定義できたので、いよいよしきい値を動かしてこの2つを追跡し、ROC曲線を描いてみましょう。
ROC曲線の描かれ方:しきい値を動かして点をつなぐ
ROC曲線は、横軸にFPR、縦軸にTPRを取り、しきい値 $t$ を高い方から低い方へ連続的に動かしたときの点 $(\mathrm{FPR}, \mathrm{TPR})$ の軌跡です。
- $t$ がとても高い → ほぼ何も陽性と判定しない → TPRもFPRもほぼ0(左下)
- $t$ がとても低い → ほぼ全部を陽性と判定する → TPRもFPRもほぼ1(右上)
つまり曲線は必ず左下 $(0,0)$ から右上 $(1,1)$ へ向かいます。途中、しきい値を下げるたびに点が右や上へ動きます。3つのしきい値での判定と、それに対応するROC上の点を見てみましょう。

上段の3つは、しきい値が高い(A)・中間(B)・低い(C)ときのスコア分布の切り分けです。しきい値が高いAではTPR=0.22・FPR=0.00とほぼ左下、中間のBでTPR=0.86・FPR=0.12、低いCではTPR=0.99・FPR=0.80と右上に寄ります。下段のROC曲線上で、A・B・Cがちょうど左下から右上へ並んでいます。しきい値を下げるほど点は右上へ進む — これがROC曲線の描かれ方です。この例のAUC(曲線下の面積)は0.948でした。
なめらかな曲線だと「描かれ方」が見えにくいので、データを10件だけにして、1件ずつ階段を上る様子も見ておきましょう。

左は10件をスコアの高い順に並べたものです。上から順に見ていき、その件が正例なら上へ1歩(縦に $1/5$、正例は5件)、負例なら右へ1歩(横に $1/5$、負例は5件)進みます。こうして描いた右の階段がROC曲線で、その下の塗った面積がAUC=0.72です。ROC曲線は「スコアの高い順に正例・負例を並べ替えた結果」をそのまま描いたものだとわかります。
ここで一つ深い疑問が生まれます。この「面積(AUC)」には、図形的な以上の意味があるのでしょうか。実は、とても直感的な確率の意味があります。
AUCの意味:ランダムなペアの勝率
AUCには次の美しい解釈があります。
AUCは、ランダムに1つ選んだ正例のスコアが、ランダムに1つ選んだ負例のスコアより高くなる確率に等しい。
つまりAUCは「正例の方が負例より高いスコアを付けられているか」という順位付けの良さを測っているのです。これを証明しましょう。
ゴールは、$\int_0^1 \mathrm{TPR}\, d(\mathrm{FPR})$ が確率 $P(S^+ > S^-)$ に等しいことを示すことです。ここで $S^+$ は正例のスコア、$S^-$ は負例のスコアを表す確率変数とします。
まず、しきい値 $t$ におけるTPRとFPRを生存関数で書きます。
$$ \mathrm{TPR}(t) = P(S^+ > t), \qquad \mathrm{FPR}(t) = P(S^- > t) $$
AUCは横軸FPRに沿った積分なので、積分変数を $u = \mathrm{FPR}(t) = P(S^- > t)$ に置き換えます。$t$ を $+\infty$ から $-\infty$ へ動かすと $u$ は $0$ から $1$ へ動きます。ここで $u$ の微小変化 $du$ は、負例スコアの確率密度 $f_-(t)$ を使って $du = -f_-(t)\, dt$ と書けます($t$ が増えると $u$ は減るのでマイナスが付きます)。これを代入すると、
$$ \mathrm{AUC} = \int_0^1 \mathrm{TPR}\, du = \int_{-\infty}^{\infty} P(S^+ > t)\, f_-(t)\, dt $$
積分の向きをそろえるため、$t$ について $-\infty$ から $+\infty$ へ積分する形に整えました。次に、内側の $P(S^+ > t)$ を期待値として書き直します。$f_-(t)\, dt$ は「負例スコアが $t$ 付近である確率」なので、この積分は「負例スコア $S^-$ について、正例スコアがそれを上回る確率を平均する」操作になります。
$$ \mathrm{AUC} = \mathbb{E}_{S^-}\!\left[\, P(S^+ > S^- \mid S^-) \,\right] = P(S^+ > S^-) $$
最後の等号は、条件付き確率を $S^-$ について平均すれば条件を外した同時確率になる、という全確率の法則です。こうして、AUC=ランダムな正例が負例より高スコアになる確率が示せました。
この解釈は、先ほどの10件の階段の例で直接確かめられます。正例5件・負例5件の全25ペアについて「正例の方が高スコアか」を数えると、ちょうどAUCと一致するはずです。

25ペアのうち「正例の方が高スコア」(緑の○)が18組、つまり $18/25 = 0.72$ で、先ほど面積から求めたAUC=0.72と完全に一致しました。AUCは図形の面積であると同時に、ペア比較の勝率でもある — この二つが同じ数になることが目で確認できます。
このペア勝率の見方は、統計学とも深くつながっています。「正例と負例のペアで正例の方が大きい確率」は、ノンパラメトリック検定で有名なマン・ホイットニーのU統計量を全ペア数で割ったものに一致します。つまりAUCは、2群のスコア分布が「どれだけずれているか」を順位だけで測る量でもあるのです。また、AUCを使って $G = 2\,\mathrm{AUC} – 1$ と定義したジニ係数もよく使われ、信用スコアリングの分野では「Gini」という名で性能を報告する慣習があります。AUCが0.5のときGiniは0、AUCが1のときGiniは1になり、「ランダムからどれだけ離れたか」を0〜1で表す指標になります。どれも中身は同じ「順位の良さ」を別の尺度で言い換えたものだと理解しておくと、文献を読むときに混乱しません。
この解釈から、AUCの極端な値の意味もすぐわかります。
AUC=0.5 と AUC=1.0 の意味
ペア勝率の解釈を使うと、AUCの基準値が腑に落ちます。
- AUC=1.0 — どんな正例も、どんな負例より高スコア。完全に分離できている理想状態。ROC曲線は左上の角 $(0,1)$ を通ります。
- AUC=0.5 — 正例と負例のスコアに区別がなく、ペア勝率はコイン投げと同じ。ROC曲線は対角線 $y=x$ に一致し、ランダム判定と同等であることを意味します。
- AUC<0.5 — 正例の方が低スコアになりがち、つまり予測を「逆」にしているサイン。スコアの符号を反転すれば $1-\mathrm{AUC}$ になります。
スコア分布の分離度を変えると、ROC曲線とAUCがどう動くかを見てみましょう。

分布がほぼ完全に分離していると、ROCは左上の角に張り付き、AUCは1に近づきます。分離が弱まるにつれ曲線は対角線に近づき、ランダム同然(分離なし)ではAUCが0.5付近になります。分離が良いほどROCは左上に張り付き、AUCは1に近づく — これが分離度とAUCの直感的な対応です。
ここまではROCとAUCが万能のように見えますが、実は大きな落とし穴があります。クラスが極端に不均衡なときです。
不均衡データの落とし穴とPR曲線
前述のとおり、FPRは陰性の中での割合なので、陰性が膨大にあると「FPRが小さく見えても、絶対数では大量の誤報」という事態が起きます。陽性がわずか1%しかないデータで、同じ分類器をROCとPR(適合率・再現率)の両方で見てみましょう。

左のROC曲線はAUC=0.924と立派に見えます。しかしFPR=0.1の点に注目すると、陰性が10000件あるので誤報は1000件 — 陽性100件の10倍もの誤報が紛れ込んでいます。右のPR曲線(AP=0.264)はこの痛みを正直に映します。適合率(陽性と判定したうちの正解率)が低く、ランダムの基準線(陽性率1%)からの上積みが小さいことが一目でわかります。
なぜこうなるのかを、定義に戻って整理しましょう。適合率は $\mathrm{Precision}=TP/(TP+FP)$ で、分母に偽陽性 $FP$ を含みます。陰性が膨大だと、FPRが小さくても $FP$ の絶対数は大きくなり、$TP$ がそれに埋もれて適合率が下がります。つまり適合率は陰性の絶対数の影響を直接受ける指標です。ROCが使うFPRはこの絶対数を「陰性総数で割って」隠してしまうため、不均衡の痛みが見えなくなります。PR曲線はその痛みを適合率を通して正直に映すので、陽性が貴重で誤報のコストが高い問題(不正検知で正常取引が大量にある、希少疾患のスクリーニングなど)では、PR曲線とAP(平均適合率)の方が現場の感覚に合います。
ここから使い分けの指針が立ちます。
- クラスがおおむね均衡 → ROC/AUCで素直に評価できる
- 陽性が極端に少ない(異常検知・不正検知・希少疾患など) → PR曲線・AP(平均適合率)を主に見る。ROCだけだと楽観的になりやすい
ROCとPRの使い分けがわかりました。次は、実際に分類器を運用するときに避けて通れない「では、しきい値をどこに決めるか」という問題です。
最適しきい値の選び方
ROC曲線はすべてのしきい値を見渡しますが、運用では最終的に一つのしきい値を決める必要があります。代表的な選び方を2つ紹介します。
ひとつはYouden指数で、$J = \mathrm{TPR} – \mathrm{FPR}$ を最大にする点を選びます。これは「ROC曲線がランダム線(対角線)から最も垂直方向に離れた点」に対応し、見逃しと誤報を対称に重視するときの自然な選択です。もうひとつはコストを考慮する方法で、見逃し1件と誤報1件のコストが違う場合に、総コストを最小にするしきい値を選びます。たとえば病気の見逃しは誤報より痛い、というように重みを付けます。

左はYouden指数 $J=\mathrm{TPR}-\mathrm{FPR}$ を最大化する点(赤い縦線の長さが最大、$J=0.776$)です。右は「見逃し1件=コスト5、誤報1件=コスト1」とした総コスト曲線で、その最小点はしきい値0.671付近 — Youden最適の0.858より低めになっています。見逃しのコストが高いほど、しきい値を下げて陽性を拾いにいくのが合理的だからです。最適しきい値は目的(コスト構造)によって変わることを覚えておきましょう。
ここで強調しておきたいのは、AUCの計算自体はしきい値に依存しないということです。AUCは全しきい値を見渡した「順位の良さ」の要約なので、モデル同士を比較したり学習中の性能を追ったりするには便利です。しかし実際に運用する瞬間には、必ずどこか一つのしきい値を選ばなければなりません。その選び方は、AUCが教えてくれるものではなく、「見逃しと誤報のどちらをどれだけ嫌うか」という業務上の価値判断で決まります。AUCで良いモデルを選び、しきい値はコストやYouden指数で決める — この二段構えが実務の基本姿勢です。Youden指数は両者を対称に扱う中立的な選択、コスト最小化は非対称な現実を反映した選択、と覚えておくとよいでしょう。
最後に、これらをPythonで実装し、scikit-learnの結果と一致するか確かめます。
Pythonでのスクラッチ実装と検証
ROC曲線の計算を、まずnumpyだけでスクラッチ実装し、sklearn.metrics の結果と一致することを確認します。原理は単純で、スコアの高い順に並べ、上から累積した正例数・負例数からTPR・FPRを求めるだけです。
import numpy as np
def roc_curve_scratch(y_true, score):
"""しきい値を高い方から下げながら TPR, FPR を計算する"""
order = np.argsort(-score) # スコアの高い順に並べる
y = y_true[order]
tps = np.cumsum(y) # 上位k件に含まれる正例数
fps = np.cumsum(1 - y) # 上位k件に含まれる負例数
tpr = np.r_[0, tps / y.sum()]
fpr = np.r_[0, fps / (1 - y).sum()]
return fpr, tpr
def auc_trapz(fpr, tpr):
return float(np.trapezoid(tpr, fpr)) # 台形則で曲線下面積
# 10件の小さな例(前の図と同じデータ)
y = np.array([1, 1, 0, 1, 0, 1, 0, 0, 1, 0])
s = np.array([0.95, 0.86, 0.78, 0.71, 0.62, 0.55, 0.41, 0.33, 0.30, 0.12])
fpr, tpr = roc_curve_scratch(y, s)
print("スクラッチAUC =", round(auc_trapz(fpr, tpr), 4))
このコードを実行するとスクラッチAUCは0.72となり、先ほどペア勝率(18/25)と面積の両方から求めた値と一致します。累積和でTPR・FPRを一気に計算できるので、データが大きくても高速です。
次に、これがscikit-learnの公式実装と一致することを確認します。
from sklearn.metrics import roc_auc_score
# AUC(ペア勝率)を直接計算して確認する:全ペアの「正例 > 負例」の割合
sp, sn = s[y == 1], s[y == 0]
pair_auc = np.mean(sp[:, None] > sn[None, :])
print("ペア勝率としてのAUC =", round(pair_auc, 4)) # 0.72
print("sklearn AUC =", round(roc_auc_score(y, s), 4)) # 0.72
roc_auc_score も0.72を返し、スクラッチ実装・ペア勝率の3つがすべて一致します。これでROC/AUCの計算が「ブラックボックス」ではなくなりました。AUCはスコアの順位だけで決まるので、スコアを単調変換(たとえば確率からlog-oddsへ)してもAUCは変わらない、という性質も実装から読み取れます。これはROC/AUCの大きな利点です。確率がうまく校正されていなくても、順位さえ正しければAUCは高く出るので、「モデルが正例と負例をどれだけ正しく順序付けられるか」だけを純粋に測れます。逆に言えば、AUCが高くても確率値そのものが信用できるとは限らない、という注意も同時に得られます。確率の値が意思決定に効く場面では、別途キャリブレーション(確率校正)の確認が必要です。
実装上の細かな注意も挙げておきます。スコアに同点(タイ)が複数あると、ROC曲線はそこで縦でも横でもなく斜めに進みます。スクラッチ実装で同点を正しく扱うには、同じスコアの正例・負例をまとめて一段で処理する必要があり、sklearn の roc_curve はこれを内部で処理しています。また、正例または負例が1件も無いとTPRやFPRの分母が0になり定義できません。実データでは、こうした端のケースに気を配ると安心です。
二値の実装ができたので、最後に多クラスへの拡張を見ておきましょう。
多クラスへの拡張:One-vs-Rest とマクロ/ミクロ平均
ROC曲線はもともと二値分類の道具ですが、多クラスにも拡張できます。基本はOne-vs-Rest(OvR)で、「クラス $k$ か、それ以外か」という二値問題を各クラスについて作り、それぞれROC/AUCを計算します。そのうえで全体を一つの数字にまとめるとき、2通りの平均があります。
- マクロ平均 — 各クラスのAUCを単純平均する。クラスごとを平等に扱う(少数クラスも同じ重み)。
- ミクロ平均 — 全クラスの判定を一つの大きな二値問題に連結してからAUCを計算する。サンプル数の多いクラスの影響が大きい。
3クラスのデータで実際に計算してみましょう。

右図のように、クラス0・1・2それぞれのOvR ROC曲線が描け、AUCは0.969・0.939・0.943でした。これらを単純平均したマクロ平均AUCは0.95、全判定を連結したミクロ平均AUCは0.951です。クラスごとの公平さを見たいならマクロ、全体のサンプル重みを反映したいならミクロと使い分けます。不均衡な多クラスでは、両者の差が少数クラスの性能を映す手がかりになります。
両者の違いをもう少し噛み砕くと、こうなります。マクロ平均は「各クラスのAUCをそのまま平均」するので、サンプル数の少ないクラスも多いクラスも同じ1票です。少数クラスの性能が悪ければマクロ平均が大きく下がるため、少数クラスを見落としていないかの監視に向きます。ミクロ平均は全クラスの判定を一つの巨大な二値問題にまとめてからAUCを計算するので、サンプル数の多いクラスの寄与が大きくなります。全体としてどれだけ当たっているかを反映したいときに向きます。両者がほぼ等しいなら各クラスの性能が揃っているサイン、マクロがミクロより目立って低いなら、どこかのクラス(多くは少数クラス)でつまずいているサインです。scikit-learnでは roc_auc_score(y, P, multi_class="ovr", average="macro") のように引数を切り替えるだけで両方を計算できます。なお、OvR以外に「クラスのペアごとに比較するOne-vs-One(OvO)」という方式もあり、クラス間の混同の構造をより細かく見たいときに使われます。
まとめ
本記事では、ROC曲線とAUCについて、発想から多クラス拡張まで解説しました。
- ROC曲線はしきい値を動かしたときの (FPR, TPR) の軌跡で、必ず左下から右上へ向かう
- TPRは陽性の行、FPRは陰性の行から計算するため、クラス比率に直接は依存しない
- AUC=ランダムな正例が負例より高スコアになる確率であり、順位付けの良さを測る(証明とペア勝率で確認)
- AUC=0.5はランダム同等、1.0は完全分離
- 不均衡データではROCが楽観的になるため、PR曲線・APを併用する
- 最適しきい値はYouden指数やコストで選び、目的によって変わる
- スクラッチ実装はsklearnと一致し、多クラスはOvR+マクロ/ミクロ平均で拡張する
評価指標の理解は、モデル選択やチューニングの土台です。次のステップとして、以下の記事も参考にしてください。