[kaggle / python] 回帰問題（house prices）に取り組む（６）~ハイパーパラメータのチューニング~

前回の続きです。
今回は、ハイパーパラメータのチューニングを行いました。

前回の記事はこちら。

https://qiita.com/siruku6/items/48e70607dc16746ec280


もう、「超初歩」っていう感じじゃなくなってきたので、今回のタイトルからは「超初歩」の文言を外しました。

# the previous and today's result（前回と今回の結果）

|           |やったこと                                  |score  |
|-----------|--------------------------------------------|-------|
|前回       |カテゴリ特徴量に対するOne hot Encodingの適用|0.14835|
|今回       |ハイパーパラメータのチューニング（optuna）  |0.13723|

なんかすごい改善した。

# Why?

ハイパーパラメータによって精度はかなり変わります。
なので、ハイパーパラメータのチューニングは、どのモデルだとしてもやって損はないと思います。（説明が雑でごめんなさい(´；ω；｀)）

さて、実際にやったことを見ていきます。

# 1. source cord

## 1-0. same as the previous cord

いつも通り、この「1-0.」は前回と同じソースです(^ワ^\*)
今回ここでは、

1. モジュール読み込み
1. データ読み込み
1. 数値特徴量のカテゴリ変数化
1. 特徴量の追加と削除
1. One-hot encoding
1. trainingデータとtestデータの分割

まで行っています。

```python
# 1. import modules
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from xgboost import XGBClassifier, XGBRegressor

# 2. load data
import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))
train = pd.read_csv('/kaggle/input/house-prices-advanced-regression-techniques/train.csv')
test = pd.read_csv('/kaggle/input/house-prices-advanced-regression-techniques/test.csv')

# 3. cast columns
train['MSSubClass'] = train['MSSubClass'].apply(str)
train['YrSold'] = train['YrSold'].astype(str)
train['MoSold'] = train['MoSold'].astype(str)
test['MSSubClass'] = test['MSSubClass'].apply(str)
test['YrSold'] = test['YrSold'].astype(str)
test['MoSold'] = test['MoSold'].astype(str)

# 4-1. add new column
train['totalRoomsSF'] = train['1stFlrSF'] + train['2ndFlrSF'] + train['TotalBsmtSF']
test['totalRoomsSF'] = test['1stFlrSF'] + test['2ndFlrSF'] + test['TotalBsmtSF']

# 4-2. drop ID column
train_ID = train['Id']
test_ID = test['Id']
train = train.drop(['Id'], axis=1)
test = test.drop(['Id'], axis=1)

# 5-1. 全保有データを縦方向に結合
## 一度全データを縦方向に連結するので、
## 事前に、train(学習データ)とtest(提出用データ作成のための特徴量)を識別するための
## 'for_train'カラムを追加しておく
train['for_train'] = True
test['for_train'] = False

## データを縦方向に結合
df_all_data = pd.concat([train, test])

# 5-2. One hot Encdoging
# NOTE: 今回One hot Encodingにかける、object型の列を特定
encode_target_columns = df_all_data.columns[df_all_data.dtypes == 'object']

## One hot Encoding適用
encoded_all_data = pd.get_dummies(df_all_data, columns=encode_target_columns)

# 6-1. split into 'train' and 'test'
encoded_train = encoded_all_data.loc[encoded_all_data['for_train'], :]
encoded_test = encoded_all_data.loc[~encoded_all_data['for_train'], :]

# 6-2. split data into explanatory variable(説明変数) and response variable(目的変数)
tmp_train_y = encoded_train['SalePrice']
encoded_train_x = encoded_train.drop(['for_train', 'SalePrice'], axis=1)
encoded_test = encoded_test.drop(['for_train', 'SalePrice'], axis=1)

# 6-3. split data into training data and test data
x_train, x_test, y_train, y_test = \
    train_test_split(encoded_train_x, tmp_train_y, test_size=0.20, random_state=0)
```

## 1-1. Newly added lines

今回新たに実装した or 変更を加えたのは以下の部分です

7．最適なハイパーパラメータの探索
8．学習model作成（& 検証）

さて、前処理、データ分割まで終えた後で、今回新しくハイパーパラメータのチューニングを行いました。

学習データと相性の良いパラメータを探したいはずなので、ハイパーパラメータのチューニングを行うタイミングは、全ての前処理が終わった後、学習の直前としました。

実装はほぼこちらの記事の通りです。

https://qiita.com/c60evaporator/items/a9a049c3469f6b4872c6#%E3%83%99%E3%82%A4%E3%82%BA%E6%9C%80%E9%81%A9%E5%8C%96optuna%E3%81%AE%E5%A0%B4%E5%90%88

```python
# 7. 最適なハイパーパラメータの探索
import time
from sklearn.model_selection import KFold, cross_val_score
import optuna

seed = 42  # 乱数シード
# NOTE: ハイパーパラメータのチューニング時に用いるXGBoostモデル
model = XGBRegressor(
    n_estimators=20, random_state=seed,
    eval_metric='rmse',
    early_stopping_rounds=5,
)
cv = KFold(n_splits=3, shuffle=True, random_state=seed)
scoring = 'neg_mean_squared_error'
# 学習時fitパラメータ指定
fit_params = {
    'verbose': 0,  # 学習中のコマンドライン出力
    #'early_stopping_rounds': 5,  # 学習時、評価指標がこの回数連続で改善しなくなった時点でストップ
    #'eval_metric': 'rmse',  # early_stopping_roundsの評価指標
    'eval_set': [(x_test, y_test)],  # early_stopping_roundsの評価指標算出用データ
}
start = time.time()

# ベイズ最適化時の評価指標算出メソッド
def bayes_objective(trial):
    params = {
        # 'n_estimators': trial.suggest_int('n_estimators', 10, 100),
        'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),
        'min_child_weight': trial.suggest_int('min_child_weight', 3, 8),
        'max_depth': trial.suggest_int('max_depth', 2, 4),
        'colsample_bytree': trial.suggest_float('colsample_bytree', 0.2, 1.0),
        'subsample': trial.suggest_float('subsample', 0.2, 1.0),
        'reg_alpha': trial.suggest_float('reg_alpha', 0.001, 0.1, log=True),
        'reg_lambda': trial.suggest_float('reg_lambda', 0.001, 0.1, log=True),
        'gamma': trial.suggest_float('gamma', 0.0001, 0.01, log=True),
    }
    # モデルにパラメータ適用
    model.set_params(**params)
    # cross_val_scoreでクロスバリデーション
    scores = cross_val_score(
        model, x_train, y_train, cv=cv,
        scoring=scoring, fit_params=fit_params,
        n_jobs=-1
    )
    val = scores.mean()
    return val

# ベイズ最適化を実行
study = optuna.create_study(
    direction='maximize',
    sampler=optuna.samplers.TPESampler(seed=seed)
)
study.optimize(bayes_objective, n_trials=200)

# 最適パラメータの表示と保持
best_params = study.best_trial.params
best_score = study.best_trial.value
print(f'最適パラメータ {best_params}\nスコア {best_score}')
print(f'所要時間{time.time() - start}秒')
```

ここで求まったハイパーパラメーターである `best_params` を使って、 `xgboost` の学習から結果の提出まで行っていきます！

```python
best_params

# 出力
{'learning_rate': 0.19655973362890475,
 'min_child_weight': 8,
 'max_depth': 4,
 'colsample_bytree': 0.7154187972679216,
 'subsample': 0.9242436226210999,
 'reg_alpha': 0.009030975784551901,
 'reg_lambda': 0.039104226426611016,
 'gamma': 0.0005399897992324197}
```


```python
# 8. 学習model作成（& 検証）
# n_estimatorsを 20 から 200 に増やしたら精度が上がったので、上げておいた
model = XGBRegressor(n_estimators=200, random_state=0, **best_params)
model.fit(x_train, y_train, eval_set=[(x_train, y_train), (x_test, y_test)])

#trainデータに対してのloss推移をplot
plt.plot(model.evals_result()['validation_0']['rmse'], label='train rmse')
#testデータに対してのloss推移をplot
plt.plot(model.evals_result()['validation_1']['rmse'], label='eval rmse')
plt.grid()
plt.legend()
plt.xlabel('rounds')
plt.ylabel('rmse')
plt.show()

# 学習結果 > [199]	validation_0-rmse:5320.75537	validation_1-rmse:27679.82422

# NOTE:
# n_estimators = 20 にしたときの精度は以下の通り
# 学習結果 > [19]	validation_0-rmse:18346.21094	validation_1-rmse:31334.98242
```

第5回の検証結果と比べると、

|評価用データ|第5回RMSE|今回RMSE|
|:-----------|--------:|-------:|
|trainingデータ|8181.078...|5320.755...|
|validationデータ|35119.731...|27679.824...|

という結果に。
今回、大幅に精度改善しているのがわかります。


## 1-2. submission

さて、提出しましょう。

```python
# 9. create csv for submission
predict_submission = model.predict(encoded_test)
submission = pd.DataFrame(
    {'Id': test['Id'], 'SalePrice': predict_submission}
)
submission.to_csv('submission.csv', index=False)
```

この結果、スコアは`0.14835`から`0.13723`へと改善されました。
かなりの改善に繋がりました...(; ･`д･´)

# Discussion (考察)

今回の変更は、

- Optunaで見つけたハイパーパラメータを利用  
Optunaで探索させる範囲は地道に狭めることで、より良いパラメータを探させた
- たまたま見つかったそこそこ良い`n_estimators`を利用

という2点でした。

それだけでかなり改善したので、ハイパーパラメータってやっぱり重要なんですね、、、と改めて感じました。

そして順位については、前回まで上位60%だったのに、今回遂に上位50%を切れたので、ちょっと小躍りしてます(((o(\*ﾟ▽ﾟ\*)o)))


# Next step

次は、目的変数を対数変換して学習させ、精度改善を試みた結果を記事にします。


# References

今回参考にした記事です。

https://qiita.com/c60evaporator/items/a9a049c3469f6b4872c6#%E3%83%99%E3%82%A4%E3%82%BA%E6%9C%80%E9%81%A9%E5%8C%96optuna%E3%81%AE%E5%A0%B4%E5%90%88

Optunaだけでなく、ハイパーパラメータ探索の様々な手法を取り上げて下さっています。

# シリーズ一覧

|No|New Trial                    |Score  |Link|Note|
|--|-----------------------------|-------|----|----|
| 1|xgboost                      |0.15663|[こちら](https://qiita.com/siruku6/items/97fdd1b12e6b18da103f)||
| 2|Label Encoding               |0.15160|[こちら](https://qiita.com/siruku6/items/48747e8c7a7f596bfa8c)||
| 3|Add and delete column        |0.15140|[こちら](https://qiita.com/siruku6/items/7b693568e6b2535e0c2a)||
| 4|Make integer into categorical|0.14987|[こちら](https://qiita.com/siruku6/items/7f3dc5a9932cd82ab860)||
| 5|One hot Encoding             |0.14835|[こちら](https://qiita.com/siruku6/items/48e70607dc16746ec280)||
| 6|Hyper parameters tuning      |0.13723|本記事||
| 7|Logarithimic transformation	 |0.13347||記事未作成|