多クラス分類のモデル評価

## 概要

分類分類(classification)タスクは、機械学習における主要なタスクであり、データが属するカテゴリーやクラス、種類が何なのかを判定する手法になります。例えば、画像を「猫」「犬」「その他」の3つのカテゴリーに分類するタスクが分類タスクに該当します。

分類タスクには様々な種類があります。

1. バイナリ分類: 2つのカテゴリーから1つを選ぶタスク。例えば、スパムメールか否かの分類タスクがこれに該当します。
2. 多クラス分類: 複数のカテゴリーから1つを選ぶタスク。例えば、画像を「猫」「犬」「その他」の3つのカテゴリーに分類するタスクがこれに該当します。
3. 多ラベル分類: 複数のカテゴリーから複数を選ぶタスク。例えば、画像に「猫」「犬」「鳥」「動物」などのラベルを付けるタスクがこれに該当します。

本記事では、多クラス分類のモデル評価を開発し、実験したいと思います。コードを実行し、結果を説明します。使用する環境はGoogle Colabです。

## 環境構築

ライブラリをインポートします。

```python
# 計算
import numpy as np

# データセット
from sklearn.datasets import make_multilabel_classification
from sklearn.model_selection import train_test_split

# モデル
from sklearn.multiclass import OneVsRestClassifier
from sklearn.linear_model import LogisticRegression

# モデル評価
from sklearn.metrics import plot_roc_curve, roc_curve, precision_recall_curve
from sklearn.metrics import roc_auc_score, accuracy_score, hamming_loss
from sklearn.metrics import multilabel_confusion_matrix
from sklearn.metrics import classification_report

# 可視化
import matplotlib.pyplot as plt
import warnings
warnings.simplefilter('ignore')
```

## データセット
Scikit-learnライブラリのmake_multilabel_classification関数でX, yのデータセットを生成します。

- n_samples=1000: 1000件のサンプルデータが生成されます。
- n_features=15: 15個の特徴量があります。
- n_classes=5: 5つのクラスがあります。
- n_labels=2: 各サンプルは大体2つのラベルがあります。
- allow_unlabeled=False: ラベルなしのデータは生成されません。
- random_state: 乱数のシード値です


```python
# データ生成
X, y = make_multilabel_classification(n_samples=1000, 
                                      n_features=15, 
                                      n_classes=5, 
                                      n_labels=2, 
                                      allow_unlabeled=False, 
                                      random_state=1)

label_names = ["label A", "label B","label C","label D", "label E"]
```
750件の訓練データと250件のテストデータに分割しています。ラベルは5つのバイナリデータです。

```python
# 訓練データとテストデータ

X_train, X_test, y_train, y_test = train_test_split(X,y)

# データ確認
print(f"X_train shape: {X_train.shape} y_train shape:{y_train.shape}")
print(f"X_test shape: {X_test.shape} y_test shape:{y_test.shape}")
print(f"x sample: {X_train[0]}")
print(f"y sample: {y_train[0]}")

X_train, X_test, y_train, y_test = train_test_split(X,y)
```
```:結果
X_train shape: (750, 15) y_train shape:(750, 5)
X_test shape: (250, 15) y_test shape:(250, 5)
x sample: [ 4.  4.  5. 10.  5.  2.  8. 11.  5.  1.  0.  1.  2.  9.  1.]
y sample: [1 0 0 0 0]
```

学習データのラベルは不均衡データです。label Cは非常に少ないです。

```python
# 学習データのラベルの確認

label_train = sum( sum(x) if isinstance(x, list) else x for x in y_train ) 
avg_label_train = label_train / len(y_train)
avg_y_train = sum(avg_label_train) 

print(label_train)
print(avg_label_train)
print(avg_y_train)
```
```:結果
[435 558  32 378 260]
[0.58       0.744      0.04266667 0.504      0.34666667]
2.2173333333333334
```

## モデル
OneVsRestClassifierという多クラス分類手法を使用し、LogisticRegressionという回帰分析のモデルを学習しています。

```python
# モデル作成
clf = OneVsRestClassifier(LogisticRegression(random_state=0))
clf.fit(X_train, y_train)
```

各クラスに属する確率を計算します。各クラスに属する確率が0.5（デフォルト）以上の場合、そのクラスに属すると予測します。

```python
# モデル推論
scores = clf.predict_proba(X_test)
preds = clf.predict(X_test )

print(f"scores sample: {scores[0]}")
print(f"preds sample: {preds[0]}")
```
```:結果
scores sample: [0.99933666 0.59963758 0.02907816 0.08235471 0.44753733]
preds sample: [1 1 0 0 0]
```

しきい値を指定する場合には下記のコードを利用できます。下記の例はしきい値を0.5設定しています。

```python
# しきい値で推論

threshold = 0.5
y_pred=[]
for sample in  scores:
  y_pred.append([1 if i>=threshold else 0 for i in sample ] )
y_pred = np.array(y_pred)

label_train = sum( sum(x) if isinstance(x, list) else x for x in y_pred )
avg_label_pred = label_train  / len(y_pred)
avg_y_pred = sum(avg_label_pred) 

print(label_train)
print(avg_label_pred)
print(avg_y_pred)
```
```:結果
[144 193   0 119  75]
[0.576 0.772 0.    0.476 0.3  ]
2.1239999999999997
```

## モデル評価
多ラベル分類には、以下のような評価方法がよく利用されます。
- Exact Match Ratio (EMR)
- Hamming Loss
- Precision（精度）
- Recall（再現率）
- F1スコア
- AUC

### Exact Match Ratio (EMR)
モデルが予測したラベルが完全に正解ラベルと一致している場合に1、それ以外の場合に0となり、その平均値がEMRとなります。EMRは全てのラベルが正解している場合に最大値1を取り、全てのラベルが不正解の場合に最小値0を取ります。

```python
accuracy_score(y_test, y_pred)
```
```:結果
0.42
```

### Hamming Loss

Hamming Lossは予測されたラベルと正解ラベル間の距離を表す指標で、正解ラベルと予測ラベルが異なる場合に1、同じ場合に0となり、その平均値がHamming Lossとなります。

```python
hamming_loss(y_test, y_pred)
```
```:結果
0.1568
```

Precision、Recall、F1スコアを計算ために、混同行列を作成します。

混同行列

混同行列 (confusion matrix) は、分類器が予測した結果と実際の結果を比較し、どのように予測が誤っているかを表すために使用される表です。混同行列は、4つの要素から構成されます。

True Positive (真陽性)：分類器が正しいクラスを正しく予測しました。
False Positive (偽陽性)：分類器が不正なクラスを正しくと予測しました。
True Negative (真陰性)：分類器が不正なクラスを不正と予測しました。
False Negative (偽陰性)：分類器が正しいクラスを不正と予測しました。

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225269/1a2d70d4-35ad-fcf6-c29d-e580de6b5819.png)

多ラベル分類は、各ラベルの混同行列を作成します。

```python
multilabel_confusion_matrix(y_test, y_pred)
```
```:結果
array([[[ 84,  13],
        [ 22, 131]],

       [[ 38,  34],
        [ 19, 159]],

       [[241,   0],
        [  9,   0]],

       [[ 97,  28],
        [ 34,  91]],

       [[154,  16],
        [ 21,  59]]])
```

混同行列のデータから、分類レポート（classification report）を計算します。各クラスに対する精度、再現率、F1スコアなどをまとめたものです。

### Precision（精度）

Precisionは予測した正解ラベルが実際の正解ラベルである割合を表します。Precisionは高い値を示すほど、予測した正解ラベルが実際の正解ラベルである確率が高いことを示します。

### Recall（再現率）

Recallは実際の正解ラベルが予測した正解ラベルである割合を表します。Recallは高い値を示すほど、実際の正解ラベルが予測した正解ラベルである確率が高いことを示します。

### F1スコア

F1はPrecisionとRecallの調和平均値を表します。F1は高い値を示すほど、PrecisionとRecallのバランスが良いことを示します。

多ラベル分類の評価では、ラベルごとに精度や再現率などの指標を計算し、それらを結合して最終的な評価値を出すことができます。これには、micro、macro、weighted、samples avgの4つの平均計算方法があります。

|              | 説明                               | 長所                                             | 短所                                         |
|:------------:|------------------------------------|--------------------------------------------------|----------------------------------------------|
|   micro avg  | 全ラベルに対する平均値             | 全体のパフォーマンスを確認できる                 | 少数クラスに対するパフォーマンスを無視しがち |
|   macro avg  | 各ラベルに対する平均値             | 少数クラスに対するパフォーマンスを確認できる     | 全体のパフォーマンスを確認できない           |
| weighted avg | 各ラベルのサンプル数に応じた平均値 | 重要なクラスのパフォーマンスを重視できる         | 重要でないクラスのパフォーマンスを無視しがち |
|  samples avg | 各サンプルに対する平均値           | 個々のサンプルに対するパフォーマンスを確認できる | 全体のパフォーマンスを確認できない           |

```python
print(classification_report(y_test, y_pred,target_names=label_names))
```
```:結果
              precision    recall  f1-score   support

     label A       0.91      0.86      0.88       153
     label B       0.82      0.89      0.86       178
     label C       0.00      0.00      0.00         9
     label D       0.76      0.73      0.75       125
     label E       0.79      0.74      0.76        80

   micro avg       0.83      0.81      0.82       545
   macro avg       0.66      0.64      0.65       545
weighted avg       0.82      0.81      0.81       545
 samples avg       0.85      0.88      0.83       545
```
混同行列のデータから、分類レポート（classification report）を計算します。各クラスに対する精度、再現率、F1スコアなどをまとめたものです。

### AUC

AUC（Area Under the Receiver Operating Characteristic Curve）は、ROC曲線（受信者操作特性曲線）の下の面積を表します。ROC曲線は、特定の予測モデルを評価するために使用される2次元グラフで、真陽性率（True Positive Rate、TPR）と偽陽性率（False Positive Rate、FPR）を示しています。値が1に近ければ、予測が完璧であり、値が0に近ければ、予測が完全に悪いことを示します。

```python
# ROC曲線

labelPlots ={}
for i in range (len(label_names)):
  clf = LogisticRegression(random_state=0)
  clf.fit(X_train, y_train[:,i])
  ax = plt.gca()
  labelPlots[i]= plot_roc_curve(clf, X_test, y_test[:,i], name= (label_names[i]), ax=ax, alpha=0.8) 

plt.title("ROC curve")

plt.show()
```
![download (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225269/7f968645-cff3-95a4-7c7d-3c43e90daa3f.png)

AUCを結合して最終的な評価値を出すことができます。これには、micro、macro、weighted、samples avgの4つの平均計算方法があります。

```python
print("roc_auc_score for different averaging methods:")
print("\tmacro:{:.2} ".format(roc_auc_score(y_test, scores)))
print("\tmicro: {:.2} ".format(roc_auc_score(y_test, scores, average='micro')))
print("\tweighted: {:.2} ".format(roc_auc_score(y_test, scores, average='weighted')))
print("\tNone: {} ".format(roc_auc_score(y_test, scores, average=None)))
```

```:結果
roc_auc_score for different averaging methods:
	macro:0.88 
	micro: 0.92 
	weighted: 0.88 
	None: [0.92372482 0.83528402 0.86906408 0.861824   0.89360294] 
```

```python
roc_auc_score(y_test, scores, average=None).mean()
```
```:結果
0.8766999724740515
```

## まとめ

多ラベル分類は他の分類と比較して異なるため、特別な評価方法が必要です。本記事では、様々な評価手法を実験し、その結果、長所や短所をまとめました。

|       評価手法      |                                    説明                                    |                                                     長所                                                     |                                              短所                                              |
|:-------------------:|:--------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:|
| Exact   Match Ratio | 全てのラベルが正解と予測が同じである割合を表します。                       | ラベル全てが正しく予測された場合のみ1となるため、全体的な精度が高い                                          | ラベルの一部のみが正しく予測された場合は0となるため、部分的な正解が見逃される                  |
| Hamming   Loss      | ラベルの予測と正解の不一致数を表します。                                   | ラベルの全ての予測に対して評価を行うため、部分的な正解も考慮できる                                           | 各ラベルの予測に対して等しい重みを与えるため、重要度の異なるラベルに対して適切な評価ができない |
| Recall              | 正解に含まれるラベルを予測した割合を表します。                             | 正解のラベルを持つデータが全て予測されるようなモデルを評価するため、漏れのない予測を行っているかを評価できる | 過学習したモデルに対して高い値が出やすい                                                       |
| Precision           | 予測に含まれるラベルが正解に含まれる割合を表します。                       | 予測されたラベルが正解のラベルである割合を評価するため、予測が精度の高いモデルを評価できる                   | 予測を少なくすることでも高い値が出やすい                                                       |
| F1                  | RecallとPrecisionの調和平均を表します。                                    | RecallとPrecisionのバランスを考慮した評価を行うため、精度と再現率のバランスの良いモデルを評価できる          | 単独では全体的なモデルの評価には向かない                                                       |
| AUC                 | ROC曲線（True Positive RateとFalse   Positive Rate）の下の面積を表します。 | 多クラス分類の場合にも適用可能であり、正解ラベルが不均等なデータセットにも適用可能                           | 閾値の変更によって結果が変わることがある                                                       |