scikit-learnのGridSearchCVでハイパーパラメータ探索

###前置き###
`scikit-learn`にはハイパーパラメータ探索用の`GridSearchCV`があって、Pythonのディクショナリでパラメータの探索リストを渡すと全部試してスコアを返してくれる便利なヤツだ。

今回はDeepLearningではないけど、使い方が分からないという声を聞くので、この`GridSearchCV`の使い方をまとめておこうと思います。
ちなみに`Keras`だと`scikit-learn`のAPIもあるので、`Keras`で作ったモデルをこの`GridSearchCV`でパラメータ探索できる。
（やったことないけど・・・）

####GridSearchCV####
>[sklearn.model_selection.GridSearchCV](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html)

###ソースコード全文###
今回は最初にコード全文を載せておく。説明は後程。
データはirisデータセットでサポートベクターマシンを使って分類する。

```python
### ライブラリのインポート
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.metrics import confusion_matrix

### 探索するパラメータ空間
def param():
  ret = {
      'C':[1, 10, 100],
      'kernel':['rbf', 'linear', 'poly'],
      'degree':np.arange(1, 6, 1),
      'gamma':np.linspace(0.01, 1.0, 50)
  }
  return ret

if __name__ == '__main__':
  # データの準備
  x = load_iris().data
  y = load_iris().target

  # 25%分のデータを除けておく
  x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.25)

  # GridSearchCVのインスタンスを作成&学習&スコア記録
  gscv = GridSearchCV(SVC(), param(), cv=4, verbose=2)
  gscv.fit(x_train, y_train)

  # スコアの一覧を取得
  gs_result = pd.DataFrame.from_dict(gscv.cv_results_)
  gs_result.to_csv('gs_result.csv')

  # 最高性能のモデルを取得し、テストデータを分類
  best = gscv.best_estimator_
  pred = best.predict(x_test)

  # 混同行列を出力
  print(confusion_matrix(y_test, pred))
```

####解説####
#####ライブラリインポート#####
```python
### ライブラリのインポート
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.metrics import confusion_matrix
```
探索パラメータが数値の時にndarrayでやると楽なのでnumpy、スコア一覧をDataFrameにしてしまえばCSVなどで吐き出すのが楽なのでpandas。
irisデータセットと分類用のサポートベクターマシン(`SVC`)、データをランダムに分割する`train_test_split`、最後の評価用の混同行列を出力する`confusion_matrix`をインポートします。

#####探索するパラメータ空間#####
```python
### 探索するパラメータ空間
def param():
  ret = {
      'C':[1, 10, 100],
      'kernel':['rbf', 'linear', 'poly'],
      'degree':np.arange(1, 6, 1),
      'gamma':np.linspace(0.01, 1.0, 50)
  }
  return ret
```
メインのソースが綺麗になるので、パラメータ空間は関数で作る。
ディクショナリで渡す必要がある。
キーがサポートベクターマシンのパラメータ名で値が探索する値のリスト
（というか多分`iterator`なら何でもOKなんだと思う）

値の最大・最小を指定しておけばいいわけではないので、ここではnumpyが活躍する。
色々あるけど、`arange`と`linspace`を覚えておけば大体OKと思う。
どちらも始点と終点を入れ替えると昇順・降順が入れ替わる。
######Tips######
```python
 # 1以上6未満で1刻みのarrayを作る
 a = np.arange(1, 6, 1)
 >> [1 2 3 4 5]

 # 0.0以上1.0以下を10分割したarrayを作る
 a = np.linspace(0.0, 1.0, 10)
 >> [0.         0.11111111 0.22222222 0.33333333 0.44444444 0.55555556 0.66666667 0.77777778 0.88888889 1.        ]
```

#####データの準備#####
```python
  # データの準備
  x = load_iris().data
  y = load_iris().target

  # 25%分のデータを除けておく
  x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.25)
```
説明変数となるx、目的変数となるyを75%と25%にランダムに分ける。
ここでの25%は最後の評価用で、75%を使って`GridSearchCV`によるパラメータ探索を行う。

#####GridSearchCVのインスタンスを作成&学習&スコア記録#####
```python
  # GridSearchCVのインスタンスを作成&学習&スコア記録
  gscv = GridSearchCV(SVC(), param(), cv=4, verbose=2)
  gscv.fit(x_train, y_train)
```
`GridSearchCV`の第1引数には推定器のインスタンスを渡す。
探索せずに固定したいパラメータがあれば、ここで指定しておけば常にそのパラメータが使われる。
第2引数にはパラメータの探索空間(ディクショナリ)を渡す。ここでは先ほどの関数を渡す。

ここでのパラメータ`cv`は交差検証の手法を指定する。
この例のようにIntで指定するとK-Fold法のn分割のnを指定することになる。　※省略すると3になる。
Int型以外では`scikit-learn`のデータ分割(`ShuffleSplit`等)を指定することもできる。
`verbose`は進捗を見たい場合は1や2を指定しておくと標準出力で進捗が見れるけど、プログレスバーみたいな上等な物は出ない。

`fit`にデータを放り込むと探索が始まる。設定した探索空間次第だが、長くなる。が、自動である。

#####スコアの一覧を取得#####
```python
  # スコアの一覧を取得
  gs_result = pd.DataFrame.from_dict(gscv.cv_results_)
  gs_result.to_csv('gs_result.csv')
```
ディクショナリで返ってくるので、そのままpandasのDataFrameに入れてCSV出力する。
・・・というのも大抵の場合、大きすぎてEXCELなどじゃないと見れたものじゃないからだ。
キーは全部英語だけど、意味が分からない物はほとんどないと思うが、一番分かりやすいのは`rank_test_score`。
この列をキーに昇順に並べ替えた時、一番上に来るものが未学習データのスコアが一番良かったパラメータの適用例。

#####最高性能のモデルを取得し、テストデータを分類#####
```python
  # 最高性能のモデルを取得し、テストデータを分類
  best = gscv.best_estimator_
  pred = best.predict(x_test)

  # 混同行列を出力
  print(confusion_matrix(y_test, pred))
```
`GridSearchCV`のインスタンスから`best_estimator_`にアクセスすると最高性能だったパラメータを適用した学習済みのインスタンスを取り出せる。
このまま、最初に除けておいた25%のデータを分類させ、混同行列を出力すると・・・・

``` :output
[[14  0  0]
 [ 0 12  1]
 [ 0  0 11]]
```
1つだけ間違ってしまうようだが、上々の性能だ。

###最後に###
`GridSearchCV`の必要最小限の使い方を解説しました。
カスタマイズ性が高いので、これ以上は色々試されると良いと思います。

例えば、評価方法は`scikit-learn`の場合、回帰問題だと決定係数$R^2$、分類問題だと正解率が適用されて、いずれも高い方が良いように扱われますが、回帰問題で正負反転した平均二乗誤差などを使うこともできます。