sklearnの評価指標に特異度(specificity)がなくて困った！

自分用の備忘録です。特異度っていうのはFalseの正解率ですね。
埋め込みリンクはどれもsklearnの公式ドキュメントです。
# まずはさらっと混合行列のおさらい

混合行列機械学習モデルの性能を評価する指標の一つです。
主に二値分類問題におけるモデルを評価する際に使われることが多く、テストデータに対する予測結果を、真陽性、真陰性、偽陽性、偽陰性の4つのマトリクスで評価したものです。

真陽性（TP）: **真に陽性**のテストデータに対してモデルの**予測結果も陽性**と判別したケース
真陰性（TN）: **真に陰性**のテストデータに対してモデルの**予測結果も陰性**と判別したケース
偽陽性（FP）: **真に陰性**のテストデータに対してモデルの**予測結果は陽性**と判別したケース
偽陰性（FN）: **真に陽性**のテストデータに対してモデルの**予測結果も陰性**と判別したケース

となります。

### 可視化してみる

混合行列を作成するにはsklearnのconfusion_matrixを使います。
以下で実際に正解クラスラベルy_trueと、モデルの出力である予測クラスy_predを事前に与え、混合行列を可視化してみます。



```python:
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 1, 1, 1, 1, 0, 0, 0, 1, 1]
cm = confusion_matrix(y_true, y_pred)
plt.figure(figsize = (10,7))
sns.heatmap(cm, annot=True)
plt.savefig('sklearn_confusion_matrix.png')
plt.show()
```

例えば真陽性（TP）は、y_tureは1でy_predも1のものを指すので、下の表からも2個あるとわかります。
偽陽性（FP）は、y_tureは0でy_predは1のものを指すので4個あるとわかりますね。


![2020-12-31 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503266/f344bdf7-8fd7-cd05-8920-ae76a0521ff4.png)

ちなみに、混合行列を可視化するための[plot_confusion_matrix](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.plot_confusion_matrix.html#sklearn.metrics.plot_confusion_matrix)こんな便利な関数も用意されています。

# ここから本題のsklearn の評価指標の話

さて、よく用いられる指標として、

**正解率(accuracy)**　予測値が実際にそうである割合

```math
Accuracy = \frac{TP+TN}{TP+FP+TN+FN} \\

```
<br/>

**適合率(precision)**　陽と予測したもののうち、実際に陽である割合

```math
Precision= \frac{TP}{TP+FP} \\

```
<br/>
**再現率(recall、True Positive Rate, TPR)**　実際に陽であるもののうち、陽と予測できた割合

```math
Recall= \frac{TP}{TP+FN} \\

```
<br/>
**特異度(specificity、True Negative Rate, TNR)**　実際に陰であるもののうち, 陰と予測できた割合

```math
Specificity= \frac{TN}{FP+TN} \\

```
<br/>
**偽陽性率 (False Positive Rate, FPR)**　実際に陰であるもののうち、陽と予測してしまった割合

```math
 \frac{FP}{FP+TN} \\

```
<br/>
**F1スコア（F1-score）**　再現率と適合率の調和平均

```math
 \frac{2Recall⋅Precision}{Recall+Precision} \\

```
<br/>
などがあります。
式を見れば気づきますが、特異度＝(1 - 偽陽性率)となっています。

ここで困ったことは、正解率、適合率、再現率、F1スコアなどはsklearnの評価指標として見つかるのですが特異度などは実装されていないということです。
<br/>
### 実際に機械学習をする場面を想定してみましょう。
まずは最もシンプルにsklearnの[ワインデータセット](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_wine.html)を[ロジステック回帰](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html#sklearn.linear_model.LogisticRegression.score)で分類してみます。

```python:
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score

wine = load_wine()
wine_target = wine["target"][:130]
wine_data = wine["data"][:130]

X_train, X_test, y_train, y_test = train_test_split(wine_data, wine_target, test_size=0.2, random_state=0)
lr = LogisticRegression()
lr.fit(X_train, y_train)
y_pred = lr.predict(X_test)

print('confusion matrix = \n', confusion_matrix(y_true=y_test, y_pred=y_pred))
print(f'Accuracy : {accuracy_score(y_true=y_test, y_pred=y_pred)}')
print(f'Precision : {precision_score(y_true=y_test, y_pred=y_pred)}')
print(f'Recall : {recall_score(y_true=y_test, y_pred=y_pred)}')
print(f'F1 score : {f1_score(y_true=y_test, y_pred=y_pred)}')
```

```python:
出力結果
confusion matrix = 
 [[14  1]
 [ 0 11]]
Accuracy : 0.9615384615384616
Precision : 0.9166666666666666
Recall : 1.0
F1 score : 0.9565217391304348
```

こんな感じで各評価指標を用いて、作成したモデルを評価するのでした。
混合行列は[confusion_matrix](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.confusion_matrix.html)、正解率は[accuracy_score](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.accuracy_score.html#sklearn.metrics.accuracy_score)、適合率は[precision](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.precision_score.html#sklearn.metrics.precision_score)、再現率は[recall](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.recall_score.html#sklearn.metrics.recall_score)、F1スコアは[F1-score](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.f1_score.html#sklearn.metrics.f1_score)でそれぞれ提供されています。
<br/>
### しかし特異度がない！！！
<br/>
<br/>
<br/>
**うああああああああああ！！！！！！！**
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
特異度は以下のように実装します。
引数にy_true、y_predを受け取り、返値に評価値を渡します。
（confusion_matrixの返値の行列は冒頭の画像の順番と同じです。）

```python:
def specificity_score(y_true, y_pred):
    tn, fp, fn, tp = confusion_matrix(y_true, y_pred).flatten()
    return tn / (tn + fp)

print(f'specificity :{specificity_score(y_test,y_pred)}')
```
```python:
出力結果
specificity :0.9333333333333333
```

ﾁｮｯﾄ面倒くさいね。
<br/>
### 次はちょっと進んでクロスバリデーション（CV）を使って評価してみましょう。
[cross_val_score](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html#sklearn.model_selection.cross_val_score)が使えますね。

```python:
from sklearn.model_selection import cross_val_score

cross_val_score(lr, X_train, y_train, cv=3, scoring="accuracy")
```
```python:
出力結果
array([0.94285714, 0.94285714, 0.97058824])
```

このときscoringに、使用したい[評価指標](https://scikit-learn.org/stable/modules/model_evaluation.html#scoring-parameter)を文字列で入れます。'precision' 、'recall'、'f1'なども使えます。しかし**特異度はやはり用意されていません**。がーん。
<br/>
ここで便利なのが、好きな評価指標を作成することができる、[make_scorer](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.make_scorer.html#sklearn.metrics.make_scorer)。
使い方はさっき作成したspecificity_scoreを、make_scorerでラップして入れるだけ。

```python:
from sklearn.metrics.scorer import make_scorer

cross_val_score(lr, X_train, y_train, cv=3, scoring=make_scorer(specificity_score))
```

```python:
出力結果
array([1.        , 0.93333333, 0.92857143])
```

簡単ですね！これでクロスバリデーションでも特異度（specificity）が使えるようになりました！
<br/>
さらに[cross_validate](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_validate.html)関数もあります。この関数は引数scoringに複数の評価指標を指定することができます。

```python:
score_funcs = {
    'accuracy': 'accuracy',
    'precision': 'precision',
    'recall' :'recall',
    'f1': 'f1',
    'specificity_score': make_scorer(specificity_score),
    }
    
cross_validate(lr, X_train, y_train, n_jobs=-1, cv=3, scoring=score_funcs)
```

```python:
出力結果
{'fit_time': array([0.05306554, 0.05142045, 0.05445862]),
 'score_time': array([0.01040649, 0.00802302, 0.00100088]),
 'test_accuracy': array([0.94285714, 0.94285714, 0.97058824]),
 'test_precision': array([1.        , 0.95      , 0.95238095]),
 'test_recall': array([0.9 , 0.95, 1.  ]),
 'test_f1': array([0.94736842, 0.95      , 0.97560976]),
 'test_specificity_score': array([1.        , 0.93333333, 0.92857143])}
```

辞書形式で出力されます。'test_hogehoge'というキーになるのですね。
うーん便利。
<br/>
### 終わりに
~~近日加筆予定です。~~ ROC曲線とか、AUCとか、あとはクロスバリデーションとパラメータチュー二ングとの組み合わせ方も書きたいと思っています。