nikkie-ftnextの日記

イベントレポートや読書メモを発信

scikit-learnのconfusion_matrixが返す混同行列を、読めるようになりたい!(2値分類篇)

はじめに

はぁ... 安西こころさん... 尊い...、nikkieです😇

scikit-learnにまつわる小ネタです。
2値分類における混同行列を読めるようになりたい!

目次

2値分類における混同行列

2値分類なのでラベルは0か1です。
正解の0/1の並び(y_true)と、モデルが推論した0/1の並び(y_pred)があったときに、2×2の行列でサンプルをセグメント分けして数を出します。

いろんな書籍で取り上げられている話題だと思いますが、今回は『評価指標入門13章 3.3の図3.2 (p.114) を参考にしました。

  • ラベルが0=負例=N(egative)
  • ラベルが1=正例=P(ositive)
推論ラベル=P 推論ラベル=N
正解ラベル=P TP FN
正解ラベル=N FP TN

TP, FN, FP, TNの命名規則は以下です。

  • 2文字目(PまたはN)は推論ラベル
  • 推論ラベルが正解ラベルと一致しているかどうかで1文字目が決まる

意味としては

  • TP: 推論ラベルがP正解ラベルと一致y_truey_predも1)
  • FP: 推論ラベルがP正解ラベルと一致しないy_trueは0、y_predは1)
  • FN: 推論ラベルがN正解ラベルと一致しないy_trueは1、y_predは0)
  • TN: 推論ラベルがN正解ラベルと一致y_truey_predも0)

となります。

scikit-learnのconfusion_matrixは書籍でよく見る説明から、行どうし・列どうしで入れ替わってる〜〜!!🤯

書籍で多く見る説明は完全に理解しているのですが、scikit-learnの返す混同行列とはギャップがあるよな〜と、脳内での変換に難しさを感じていました。
なので、この機に徹底的に理解しちゃいます!

返り値(Returns)に注目すると、以下のようにあります。

Confusion matrix whose i-th row and j-th column entry indicates the number of samples with true label being i-th class and predicted label being j-th class.

confusion_matrixが返すndarrayについて
i=0,1, j=0,1 としたときの [i, j]要素は

  • i行 (i-th row): 正解ラベルがiのサンプル数 (true label being i-th class)
  • j列 (j-th column): 推論ラベルがjのサンプル数 (predicted label being j-th class)

よって、表で表すと

推論ラベル=N 推論ラベル=P
正解ラベル=N TN FP
正解ラベル=P FN TP

となります。

これはドキュメント中のサンプルコード(以下に引用)とも一致しますね。

>>> tn, fp, fn, tp = confusion_matrix([0, 1, 0, 1], [1, 1, 1, 0]).ravel()
>>> (tn, fp, fn, tp)
(0, 2, 1, 1)

サンプルコード内訳2

>>> confusion_matrix([0, 1, 0, 1], [1, 1, 1, 0])
array([[0, 2],
       [1, 1]])
>>> # tn=0, fp=2, fn=1, tp=1

この記事のコードの動作環境

  • Python 3.10.9
  • scikit-learn 1.2.2

confusion_matrixの返り値からprecisionやrecallを求めるコード

precision

📌定義はtp / (tp + fp)
https://scikit-learn.org/stable/modules/generated/sklearn.metrics.precision_score.html

confusion_matrixが返す混同行列では

  • tp[1][1](1列目が推論ラベル=P。1行目は正解もP)
  • fp[0][1](0行目は正解がNなのでPという推論は誤っている)

ですから、上のサンプルコードを使うと

>>> cm = confusion_matrix([0, 1, 0, 1], [1, 1, 1, 0])
>>> cm[1][1] / (cm[1][1] + cm[0][1])  # tp / (tp + fp)
0.3333333333333333

>>> from sklearn.metrics import precision_score
>>> precision_score([0, 1, 0, 1], [1, 1, 1, 0])  # 検算
0.3333333333333333

recall

📌定義はtp / (tp + fn)
https://scikit-learn.org/stable/modules/generated/sklearn.metrics.recall_score.html

confusion_matrixが返す混同行列では

  • tp[1][1](1行目は正解ラベルP。1列目が推論ラベル=P)
  • fn[1][0](0列目は推論ラベル=Nなので、Nという推論は誤っている)

ですから

>>> cm[1][1] / (cm[1][1] + cm[1][0])  # tp / (tp + fn)
0.5 

>>> from sklearn.metrics import recall_score
>>> recall_score([0, 1, 0, 1], [1, 1, 1, 0])  # 検算
0.5

終わりに

scikit-learnが返すconfusion_matrixも完全に理解した!
この返り値は2値分類では

  • 行は正解ラベル0,1
  • 列は推論ラベル0,1

となっています。つまり

array([[TN, FP],
       [FN, TP]])

書籍などで見かけることが多いのは

  • 行は正解ラベル1,0
  • 列は推論ラベル1,0

で、scikit-learnでは行と列がそれぞれの中で入れ替わっているので、読み替えに苦労していました。

TP, FN, FP, TNの命名規則

  • 推論ラベルで2文字目のPかNかが決まる
  • 1文字目(T/F)は推論ラベルと正解ラベルが一致しているかどうか

今後はこのエントリをショートカットに使います!


  1. よい本だと思います!
  2. サンプルコードでは、numpyのarrayのravelメソッドでshape (2, 2)を(4,)と変換しています。ドキュメントによるとravelreshape(-1, order=order)と同じだそうです。