ロジスティック回帰② scikit-learn による実装

[前回記事](https://qiita.com/892noske/items/9166b488187258d08559)

# 前回まで
ロジスティック回帰のモデルの定式化、コスト関数など理論的な枠組みを学習しました。
今回は実際にコードを書いてロジスティック回帰を実行していきます。

# データ準備
線形回帰の時と同様に、ペンギンデータセットを使用していきます。

```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report

# データの読み込み
penguins = sns.load_dataset('penguins')

# ヒゲペンギンとジェンツーペンギンのデータのみを抽出
penguins = penguins[penguins['species'].isin(['Chinstrap', 'Gentoo'])]

# 必要な列の欠損値を除外
penguins = penguins.dropna(subset=['bill_length_mm', 'bill_depth_mm', 'species'])

# 説明変数と目的変数の設定
X = penguins[['bill_length_mm', 'bill_depth_mm']]
y = penguins['species']

# 目的変数を数値に変換
y = y.map({'Chinstrap': 0, 'Gentoo': 1})
```

* データをロードし、対象となる項目、種類を選択する部分は前回同様です。
* 分類問題であるため、教師データはペンギンの種類データを元に0または1の2値に変換したものを使います。'species'カラムには種類が英語でそのまま入っているため、ヒゲペンギン「Chinstrap」を0に、ジェンツーペンギン「Gentoo」を1に変換します。


# ロジスティック回帰
続けてscikit-learnによるロジスティック回帰を行っていきます。今回は[scikit-learnsklearn.linear_model.LogisticRegression](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html)を使います。
最初にLogisticRegression()によりインスタンスを生成します。主なパラメタは以下の通りです。
* penalty：正則化項を指定します。‘l1’（L1正則化）, ‘l2’（L2正則化）, ‘elasticnet’（エラスティックネット）, None（正則化なし）が選択可能で、デフォルトは‘l2’です。
* solver：モデルを解くアルゴリズムを指定します。デフォルトは'lbfgs'（L-BFGS法）です。

```python
# ロジスティック回帰モデルの作成と学習
model = LogisticRegression()
model.fit(X, y)

# 訓練データで予測
y_pred = model.predict(X)

```

インスタンス生成後、fitメソッドにより学習を行います。学習後、predictメソッドにより各入力データXがどのクラスに属するかの予測値を出力します。前回の説明にもありましたが、ロジスティック回帰ではモデルからの予測値は0から1の連続値を取ります。そのため、各データがどのクラスに属するかは予測値が0.5を超えているかどうかで判定します。scikit-learnsklearn.linear_model.LogisticRegressionでは
* 'predict'メソッド：どのクラスに属するかの値（0か1の値）
* 'predict_proba'メソッド：予測値そのもの（0から1の連続値）

を出力するので必要に応じて使い分けます。

次にモデルの評価を行います。

```python
# モデルの評価
accuracy = accuracy_score(y, y_pred)
print(f'正解率: {accuracy:.2f}')
```
```python:結果
Accuracy: 0.97
```

モデルの予測結果と実際の教師データから、sklearn.metrics.accuracy_scoreメソッドを用いて正解率を算出します。正解率accuracyは
* 真陽性（True Positive, TP）：正解と予測したデータの内、実際に正解であるデータ
* 真陰性（True Negative; TN）：不正解と予測したデータの内、実際に不正解であるデータ
と全サンプル数Nを用いて
```math
\begin{align}
\rm{accuracy} =\frac{TP+TN}{N}
\end{align}
```
で算出されます。今回の様な2クラス分類の場合、どのクラスを正解とみなすかは2パターンありうるのですが、どちらを正解とみなしても正解率の計算結果は変わらない点が重要です。便宜的に正解ラベル1はジェンツーペンギン、不正解ラベル0はヒゲペンギンに対応するものと考えると、TPはジェンツーペンギンを正しく予測できたデータ数、TNはヒゲペンギンを正しく予測できたデータ数になります。正解率0.97であったため全サンプル中97%のデータは正しく分類できたことになります。

次に詳細なモデルの評価のためにsklearn.metrics.confusion_matrixを用いて混同行列（Confusion Matrix）を表示します。混同行列はTP、TNに加え、
* 偽陽性（False Positive, FP）：正解と予測したデータの内、実際は不正解であるデータ
* 偽陰性（False Negative; FN）：不正解と予測したデータの内、実際は正解であるデータ
を用いて

|  | 不正解と予測 | 正解と予測 |
|:-:|:-:|:-:|
| 実際は不正解 | TN | FP |
| 実際は正解 | FN | TP |

の形で表現されます。

今回の場合、混同行列は下記の事を示しています。
* ヒゲペンギン68体の内、実際にヒゲペンギンと予測できたのは64体、誤ってジェンツーペンギンと予測したのが4体
* ジェンツーペンギン123体の内、実際にジェンツーペンギンと予測できたのは121体、誤ってヒゲペンギンと予測したのが2体

また同じ意味ですが次の様にも表現できます。
* ヒゲペンギンと予測した66体の内、実際にヒゲペンギンだったのが64体、実際はジェンツーペンギンだったのが2体
* ジェンツーペンギンと予測した125体の内、実際にジェンツーペンギンだったのが121体、実際はヒゲペンギンだったのが4体

次にsklearn.metrics.classification_reportを用いて分類レポート（Classification Report）を表示します。

```python
# 混同行列と分類レポートの表示
print('混同行列:')
print(confusion_matrix(y, y_pred))
print('分類レポート:')
print(classification_report(y, y_pred))
```
```python:結果
Confusion Matrix:
[[ 64   4]
 [  2 121]]
Classification Report:
              precision    recall  f1-score   support

           0       0.97      0.94      0.96        68
           1       0.97      0.98      0.98       123

    accuracy                           0.97       191
   macro avg       0.97      0.96      0.97       191
weighted avg       0.97      0.97      0.97       191
```

分類レポートでは次の指標を表示します。
* 適合率（precision）：正解と予測したデータの内、実際に正解のデータの割合
```math
\begin{align}
\rm{precision} =\frac{TP}{TP+FP}
\end{align}
```
* 再現率（recall）：実際の正解データの内、正解と予測できたデータの割合
```math
\begin{align}
\rm{recall} =\frac{TP}{TP+FN}
\end{align}
```
* F1値（f1-score）：適合率と再現率の調和平均
```math
\begin{align}
\rm{f1-score} =\frac{1}{\rm{precision}^{-1}+\rm{recall}^{-1}}
\end{align}
```
* 正解率：定義は上記参照

ここで、適合率、再現率、F1値についてはクラス0（ヒゲペンギン）とクラス1（ジェンツーペンギン）についてそれぞれの値が表示されることに注意が必要です。これは正解率の計算式と異なり、適合率、再現率、F1値の計算式では正解と不正解のラベルについて対称になっていないためです。
例えば、再現率については
<dl>
  <dt>ヒゲペンギンを正解ラベルとする場合</dt>
  <dd>TP＝64</dd>
  <dd>FN＝4</dd>
  <dd>recall＝64÷(62+4)≒0.94</dd>
  <dt>ジェンツーペンギンを正解ラベルとする場合</dt>
  <dd>TP＝121</dd>
  <dd>FN＝2</dd>
  <dd>recall＝121÷(121+2)≒0.98</dd>
</dl>
となります。（sklearn.metrics.classification_reportの結果と一致）

マクロ平均（macro avg）と重み付き平均（weighted avg）はクラス毎に計算される各指標を一つの指標として扱うために計算されるものです。

* マクロ平均（macro avg）：各クラス毎に計算した指標を単純平均したもの
```math
\begin{align}
macro\ avg =\frac{1}{N}\sum_i P_i
\end{align}
```
* 重み付き平均（weighted avg）：各クラス毎に計算した指標をクラスのデータ数で重み付けし平均したもの
```math
\begin{align}
weighted\ avg =\frac{\sum_i n_iP_i}{\sum_i n_i}
\end{align}
```
ここで$P_i$は$i$番目のクラスについて計算した指標値、$n_i$は$i$番目のクラスのデータ数です。