scikit-learnでモデルのハイパーパラメータチューニングをしよう！

#ハイパーパラメータチューニングって？
モデルによってあらかじめ決めなきゃいけないパラメータがあります。
(例えばk-meansのクラスタ数や、SVCの正則化項の強さ、決定木の深さなど)

それを『ハイパーパラメータ』というのですが、困ったことに同じモデルだとしてもハイパーパラメータの値によって精度が**大幅に**変わることもあります。

それをうまく、学習データを用いて決めてしまおうというのが、ハイパーパラメータチューニングなのです！！

#グリッドサーチとランダムサーチ
そのチューニング手法の内、今回扱うのは、グリッドサーチとランダムサーチの2つです。
ざっくりいいますと、ハイパーパラメータαがあるとすると以下の流れで実行します。

・グリッドサーチは、あらかじめαの**範囲**(ex. 0,1,2,3,4,5など)を指定して、実際にそのパラメータでモデルの精度を出してみて、一番いいやつをパラメータにする。

・ランダムサーチは、あらかじめαが従う**分布**(ex. 平均0, 標準偏差1の正規分布など)を指定して、そこからランダムに取り出し、実際にそのパラメータでモデルの精度を出してみて、一番いいやつをパラメータにする.

![Screen Shot 2017-02-25 at 22.37.35.png](https://qiita-image-store.s3.amazonaws.com/0/167522/936568c9-87d0-7e7f-c5e3-24065387718c.png)


以上のように、両者ともハイパーパラメータαをそのままあてずっぽうで決めるという手順ではなく、
その前に、**範囲や分布**を決め、実際の訓練データを使って決めるという手順をとっていることがわかります。(より詳細については、参考文献をご覧になってください!)


#Pythonコード

上記の2つがscikit-learnでは標準装備されているので利用していきます！
python3.5.1, scikit_learn-0.18.1でのコードです。

今回は、UCIのMachine Learning Repositoryからデータをとって、RandomForestClassifierの2つの分類器を使用して、パラメータチューニングをしています。
コードの全容は[github](https://github.com/ragAgar/qiita/blob/master/Grid_and_Random_Search.ipynb)にアップロードしています.


### STEP1 データをUCIリポジトリからダウンロード

```python:Grid_and_Random_Search.ipynb
 df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases'
                  '/breast-cancer-wisconsin/wdbc.data', header=None)
```
分かりやすくするために、予測したいカラムをTargetに、その他をa~にします。

```python:Grid_and_Random_Search.ipynb
 columns_list = [] 
 for i in range(df.shape[1]):
     columns_list.append("a%d"%i) 
 columns_list[1] = "Target" 
 df.columns = columns_list
```

### STEP2 データを分割
```python:Grid_and_Random_Search.ipynb
 y = df["Target"].values
 X = df.drop(["a0","Target"],axis=1)
```
trainデータとtestデータに分割

```python:Grid_and_Random_Search.ipynb
 #split X,y to train,test(0.5:0.5)
 from sklearn.cross_validation import train_test_split

 X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.5,random_state=2017)
```


### STEP3 デフォルトの状態でのモデルの精度を検査してみる.

```python:Grid_and_Random_Search.ipynb
 from sklearn.metrics import classification_report

 def model_check(model):
     model.fit(X_train,y_train)
     y_train_pred = classification_report(y_train,model.predict(X_train))
     y_test_pred  = classification_report(y_test,model.predict(X_test))
        
     print("""【{model_name}】\n Train Accuracy: \n{train}
           \n Test Accuracy:  \n{test}""".format(model_name=model.__class__.__name__, train=y_train_pred, test=y_test_pred))

print(model_check(RandomForestClassifier()))
```
```python:出力結果1(デフォルト)
    [RandomForestClassifier]
     Train Accuracy: 
                 precision    recall  f1-score   support

              B       1.00      1.00      1.00        67
              M       1.00      1.00      1.00        75

    avg / total       1.00      1.00      1.00       142


     Test Accuracy:  
                 precision    recall  f1-score   support

              B       0.89      0.93      0.91        72
              M       0.93      0.89      0.91        70

    avg / total       0.91      0.91      0.91       142
```
Trainデータの正答率は1.0、Testデータの正答率は0.91だということがわかりました。
ここからグリッドサーチとランダムサーチを実装していきます。
以降は参考文献3を参考にしています。

### STEP4 グリッドサーチ
```python:Grid_and_Random_Search.ipynb
 #Grid search

 from sklearn.grid_search import GridSearchCV

 # use a full grid over all parameters
 param_grid = {"max_depth": [2,3, None],
              "n_estimators":[50,100,200,300,400,500],
              "max_features": [1, 3, 10],
              "min_samples_split": [2, 3, 10],
              "min_samples_leaf": [1, 3, 10],
              "bootstrap": [True, False],
              "criterion": ["gini", "entropy"]}

 forest_grid = GridSearchCV(estimator=RandomForestClassifier(random_state=0),
                 param_grid = param_grid,   
                 scoring="accuracy",  #metrics
                 cv = 3,              #cross-validation
                 n_jobs = 1)          #number of core

 forest_grid.fit(X_train,y_train) #fit

 forest_grid_best = forest_grid.best_estimator_ #best estimator
 print("Best Model Parameter: ",forest_grid.best_params_)
```

```python:出力結果2(グリッドサーチ)
    [RandomForestClassifier]
     Train Accuracy: 
                 precision    recall  f1-score   support

              B       0.99      0.99      0.99        67
              M       0.99      0.99      0.99        75

    avg / total       0.99      0.99      0.99       142


     Test Accuracy:  
                 precision    recall  f1-score   support

              B       0.96      0.89      0.92        72
              M       0.89      0.96      0.92        70

    avg / total       0.92      0.92      0.92       142
```
合計の正答率や、f1-scoreなど全ての精度が上昇しています！！

### STEP5 ランダムサーチ
ランダムサーチでは、scipyを使ってパラメータの従う分布を表現する。
今回は、イテレーション回数をグリッドサーチと同様にしています。

```python:Grid_and_Random_Search.ipynb
#Random search
from sklearn.grid_search import RandomizedSearchCV
from scipy.stats import randint as sp_randint

param_dist = {"max_depth": [3, None],                  #distribution
              "n_estimators":[50,100,200,300,400,500],
              "max_features": sp_randint(1, 11),
              "min_samples_split": sp_randint(2, 11),
              "min_samples_leaf": sp_randint(1, 11),
              "bootstrap": [True, False],
              "criterion": ["gini", "entropy"]}

forest_random = RandomizedSearchCV( estimator=RandomForestClassifier( random_state=0 ),
                                    param_distributions=param_dist,
                                    cv=3,              #CV
                                    n_iter=1944,          #interation num
                                    scoring="accuracy", #metrics
                                    n_jobs=1,           #num of core
                                    verbose=0,          
                                    random_state=1)

forest_random.fit(X,y)
forest_random_best = forest_random.best_estimator_ #best estimator
print("Best Model Parameter: ",forest_random.best_params_)
```
```python:出力結果3(ランダムサーチ)
    [RandomForestClassifier]
     Train Accuracy: 
                 precision    recall  f1-score   support

              B       1.00      1.00      1.00        67
              M       1.00      1.00      1.00        75

    avg / total       1.00      1.00      1.00       142


     Test Accuracy:  
                 precision    recall  f1-score   support

              B       0.94      0.92      0.93        72
              M       0.92      0.94      0.93        70

    avg / total       0.93      0.93      0.93       142
```
デフォルトの場合と比べると、どの項目も2%増加していることがわかりました！

# まとめ
グリッドサーチ、ランダムサーチともに精度が良くなりました！
ただ、おそらく今回は元々精度が高いようなデータを選んだため、効果が見えづらくなってしまったのではないかと思います。
精度が良くないようなデータに試してみるとチューニングの効果が分かりやすいかもしれません。

コード全容は、[github](https://github.com/ragAgar/qiita/blob/master/Grid_and_Random_Search.ipynb)にアップロードしてあります。


参考文献
1. [Bergstra, J., & Bengio, Y. (2012)](http://www.jmlr.org/papers/volume13/bergstra12a/bergstra12a)
2. http://qiita.com/SE96UoC5AfUt7uY/items/c81f7cea72a44a7bfd3a
3. http://scikit-learn.org/stable/auto_examples/model_selection/randomized_search.html