多ラベル分類の評価指標について

一つの入力に対して、複数のラベルの予測値を返す分類問題（多ラベル分類, 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とすると扱いやすい。

```py
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~３で正解しているので、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`関数を利用することで計算できる。


[sklearn.metrics.f1_score](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.f1_score.html)

`average`オプションで`macro`と指定すれば良い。

```py
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`と指定すれば良い。

```py
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](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.classification_report.html)



```py
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`オプションを指定することで、任意の桁数の小数点を出力できる

```py
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
```

# 参考

- https://www.haya-programming.com/entry/2018/03/14/112454
- https://qiita.com/TaskeHAMANO/items/120cabf01b299b8f35ce