一つの入力に対して、複数のラベルの予測値を返す分類問題(多ラベル分類, multi label classificationと呼ばれる)の評価指標について算出方法とともにまとめる。
例として、画像に対して、4つのラベルづけを行う分類器の評価指標の話を考えてみる。
モデルができたものとして、このモデルの評価を行いたい。
以下の表のような予測と真値であった。
画像ID | 予測 | 真値 |
---|---|---|
1 | 1 | 1 |
2 | 1,2,3 | 1,2 |
3 | 1,2,3,4 | 1,2,3,4 |
(注)「予測」と「真値」の列の番号はラベルの番号とし、画像ID1では、ラベル1に属すると予測し、真値もラベル1であったことを示す。
この結果のmacro-F1, micro-F1を計算する。
この後、計算方法とともにpythonでのコードの書き方も紹介する。pythonでは、ラベルを列とし、属する場合を1、そうでない場合を0とすると扱いやすい。
from sklearn.metrics import recall_score, precision_score, f1_score # 読み込んでおく
import numpy as np
y_true = np.array([[1,0,0,0], [1,1,0,0], [1,1,1,1]])
y_pred = np.array([[1,0,0,0], [1,1,1,0], [1,1,1,1]])
macro-F1
macro-F1は各クラスごとに、F値を計算し、それを平均したものである。
愚直に計算
ラベル1についてみてみると、画像IDの1~3で正解しているので、F=1。
ラベル2, 4についても同様。
ラベル3については混同行列は以下のようになる。
真値 P | 真値 N | |
---|---|---|
予測P | 1 | 1 |
予測N | 0 | 1 |
Precision = 1/2, Recall = 1であり、
これより、F = 2 * Precision * Recall / (Precision + Recall) = 2/3
よってラベル1~4のF値の平均は、(1+1+1+2/3) / 4 = 11/12 = 0.916666...となる。
pythonで計算
sklearnにあるf1_score
関数を利用することで計算できる。
average
オプションでmacro
と指定すれば良い。
average = "macro"
# Recallの平均
print(recall_score(y_true=y_true, y_pred=y_pred, average=average))
# -> 1.0
# Recallの平均
print(precision_score(y_true=y_true, y_pred=y_pred, average=average))
# -> 0.875
# macro-F1
print(f1_score(y_true=y_true, y_pred=y_pred, average=average))
# -> 0.9166666666666666
この指標の特徴
ラベル別に、二値分類して、それらのF値を平均したものであるため、個々のラベルについての独立に最適化可能。一方、ラベルを独立に扱うため、ラベルの不均衡は考慮しない。
micro-F1
micro-F1はレコードとクラスのペアの各々でTP, FN, FP, TNを集計し、その混同行列のF値を計算するものである。
愚直に計算
再掲。予測と真値は以下のようになっている。
画像ID | 予測 | 真値 |
---|---|---|
1 | 1 | 1 |
2 | 1,2,3 | 1,2 |
3 | 1,2,3,4 | 1,2,3,4 |
TPについては、予測でも真値でも登場するラベルをカウントする。この場合
画像ID1の1, 画像ID2の1,2、画像ID3の1,2,3,4の7つが該当する。
よってTP=7。
FPについては、「誤ってPと予測したもの」の数を数える。
これは、予測したものの真値では登場しなかったものの数を数えればよく、画像ID2の3のみ。
よってFP=1。
FNについては、「誤ってNと予測したもの」の数を数える。
これは、予測できなかったが、真値では登場したものの数を数えればよく、存在しない
よってFN=0。
TNについては、負例と予測され真値でも負例のもの、つまり予測でも真値でも存在しなかったものの数をカウントすればよい。
画像ID1の2,3,4, 画像ID2の4が該当する。
よってTN = 4。
混同行列を書くと以下
真値 P | 真値 N | |
---|---|---|
予測P | 7 | 0 |
予測N | 1 | 4 |
Precision = 1, Recall = 7/8より
F値 = 2 * Precision * Recall / (Precision + Recall) = 0.93333
pythonで計算
macro-F1と同様、sklearnにあるf1_score
関数を利用することで計算できる。
average
オプションでmicro
と指定すれば良い。
average = "micro"
# Recallの平均
print(recall_score(y_true=y_true, y_pred=y_pred, average=average))
# -> 1.0
# Recallの平均
print(precision_score(y_true=y_true, y_pred=y_pred, average=average))
# -> 0.875
# macro-F1
print(f1_score(y_true=y_true, y_pred=y_pred, average=average))
# -> 0.9333333333333333
この指標の特徴
データセット全体に対して評価値計算を行うので、ラベルの不均衡も考慮される(らしい)
同時に計算
sklearnには、結果を見れる claasification_report
という関数がある。
sklearn.metrics.classification_report
from sklearn.metrics import classification_report
print(classification_report(y_true, y_pred))
結果
precision recall f1-score support
0 1.00 1.00 1.00 3
1 1.00 1.00 1.00 2
2 0.50 1.00 0.67 1
3 1.00 1.00 1.00 1
micro avg 0.88 1.00 0.93 7
macro avg 0.88 1.00 0.92 7
weighted avg 0.93 1.00 0.95 7
samples avg 0.89 1.00 0.93 7
digit
オプションを指定することで、任意の桁数の小数点を出力できる
print(classification_report(y_true, y_pred, digits= 6)) # 小数点第6位まで出力
結果
precision recall f1-score support
0 1.000000 1.000000 1.000000 3
1 1.000000 1.000000 1.000000 2
2 0.500000 1.000000 0.666667 1
3 1.000000 1.000000 1.000000 1
micro avg 0.875000 1.000000 0.933333 7
macro avg 0.875000 1.000000 0.916667 7
weighted avg 0.928571 1.000000 0.952381 7
samples avg 0.888889 1.000000 0.933333 7