AWS SagemakerでXGBoostのハイパーパラメータチューニング

知識ほとんどなくても簡単な機械学習なら誰でも（しかも低コストで）できるようになったんだなあというメモ。
ファイルをアップロードした後は、jupyterからでもGUIでもできるので、両方書きます。

# ファイルの準備

Sagemaker組み込みのMLインスタンスを呼び出すので、s3にファイルを置きます。
形式はデフォルトではlibsvm形式ですが、`content_type`を指定することでcsvにも対応しています。

```py
import pandas as pd
# df_X, df_y are given

train_fname = 'train.csv'
# 目的変数は最後の列に置く
df = pd.concat([df_X, df_y], axis=1)
df.to_csv(train_fname, header=False, index=False)

import boto3
import os
bucket = 'bucket'
train_dir = 'train_dir'
train_path = os.path.join(train_dir, train_fname)

boto3.resource('s3').Bucket(bucket).Object(train_path).upload_file(train_fname)

```

同様に、別ディレクトリにvalidetionデータも置いておきます。
ディレクトリ毎にファイルを読み込むので、必ず異なるディレクトリにおいてください。
逆に、同じディレクトリに入れれば、データが複数のファイルに分割されていてもOKです。

# チューニングの実行（ノートブックインスタンスから）

数行追加するだけでチューニングできる。
- チューニングなしで実行するとき：宣言したXGBoostインスタンスに学習用ディレクトリを指定して`.fit`
- チューニングありで実行するとき：XGBoostインスタンスとチューニングの設定をHyperparameterTunerに渡して、そのインスタンスに学習用ディレクトリを指定して`.fit`

`objective_metric_name`は、GUIの方を眺めて目的のものを引っ張ってくると楽。

```py
from sagemaker.session import s3_input
from sagemaker.predictor import csv_serializer, json_deserializer
import sys
from sagemaker.tuner import HyperparameterTuner, IntegerParameter, CategoricalParameter, ContinuousParameter

#東京リージョンの場合
region_path = '501404015308.dkr.ecr.ap-northeast-1.amazonaws.com/xgboost:latest'

xgb = sagemaker.estimator.Estimator(region_path,
    sagemaker.get_execution_role(), 
    train_instance_count=1, 
    train_instance_type=`ml.m4.xlarge`,
    output_path='s3://{0}/output/'.format(bucket),
    sagemaker_session=sagemaker.Session())

#固定で使いたいハイパーパラメータはここで入れておく
#ここで渡しても、HuerparameterTunerでチューニング候補に入れたパラメータはチューニングされる
xgb.set_hyperparameters(max_depth=8,
    eta=0.02,
    gamma=0,
    min_child_weight=1,
    subsample=0.8,
    silent=0,
    objective='binary:logistic',
    num_round=100,
    max_delta_step=1,
    colsample_bytree=0.8,
    scale_pos_weight=1,
    eval_metric='auc')

# チューニングしたいパラメータと範囲をdictで指定
hyperparameter_ranges = {'eta': ContinuousParameter(0.1,1.0),
                         'max_depth': IntegerParameter(5, 10)}

# HyperparameterTuner宣言

#max_parallel_jobsは10以下でないといけないらしい
tuner = HyperparameterTuner(xgb,
                    objective_metric_name=`validation:auc`,
                    hyperparameter_ranges=hyperparameter_ranges,
                    max_parallel_jobs=10, 
                    max_jobs=100)    

# libsvm形式の場合はアドレスを直接渡せるが、content_typeを指定するときはs3_inputで指定してから渡さないといけない
train = s3_input(s3_data='s3://{0}/{1}'.format(bucket,train_dir), content_type='text/csv')
valid = s3_input(s3_data='s3://{0}/{1}'.format(bucket,valid_dir), content_type='text/csv')
tuner.fit({'train': train, 'validation': valid})

# fitは他インスタンスに仕事を投げる命令なので、メインスレッドは自由になる。
# チューニング終了まで待ちたい場合はwaitする。
tuner.wait()

# 各ハイパーパラメータでの結果はDataFrame形式で見られる
print tuner.analytics().dataframe()

# 最適なモデルをデプロイ
# 自動的に最適なモデルが使われるので、以降はチューニングなしと同じ
xgb_predictor_tuned = tuner.deploy(initial_instance_count=1, instance_type='ml.m4.xlarge')
xgb_predictor_tuned.content_type = 'text/csv'
xgb_predictor_tuned.serializer = csv_serializer
xgb_predictor_tuned.deserializer = json_deserializer

# predictも同じ
# test_array is given

predict = xgb_predictor_tuned.predict(test_array)

# エンドポイントの削除も同じ
xgb_predictor_tuned.delete_endpoint()
```


# チューニングの実行（GUIから）

ファイルさえ置いてあれば、`predict`以外は全てGUIでもできます。
簡単で便利。なんだけど過去のチューニング設定をちょっとだけいじって再チューニング、みたいなことができないからノートブックインスタンスからの方が結果的にストレスは少ないかも。
画像撮るのが面倒だったので文字ベースになります。。

- ダッシュボード左メニューから【ハイパーパラメータの調整ジョブ】を選択し、右上の方にある【ハイパーパラメータ調整ジョブの作成】を選択
- 【調整ジョブ名】に任意の（他と被らない）名前を付ける（日付とか含めると楽）
- I AMロールを設定
    - 何もわからないなら新しいロールの作成しとけばOK
        - これって`get_execution_role()`ってことなんですね
        - `arn:aws:iam::【アカウントID（ハイフンなし）】:role/service-role/`を【カスタムIAMロールのARN】に書く
- ネットワークは詳しくないので環境に従って選んでください
    - セキュリティとか気にならないなら非VPCでいいはず
- トレーニングジョブの設定
    - XGBoostを選択（当然他のでも良いけど、入力ファイルのフォーマットをアルゴリズムに応じて変えるのを忘れずに）
    - アルゴリズムを選択すると、下に【入力モード】【トレーニングイメージのECRパス】【メトリクス】が出てきます
        - 入力モードは、XGBoostではFile固定みたいですね。
        - トレーニングイメージのECRパスは、ノートブックインスタンスで指定したものと同じなので、逆に「このアルゴリズムのパスがわからない！」ってときはここから見られますね。
        - メトリクス名は、この次のページでここから選ぶことになります。ノートブックインスタンスから実行するときに`objective_metric_name`がわからない場合は、ここから選びましょう。

- 設定のチューニング
    - 戦略は、おそらくアルゴリズムによっては選べるのでしょう。
    - 目的が、前のページのメトリクス名よりも減っていますね。validetion系しかないみたいですね。過学習しないためには当たり前ですが。
    - タイプは目的に応じて自動で選ばれるもので良いと思います。自前で用意したアルゴリズムの場合は目的に合ったものを選べってことでしょうか。
- ハイパーパラメータ設定
    - ここで範囲をしていしたいものなどを決めます。
    - 星印が付いているもの以外は、未入力でもデフォルト値を使ってくれるみたいですね。
    - 下の方にあるobjectiveは、しっかりと目的変数にあったものを選びましょう。
- 入力データ設定
    - ここで使うファイル（のあるディレクトリ）を指定します。
    - trainとvalidationを用意しているので、チャネルの追加でvalidationという名前のチャネルも作っておきましょう。
    - コンテンツタイプは、`text/csv`
    - S3データタイプは`S3Prefix`
    - URIは`s3://bucket/train_dir`のようにします。
    - 他はデフォルトでOK
- 出力データ設定
    - S3のどこに学習済のモデルを置くかを指定します。
    - ノートブックインスタンスで使った`output_path`と同じです。
- リソース設定
    - インスタンスタイプと数を指定します。
- リソースの制限
    - トレーニングジョブの最大数
        - チューニングで探索するパラメータパターンの個数を指定します。
多いほど細かい探索が行われます。
    - 最大並行トレーニングジョブ
        - いくつのジョブを同時に実行するかを指定します。
    - トレーニングジョブあたりの最大ランタイム
        - ひとつのジョブに最大どのくらいかけるかの時間を指定します。
        - 全てのジョブが最大時間かかるとしたときに30日で終わらない場合、警告が出ます。


トレーニング終了後は、概要から各パラメータや評価値が確認できます。
トレーニングジョブの詳細から、【モデルの作成】でデプロイできます。

デプロイしたモデルは、使わない際は削除を忘れずに。
GUIの場合はダッシュボード左メニューから【エンドポイント】を選択し、削除したいエンドポイントを指定して【アクション】から削除するだけです。