混同行列と性能評価指標

混同行列と性能評価指標

学習済みモデルが、どの程度良いか判定するする指標を性能評価指標といいます。
そこで登場するのが混同行列です。

混同行列について

余談ですが、普通に「こんどうぎょうれつ」と入力すると「近藤行列」と変換されてしまい、「混同行列」= 「近藤行列」とイメージが刷り込まれているのですが、近藤さんの行列はかなり重要ですからしっかりとマスターしましょう。

さて、混同行列とは、モデルの予測結果を、真陽性(True Positive)、真陰性(True Negative)、偽陽性(False Positive)、偽陰性(False Negative)のグループに分けて分類することです。

そして次のような表にすることができます。

予測されたクラス
P N
実際のクラス P 真陽性TP 偽陰性FN
N 偽陽性FP 真陰性TN

混同行列の意味

なんだかよくわからない表ですが、これは真陽性(True Positive)、真陰性(True Negative)、偽陽性(False Positive)、偽陰性(False Negative)の4つの観点で予測結果をまとめた表です。
要は予測結果と実際の結果を比較してどれくらい正解していたかを見るものです。

性能評価指標

分類モデルが他のものと比べて優れているかどうかを比較する基準になる数値が性能評価指標です。

性能評価指標には次のものがあります。

  • 正解率:全てのデータで判定結果が合っていたかどうかを算出
  • 適合率:陽性であると予測したうち、実際に陽性である割合を示す
  • 再現率:本当に陽性であるケースのうち、陽性と予測できた割合を示す

*適合率は精度という時があります。
*再現率は検出率、感度、真陽性率という場合があります。

再現率を上げると適合率が下がることが確認できます。

性能評価指標の算出方法

正解率(Accuracy)=(TP+TN)/(TP+FP+TN+FN)
適合率(Precision)=TP/(TP+FP)
再現率(Recall)=TP/(TP+FN)

計算するとき混同行列の表を次のように簡略化すると計算しやすくなります。
ただし、a = TP, b = FN, c = FP, d = TN

混同行列
a b
c d

正解率 = (a + d)/(a + b + c + d)
適合率 = a / (a + c)
再現率 = a / (a + b)
F値 = 2 * (適合率*再現率)/(適合率+再現率)

簡単な例

次の内容から混同行列を作ってみると良いです。

  • メールが10件来た。
  • スパムと予測したのが8件
  • スパムでないと予測したのは2件
  • 実際にスパムだったのは7件
  • 実際にスパムでなかったのは3件

混同行列を作ったら次のようになった。

混同行列
スパム予測 スパムでないとと予測
実際にスパム 7 0
実際にスパムでない 1 2

Pythonでのコード

from sklearn.metrics import confusion_matrix
true = [0, 0, 1, 0, 0, 1, 0, 0, 0, 1] # 実際の値 0がスパム
pred = [0, 0, 1, 0, 0, 1, 0, 0, 0, 0] # 予測値 0がスパム
m = confusion_matrix(true, pred)
m

結果
array([[7, 0],
[1, 2]])

正解率

正解率は全体に対して予測が当たった割合。

正解率 = (a + d)/(a + b + c + d)

正解率 = 9 / 10 = 90%

accuracy = (m[0, 0] + m[1, 1]) / m.sum()
accuracy

結果
0.9

適合率

予測した中で実際にどれだけ予測が当たったかということ

適合率 = a / (a + c)

適合率 = 7 / (7 + 1) = 87.5%

Pythonの結果は次のようになる。

切り口を0の予測か1の予測かで結果が変わるので注意。今回はスパム(0)の適合率

precision = (m[0,0]) / m[:,0].sum()
precision

結果
0.875

再現率

再現率はスパムデータのうち、スパムと予測できた割合です。

再現率 = a / (a + b)

再現率 = 7 / (7 + 0) = 1

recal = (m[0,0])/m[0,:].sum()
recal

結果
1.0

適合率と再現率の使い分け

適合率を重視する例
子供に見せる番組の仕分け:安心な番組とそうでない番組を切り分けるモデルを訓練して、良い番組を多少排除(再現率が低い)しても安全な番組だけ仕分ける(適合率が高い)

監視ビデオから万引き犯を見つける:万引き犯を見分けるモデルを訓練して、万引き犯らしき人を確実にチェックできる。(再現率が高い)けれどもこの場合は適合率が低くなる。

適合率と再現率はトレードオフの関係にあります。

F値
適合率か、再現率かで優先順位に迷った場合に使えます。 数式的には調和平均が使われています。

F値 = 2 * (適合率*再現率)/(適合率+再現率)

F値 = 2 * (0.875 * 1) / (0.875 + 1) = 0.933

調和平均:例えば速度の平均を計算することを考えると、乗り物がある距離を時速 60 km で走りそれから同じ距離を時速 40 km で走った場合、全体の走行時間と走行距離から求められる平均速度は調和平均の値である時速 48 km であって、算術平均によって求められる時速 50 km を平均とするのは適切ではない。
2 * (60 * 40)/(60 + 40) = 48

f1 = 2 * (precision * recal) / (precision + recal)
f1

結果
0.9333333333333333

混同行列の具体例

癌のリスクを分析するモデルを考えます。機械学習により2種類の分類モデルを学習しました。
次の表は2つの分類モデルによる分類結果の混同行列です。
なお、テストに使用したデータ件数は100です。

表1
モデル1 予測されたクラス
P N
実際のクラス P 40 10
N 10 40
表2
モデル2 予測されたクラス
P N
実際のクラス P 32 2
N 18 48

ここで、「陽」は癌であることを意味します。
患者に対して癌でないと誤診するリスクを最小にしたい場合、モデル1とモデル2のどちらを選ぶべきでしょうか。

model1 a=40,b=10,c=10,d=40
model2 a=32,b=2,c=18,d=48

model1正解率 80/100 = 0.8
model2正解率 80/100 = 0.8

model1適合率 40/50 = 0.8
model2適合率 32/50 = 0.64

model1再現率 40/50 = 0.8
model2再現率 32/34 = 0.94

model1 F値 2*(0.8*0.8)/(0.8+0.8) = 0.8
model2 F値 2*(0.64*0.94)/(0.64+0.94) = 0.76

判定はモデルの案件次第で適合率を優先するか再現率を優先するかということになります。

model1は正解率、適合率、再現率それぞれ80%です。
一方model2は正解率80%、適合率64%、再現率94%になっています。

適合率とは、陽性であると予測したうち、実際に陽性である割合を示す。
再現率とは、本当に陽性であるケースのうち予測した陽性が何%かを示すものです。

今回の案件の場合は再現率より適合率の方が優先されます。
なぜなら、癌の発見を見逃すことは人命に関わりますので少しでも癌の兆候があれば陰性で合っても陽性と判定した方が良いからです。
結果、数値の変動を分析すると癌の的中率は上がって再現率が上がります。そして適合率は下がる結果となります。
その結果適合率はモデル1の方が高いのでモデル1を選ぶべきとなります。

Pythonを使った例

先ほどの例とは違いますが、次のような教師データと予測データを用意します。
教師データ = [0,0,0,1,1,1,1,1,1,1]
予測データ = [1,0,0,1,1,1,0,1,1,0]

混同行列は次のようになります。

表3
モデル1 予測されたクラス
P N
実際のクラス P 2 1
N 2 5

このデータをPythonで混同行列を作成して、適合率と再現率とF1値を求めます。
ただし、次の条件で作成します。

y_trueが教師データ
y_predが予測データ
0が陽性、1が陰性

import numpy as np
from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score,f1_score

y_true = [0,0,0,1,1,1,1,1,1,1]
y_pred = [1,0,0,1,1,1,0,1,1,0]

# 混同行列作成
print('混同行列\n{}'.format(confusion_matrix(y_true,y_pred)))

# 正解率
print('正解率: {0:.3f}'.format(accuracy_score(y_true, y_pred)))

# 適合率算出
print('適合率: {0:.3f}'.format(precision_score(y_true,y_pred)))

# 再現率算出
print('再現率: {0:.3f}'.format(recall_score(y_true,y_pred)))

# F1値算出
print('F1: {0:.3f}'.format(f1_score(y_true,y_pred)))

表示結果

—————————————————
混同行列
[[2 1]
[2 5]]

正解率: 0.700
適合率: 0.833
再現率: 0.714
F1: 0.769
—————————————————