【Keras入門(4)】Kerasの評価関数(Metrics)

入門者に向けてKerasの評価関数について解説します。
適合率(Precision)や再現率(Recall)を評価関数として追加したときに、理解に時間をかけたので記録しておきます。
TensorBoardも含めて[Google Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)を使っているのでローカルでの環境準備すらしていません。Google Colaboratoryについては[「Google Colaboratory概要と使用手順(TensorFlowもGPUも使える)」](https://qiita.com/FukuharaYohei/items/18bc4465923bdda81826)の記事を参照ください。

以下のシリーズ記事です。

- [【Keras入門(1)】単純なディープラーニングモデル定義](https://qiita.com/FukuharaYohei/items/579cc0184885b4ba75df)
- [【Keras入門(2)】訓練モデル保存(KerasモデルとSavedModel)](https://qiita.com/FukuharaYohei/items/ac6333391b8abda94bdc)
- [【Keras入門(3)】TensorBoardで見える化](https://qiita.com/FukuharaYohei/items/fe73cdb63ee82a7ea318)
- 【Keras入門(4)】Kerasの評価関数(Metrics) <- 本記事
- [【Keras入門(5)】単純なRNNモデル定義](https://qiita.com/FukuharaYohei/items/25de4a0faf634ad34efc)
- [【Keras入門(6)】単純なRNNモデル定義(最終出力のみ使用)](https://qiita.com/FukuharaYohei/items/39f865bb53cdd5052179)
- [【Keras入門(7)】単純なSeq2Seqモデル定義](https://qiita.com/FukuharaYohei/items/27cd247342a0f7006511) 

##使ったPythonライブラリ
[Google Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)でインストール済の以下のライブラリとバージョンを使っています。KerasはTensorFlowに統合されているものを使っているので、ピュアなKerasは使っていません。Pythonは3.6です。

- tensorflow: 1.13.1
- Numpy: 1.16.3

# Kerasでの評価関数(Metrics)の基本的な使い方
[compile関数](https://keras.io/ja/models/model/#compile)で評価関数(Metrics)を指定します。"acc"または"accuracy"を選ぶと、損失関数や出力テンソルの情報から自動で["categorical_accuracy"](https://github.com/keras-team/keras/blob/c2e36f369b411ad1d0a40ac096fe35f73b9dffd3/keras/metrics.py#L13)などを判断してくれるようです。

```python
model.compile(loss="binary_crossentropy", optimizer="sgd", metrics=['acc'])
```

概要は[評価関数の公式文書](https://keras.io/ja/metrics/)に書いてあります。TensorFlow1.13以降を使えば、適合率(Precision)や再現率(Recall)なども指定できます(以下のコード参照)。あまりよく調べていないのですが純正Kerasだと使えなそうな気がします。使用可能なMetricsは[API文書](https://keras.io/api/metrics/)を参照ください。
※2021/09/16 リンク先変更しました。こちらの方がわかりやすい。

```python
from tensorflow.keras.metrics import Precision, Recall
model.compile(loss="binary_crossentropy", optimizer="sgd", metrics=[Precision(), Recall()])
```

# Kerasでのカスタム評価関数(Metrics)の定義の仕方
Tensorの扱い方に慣れていないので、作り方を理解するのに時間がかかりました。[公式ガイド](https://keras.io/ja/metrics/#_5)を見ても情報が少なくてよくわからない。[metrics.py](https://github.com/keras-team/keras/blob/master/keras/metrics.py)などを見てつくりながら確認しました。

> (y_true, y_pred) を引数とし，各データ点に対してスカラを返す関数を評価関数として利用できます:
> y_true: 正解ラベル．Theano/TensorFlow テンソル
> y_pred: 予測．y_trueと同じ形状のTheano/TensorFlow テンソル

## カスタム評価関数
### カスタム関数の基本とTP定義
今回はOne-Hot-Encodingでない二値分類だとします。
その場合、関数の引数y_true(正解ラベル)は0または1、y_pred(予測値)は0から1までの連続変数です。仮にバッチサイズが4で、TP・TN・FP・FNの全パターンの場合は以下のように引数が渡されます。

||TP|TN|FP|FN|
|:-:|:-:|:-:|:-:|:-:|
|y_true|1|0|0|1|
|y_pred|0.8|0.2|0.8|0.2|


例えばTP(True Positive)のMetricsは以下のように定義します。

```python
import tensorflow.keras.backend as K

def tp(y_true, y_pred):
    return K.sum(K.round(y_true * y_pred)) * batches
```

混合行列で*"K.round(y_true * y_pred)"*の部分を表現すると下記のようになり、TP部分の1が*"K.sum"*でカウントされます(*"K.round"*は四捨五入)。

||実際は正<br>(Positive)<br>y_true=1|実際は負<br>(Negative)<br>y_true=0|
|:-:|:-:|:-:|
|予測が正<br>(Positive)<br>y_pred=0.8|**TP<br>$1 \times 0.8 \fallingdotseq 1$**|FP<br>$0 \times 0.8 \fallingdotseq 0$|
|予測が負<br>(Negative)<br>y_pred=0.2|FN<br>$1 \times 0.2 \fallingdotseq 0$|TN<br>$0 \times 0.2 \fallingdotseq 0$|

計算式の末尾の*"\* batches"*の部分は、1エポック内でバッチをまわす回数です。Kerasはバッチ間のMetricsが平均により算出されます。そのため、平均化したくない場合にはバッチ回数で乗算が必要です(二度手間)。

例えば、30件のデータに対してバッチサイズを10にして1エポックで3回バッチ実行をします。

```python
model.fit(data, labels, epochs=300, batch_size=10)
```

その場合、乗算をしないと以下のように計算され、最終的にエポック内でのMetrics出力値は20となります。

||関数出力値|
|:-:|:-:|
|バッチ1回目|10|
|バッチ2回目|20|
|バッチ3回目|30|
|出力Metrics|(10+20+30)/3 = 20|

TPのような絶対値を出力したいMetricsでは、平均化したくないので乗算をして以下のように計算をします。

||TP関数出力値|
|:-:|:-:|
|バッチ1回目|$10 \times 3 = 30$|
|バッチ2回目|$20 \times 3 = 60$|
|バッチ3回目|$30 \times 3 = 90$|
|出力Metrics|$ (30+60+90) / 3 = 60$<br>$ 10+20+30 = 60$|

### TN定義
TN(True Negative)の場合は以下の計算式で算出します。

```Python
def tn(y_true, y_pred):
    return K.sum(K.cast(K.equal(K.round(y_true + y_pred), 0), K.floatx())) * batches
```

混合行列で*"K.round(y_true ＋ y_pred)"*の部分を表現すると下記のようになり、TNは0と計算させます。

||実際は正<br>(Positive)<br>y_true=1|実際は負<br>(Negative)<br>y_true=0|
|:-:|:-:|:-:|
|予測が正<br>(Positive)<br>y_pred=0.8|TP<br>$1 + 0.8 \fallingdotseq 2$|FP<br>$0 + 0.8\fallingdotseq 1$|
|予測が負<br>(Negative)<br>y_pred=0.2|FN<br>$1 + 0.2 \fallingdotseq 1$|**TN<br>$0 + 0.2 \fallingdotseq 0$**|

そのあとに以下の流れでTNをカウントします。
1. *"K.equal"*で0をTrueに変換
2. *"K.cast"*でTrueを1に変換
3. *"K.sum"*で1を合算


### FP定義
FP(False Positive)の場合は以下の計算式で算出します。

```Python
def fp(y_true, y_pred):
    return K.sum(K.cast(K.equal(K.round(y_pred) - y_true, 1), K.floatx())) * batches
```

混合行列で*"K.round(y_pred) - y_true"*の部分を表現すると下記のようになり、FPは1と計算させます。

||実際は正<br>(Positive)<br>y_true=1|実際は負<br>(Negative)<br>y_true=0|
|:-:|:-:|:-:|
|予測が正<br>(Positive)<br>y_pred=0.8|TP<br>$1 - 1 = 0$|**FP<br>$1 - 0 = 1$**|
|予測が負<br>(Negative)<br>y_pred=0.2|FN<br>$0 - 1 = -1$|TN<br>$0 - 0 = 0$|

そのあとに以下の流れでFPをカウントします。
1. *"K.equal"*で1をTrueに変換
2. *"K.cast"*でTrueを1に変換
3. *"K.sum"*で1を合算

### FN定義
FN(False Negative)の場合は以下の計算式で算出します。

```Python
def fn(y_true, y_pred):
    return K.sum(K.cast(K.equal(y_true - K.round(y_pred), 1), K.floatx())) * batches
```

混合行列で*"y_true - K.round(y_pred)"*の部分を表現すると下記のようになり、FNは1と計算させます。

||実際は正<br>(Positive)<br>y_true=1|実際は負<br>(Negative)<br>y_true=0|
|:-:|:-:|:-:|
|予測が正<br>(Positive)<br>y_pred=0.8|TP<br>$1 - 1 = 0$|FP<br>$0 - 1 = -1$|
|予測が負<br>(Negative)<br>y_pred=0.2|**FN<br>$1 - 0 = 1$**|TN<br>$0 - 0 = 0$|

そのあとに以下の流れでFNをカウントします。
1. *"K.equal"*で1をTrueに変換
2. *"K.cast"*でTrueを1に変換
3. *"K.sum"*で1を合算

### 適合率(Precision)と再現率(Recall)
TP、FP、FNが算出できれば適合率(Precision)と再現率(Recall)は簡単です。TPなどの値から計算すればいいだからです。

```python
def custom_precision(y_true, y_pred):
    return tp(y_true, y_pred) / (tp(y_true, y_pred) + fp(y_true, y_pred))

def custom_recall(y_true, y_pred):
    return tp(y_true, y_pred) / (tp(y_true, y_pred) + fn(y_true, y_pred))
```

F1スコアを出したい場合には、簡単にできないので注意してください。
[Kerasで評価関数にF1スコアを使う方法](https://blog.shikoan.com/keras-f1score/)参照。

# Pythonプログラム
プログラム全体は[GitHub](https://github.com/YoheiFukuhara/keras-for-beginner/blob/master/Keras06_Metrics.ipynb)にあります。
適合率(Precision)や再現率(Recall)を追加するだけなら、TensorFlow1.13以上のバージョンでは、ここで書いてあるような面倒なことは不要です。
TensorBoardに出力すれば、以下のようにMetricsを見ることができます。
※出力方法は[記事「【Keras入門(3)】TensorBoardで見える化」](https://qiita.com/FukuharaYohei/items/fe73cdb63ee82a7ea318)参照

![04.MetricsTensorBoard.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/e4ef77a3-17f5-a252-6f53-7950f38aaf17.jpeg)