ロジスティック回帰の代表的なハイパーパラメータ

## ロジスティック回帰とは

ロジスティック回帰とは、線形分離可能なデータの境界線を学習によって見つけて、
データの分類を行う、代表的な教師あり学習の１つです。
呼び名に「回帰」と付いていますが、あくまで「分類」の手法です。

https://aws.amazon.com/jp/what-is/logistic-regression/

「境界線が直線になること」がロジスティック回帰の大きな特徴です。
そのため、二項分類などクラスの少ないデータに使われ、
また降水確率など、データがクラスに分類される確率を知りたいときにも使われます。

欠点としては、
- データが線形分離可能でないと分類できない
- ⾼次元の疎なデータ(0が多いデータ)には適さない
- 訓練データから学習した境界線がデータの近くを通るため、汎化能力(未知のデータに対する予測能力)が低い
などが挙げられます。


このロジスティック回帰には、モデルを操作する人間が調整しなければいけないハイパラメータがあります。
いくら機械学習といえども、すべての学習過程を自動化することは難しいのですね。

ロジスティック回帰のハイパーパラメータの一覧は以下です。たくさんありますね。


https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html


今回は数あるハイパーパラメータの中から、代表的なハイパーパラメータ「C」「panalty」「multi_class」「random_staet」の４つを解説していきます。

## ハイパーパラメータ C
Cは「教師データの分類の間違いに対して、モデルが学習する識別境界線をどのくらい厳しくするのか」という指標になります。


Cの値は大きければ大きいほど、
モデルは教師データを完全に分類できるような識別線を学習するようになります。 
しかし教師データに対しては過学習になり、
訓練データ以外のデータに予測を行うと、正解率が下がる場合が多くなります。

逆にCの値を小さくすると、
教師データの分類の誤りと外れ値データに寛容になり、より一般化された境界線を得やすくなります。 ただし外れ値の少ないデータでは、
境界線がうまく識別できていないモデルになる場合もあります。
極端に小さくても、うまく境界線が識別できません。

scikit-learnのロジスティック回帰モデルにおいて、Cの初期値は1.0です。
以下でCの値が変化することによって、
どのくらいモデルの正解率が変わるかをグラフで確認してみます。



```ruby:ハイパーパラメータCの確認.py
#ライブラリのインポート
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
from sklearn import preprocessing
from sklearn.model_selection import train_test_split

# 架空データの生成　
X, y = make_classification(
    n_samples=1250, n_features=4, n_informative=2, n_redundant=2, random_state=42)
train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42)
#n_redundantは分類に不要な特徴量の個数
# random_stateは固定（詳しくは下記参照）


# Cの値の範囲を設定(今回は1e-5,1e-4,1e-3,0.01,0.1,1,10,100,1000,10000)
C_list = [10 ** i for i in range(-5, 5)]

# グラフ描画用の空リストを用意
train_accuracy = []
test_accuracy = []

# C_listに納められているCの値を取り出し、順番にモデルに学習
for C in C_list:
    model = LogisticRegression(C=C, random_state=42)
    model.fit(train_X, train_y)
    train_accuracy.append(model.score(train_X, train_y))
    test_accuracy.append(model.score(test_X, test_y))
    
# グラフの描画
# semilogx()はxのスケールを10のx乗のスケールに変更できる
plt.semilogx(C_list, train_accuracy, label="accuracy of train_data")
plt.semilogx(C_list, test_accuracy, label="accuracy of test_data")
plt.title("accuracy by changing C")
plt.xlabel("C")
plt.ylabel("accuracy")
plt.legend()
plt.show()

>>>出力結果
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/8a623383-9e86-473c-8646-2fab8b88f26b.png)


C=0.001のとき、訓練データとテストデータどちらもバランスよく高い正解率が出ています。
一方、C>0.02では、過学習が起きてしまい、訓練データでは高い正解率が出ていますが、
テストデータの正答率は下がってしまっています。

ちなみにCは、線形サポートベクターマシーン（SVM）でも分類の誤りの許容度を示すハイパーパラメータとして定義されています。

## ハイパーパラメータpenalty

先ほどのCが分類の誤りの許容度だったのに対し、
penaltyは「モデルの複雑さに対するペナルティ」を表します。

penaltyに入力できる値は「L1」「L2」「elasticnet」「none」の4つがあります。
基本的にはデフォルトのまま「L2」を選びますが、「L1」を選んだほうが欲しいデータが得られる場合もあります。

- L1(ラッソ回帰）...データの"特徴量の削減"により、識別境界線の一般化を図るペナルティ
- L2（リッジ回帰）...データ全体の"重みの減少"により、識別境界線の一般化を図るペナルティ
- elasticnet...L1,L2の両方を使用するペナルティ
- none...正則化を行わない

以下、penaltyの違いによる結果の違いを見てみます。
```ruby:penalty=L1の場合.py
# パッケージをインポート
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification

# データirisを取得
# irisは、特徴量5データ数150のアヤメという花のサンプルデータのテーブル
iris = datasets.load_iris()
X = iris.data[:, [0, 2]]
y = iris.target
# trainデータ、testデータの分割
train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.3, random_state=42)

# ロジスティック回帰モデルの構築(penaltyにL1を指定)
model = LogisticRegression(penalty="l1",solver="liblinear")

# モデルの学習
model.fit(train_X, train_y)

# test_Xに対するモデルの分類予測結果
y_pred = model.predict(test_X)
print(y_pred)

# 以下で可視化
plt.scatter(X[:, 0], X[:, 1], c=y, marker=".",
            cmap=matplotlib.cm.get_cmap(name="cool"), alpha=1.0)

x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.02),
                       np.arange(x2_min, x2_max, 0.02))
Z = model.predict(np.array([xx1.ravel(), xx2.ravel()]).T).reshape(xx1.shape)
plt.contourf(xx1, xx2, Z, alpha=0.4,
             cmap=matplotlib.cm.get_cmap(name="Wistia"))
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
plt.title("classification data using LogisticRegression")
plt.xlabel("Sepal length")
plt.ylabel("Petal length")
plt.grid(True)
plt.show()

>>>出力結果
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/43792d3a-e061-2112-2bbe-f03ea5673c7c.png)


- penalty="l2"の場合（penalty以外のコードは上記と同じため省略）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/13b4bf4a-91af-25d8-f9c6-6c6d66d16505.png)

- penalty="none"の場合（penalty以外のコードは上記と同じため省略）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/8cbcf503-a391-6c43-202e-b4811417e778.png)


penaltyがL1のときとnoneのときの出力結果が似ていますね。
L2正則化(リッジ回帰)は「過学習を抑えることで汎化しやすくなる」という特徴がありますので、
ちょうど良い分類になっている可能性が高いです。

逆にL1は「予測に影響を及ぼしにくいデータ」にかかる係数をゼロに近づける手法です。主に余分な情報がたくさん存在するようなデータの回帰分析を行う際に用います。
今回のirisのデータには特徴量が5つしかありませんので、そこを鑑みてもpenaltyにはL2がふさわしいように思えます。



ちなみに"penalty=l1"だけですと、
```ruby:
ValueError: Solver lbfgs supports only 'l2' or 'none' penalties, got l1 penalty.
```
というエラーメッセージが表示されました。
調べると今はデフォルトのSolverがlbfgsに変わっているそうで、
penaltyにはl2、またはnoneしかサポートしていないそうです。
というわけで、Solverにliblinearを指定しないといけないようでした。

## ハイパーパラメータ multi_class
multi_classは多クラス分類を行う際に、「モデルがどういった動作を行うか」を決めるハイパーパラメータです。
ロジスティック回帰では「ovr」「multinomial」の2つがあります。

- ovr...クラスに対して「属する/属さない」の二値分類の問題に適している
- multinomial...各クラスに分類される確率も考慮され、「属する/属さない」だけではなく「どれくらい属する可能性があるか」を扱う問題に適している




## ハイパーパラメータ random_state

モデルは学習の際に、データをランダムな順番で処理していきます。
random_stateは、その順番を制御するためのハイパーパラメータです。
ロジスティック回帰モデルの場合、
データの処理順によって大きく境界線が変わる場合があります。

しかしこのrandom_stateの値を固定することによって、同じデータの学習結果を出力できます。
データの再現性も考え、基本はrandom_stateは固定しておく方が良さそうです。

## まとめ

今回はロジスティック回帰の数あるハイパーパラメータのなかから、４つを解説してみました。
ロジスティック回帰に関わらず、機械学習モデルそれぞれにハイパーパラメータはいくつもあり、
すべてを100%理解して最適なパラメータを選択するのは非常に難しいようです。

まずは、それぞれのパラメータのデフォルトの組み合わせでモデルを作っていくことからスタートですね。
k-Fold交差検証などで汎化性能を測定し、徐々にふさわしいハイパーパラメータを選んで最も精度が高い組み合わせを探すこともできそうです。
＊ハイパーパラメーターの範囲を指定して、精度の良いハイパーパラメーターセットを計算機に見つけてもらうという方法（グリッドサーチやランダムサーチ）もありますが、次回以降記事にしたいと思います。

パラメータの選択には、Try&Errorの場面も多く出そうです。
他の方のいろんなコードを読み、どんなモデルが選択されているのか見たり、
他にどんなハイパーパラメータがあるのかと、リサーチする能力も大切にしたいと思います。