SageMakerとxGBoostで機械学習の全体像をつかむ

基盤エンジニアにとっても、機械学習基盤を検討する上で、機械学習とそのワークフローを知っておきたい。
そこで、AWS SageMakerで機械学習チュートリアルを試してみた備忘録。
機械学習初心者なので、チュートリアルで出てくる基本的な用語の概要もメモするので、ちょいちょい脱線気味になります。

#チュートリアル概要
■クレジットカード債務不履行ユーザ予測
https://aws.amazon.com/jp/blogs/news/simplify-machine-learning-with-xgboost-and-amazon-sagemaker/

・ユーザ属性と過去の支払履歴/債務不履行実績データをもとに、テストユーザの債務不履行になる確率を予測
・xGboostで、2クラス分類ロジスティック回帰

※2クラス分類ロジスティック回帰とは超ざっくり言うと、A or Bを1〜0の範囲で確立で出す方法


#xGBoostとは？

・eXtreme Gradient Boosting

・勾配ブースティングツリー：GBDT(Gradient Boosting Decision Tree)を使ったアルゴリズム（アンサンブル学習）の実装
・DMLCが提供するOSSの機械学習ライブラリ
・回帰や分類によく使われる
・Kaggle(データサイエンスコンペ)で上位利用者の採用が多いことでも有名
・scikit-learnでもGBDT実装はあるが、それと比較しても優位（らしい）

ドキュメント）
https://xgboost.readthedocs.io/en/latest/
参考）
http://kefism.hatenablog.com/entry/2017/06/11/182959
https://qiita.com/R1ck29/items/4607b4ac941439ed6bbc

###アンサンブル学習とは？
複数の弱学習器を組み合わせる手法で、超ざっくり言うと多数決。
※弱学習器とは、文字通り"弱い(性能の低い)"学習モデル。決定木など。
※インフラ的に言えばある意味「性能の低いホストを組み合わせてクラスタにすれば高性能になる」的発想

アンサンブル学習には、いくつか組み合わせ方法がある。

①バギング
　・弱学習器を並列に学習して組み合わせる
　　※インフラ的に言えばある意味MapReduce的発想
　　例：ランダムフォレスト

②ブースティング
　・弱学習器を直列(逐次的)に学習して組み合わせる
　・前の弱学習器の結果をもとに、間違った箇所を重点的に精度を上げていく
　　例：AdaBoost、勾配ブースティング

③スタッキング
　・上記のモデルを含む様々な学習モデル結果を積み上げ（スタック）して精度向上を図る上級技
　※データマイニングコンペ「KDD cup」2015年優勝者は64個のモデルでスタッキングしている。

参考）
https://www.codexa.net/what-is-ensemble-learning/

###勾配ブースティング(Gradient Boosting)とは？
・ブースティングのアンサンブル学習の１つ
・ブースティングの際の最適化問題に勾配降下法を利用している
・弱識別器に決定木をもちいたものがGBDT(Gradient Boosting Decision Tree)※xGBoostはこれ

参考）
https://zaburo-ch.github.io/post/xgboost/
http://smrmkt.hatenablog.jp/entry/2015/04/28/210039
　

#AWS SageMakerとは？

###特徴

![sagemaker.png](https://qiita-image-store.s3.amazonaws.com/0/112537/752cddea-e450-4d3f-fbb6-9ff920a1374b.png)


・開発者やデータサイエンティストが機械学習モデルを開発、トレーニング(学習)、デプロイできるようにするマネージド型サービス
・マネージドのJupyter Notebookを利用でき、煩雑なインストール作業やリソース設定なく、すぐに機械学習プログラムの開発ができる
・さらに、開発したモデルを、アプリケーションから連携するためのエンドポイントも提供してくれる。
・さまざまなビルトインアルゴリズムが提供されている
・すべてをSageMakerで行う必要もなく、一般的に高性能リソースが必要なモデル開発(トレーニング)はオンプレ資産で開発し、デプロイ(エンドポイント)だけSageMakerなど、柔軟な使い分けもできる。（自前のdockerコンテナで実装）
・学習ジョブの分散学習も、SageMakerのAPI（sagemaker.estimater）で分散数を指定するだけで容易に可能。
・エンドポイントでは、複数モデルのA/Bテストも可能


###モデル開発パターン

①ビルトインアルゴリズムを使う

②OSS機械学習フレームワークを使って実装
※セットアップ済みの環境を使える

・TensorFlow
・MXNet
・Spark
・Chainer
・Keras（TensorFlow/CNTK/Theano上で実行可能な高水準のニューラルネットワークPythonライブラリ）
・etc

③上記以外で独自実装

コードを書く量は、ケースバイケースだがおそらく

① < ② < ③

###ビルトインアルゴリズム例
教師あり系）
・xGBoost
・ImageClassification(ResNet)
ほかいろいろ

教師なし系）
・K-Means
・主成分分析(PCA)
ほかいろいろ

###セキュリティ面
・2018年6月より、東京リージョン対応
・Private Link対応で、VPC内リソースで開発/学習/推論が可能
・PCIやISOなどの第三者認証対応

###公式サイト）
https://aws.amazon.com/jp/sagemaker/
black belt）
https://www.slideshare.net/AmazonWebServicesJapan/20180308-aws-black-belt-online-seminar-amazon-sagemaker-90045719


#[本編]クレジットカード債務不履行ユーザ予測

詳細）
https://aws.amazon.com/jp/blogs/news/simplify-machine-learning-with-xgboost-and-amazon-sagemaker/

##[1] 全体の流れ

①事前準備
　・データおよび開発モデル格納用S3バケット作成
　・SageMakerのJupyterNotebookインスタンス作成
　  ※カーネルはconda_python2 （python2.7）
②データ収集/整形
　※トレーニング用/バリデーション用/テスト用含む
③学習モデル作成/トレーニング
④学習済モデルのデプロイ（エンドポイント作成）
⑤推論実行（テスト）

チュートリアルではここまでですが、ついでに以下も対応
⑥ハイパーパラメータチューニング
⑦外部プログラムから推論実行（エンドポイント連携）

事前準備については省略し、②以降の補足を中心に記載します


##[2] データ収集

```py
bucket = '<S3バケット名>'
prefix = '<S3キー>'

# Define IAM role
import boto3
import re
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os
import sagemaker
from sagemaker import get_execution_role
from sagemaker.predictor import csv_serializer

#指定したROLEを読み込み
role = get_execution_role()

#データ取得
!wget https://archive.ics.uci.edu/ml/machine-learning-databases/00350/default%20of%20credit%20card%20clients.xls
dataset = pd.read_excel('default of credit card clients.xls')

#データ確認
pd.set_option('display.max_rows', 8)
pd.set_option('display.max_columns', 15)
dataset
```

データを見てみるとこんな感じ

![sagemaker2.png](https://qiita-image-store.s3.amazonaws.com/0/112537/671920aa-ccc7-1565-c36c-e7f785bbb956.png)

X1～5は性別などのユーザ属性、X6～23は支払いに関する履歴、Yは債務不履行になったかのTrue/False

今回は過去の履歴をもとに、債務不履行になりそうな人を確立で当てるので、“Y” 属性をターゲット属性として予測する。

##[2] データ整形

SageMakerのxGboostは、CSV または LibSVM 形式のデータを読み込み可能。
CSVトレーニングの場合、ターゲット変数が最初の列にあり、CSV にはヘッダーレコードがないと見なす。
CSV推論の場合、アルゴリズムは CSV 入力にラベル列がないと見なす。

そこで、元データのindex（ID列）を削除し、ターゲット変数となるY列を1カラム目に移行する

```py
dataset = dataset.drop('ID')
dataset = pd.concat([dataset['Y'], dataset.drop(['Y'], axis=1)], axis=1) 
```

そしてデータを、トレーニング用、バリデーション用、テスト用の３つに分割し、S3に格納する

それぞれの役割は
・トレーニング：学習用
・バリデーション：ハイパーパラメータチューニング用
・テスト：推論テスト用

参考）
https://qiita.com/QUANON/items/ae569961ea02b4468e23

```py
train_data, validation_data, test_data = np.split(dataset.sample(frac=1, random_state=1729), [int(0.7 * len(dataset)), int(0.9 * len(dataset))])
train_data.to_csv('train.csv', header=False, index=False)
validation_data.to_csv('validation.csv', header=False, index=False)

boto3.Session().resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train/train.csv')).upload_file('train.csv')
boto3.Session().resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'validation/validation.csv')).upload_file('validation.csv')
```

##[3] 学習/トレーニング

SageMakerでの開発には、以下のオプションがある
・AWS SDK for Python (Boto) 
・SageMaker高レベルPythonライブラリ
後者を利用すると、トレーニングやデプロイ等が、非常に簡易なコードのみで実装できる。

具体的にはfit()でトレーニング実行、deploy()で学習済モデルデプロイ（エンドポイント作成）など。

```py
from sagemaker.session import s3_input

# 対象リージョンのxGboostのコンテナを指定。（ジョブが実行される仮想環境）
containers = {'ap-northeast-1': '501404015308.dkr.ecr.ap-northeast-1.amazonaws.com/xgboost:latest'}

sess = sagemaker.Session()

#Sagemaker Setting
xgb = sagemaker.estimator.Estimator(containers[boto3.Session().region_name],
                                    role, 
                                    train_instance_count=1, 
                                    train_instance_type='ml.m4.xlarge',
                                    output_path='s3://{}/{}/output'.format(bucket, prefix),
                                    sagemaker_session=sess)

#Hyper Parameter
#objective パラメータ：XGBoost にどのような問題 ( 分類、回帰、ランク付けなど ) を解決するかを指示
#binary:logistic：バイナリ分類の問題
xgb.set_hyperparameters(eta=0.1,
                        objective='binary:logistic',
                        num_round=25)

#Input File指定
s3_input_train = s3_input(s3_data='s3://{0}/{1}/train/train.csv'.format(bucket,prefix), content_type='text/csv')
s3_input_validation = s3_input(s3_data='s3://{0}/{1}/validation/validation.csv'.format(bucket,prefix), content_type='text/csv')

#Trainning実行
xgb.fit({'train': s3_input_train, 'validation': s3_input_validation})
```

fit()でトレーニングが実行される

## [4]モデルデプロイ/エンドポイント作成

SageMaker高レベルPythonライブラリを使えばたったこれだけ。
デプロイインスタンスリソースタイプなどを指定する。

```py
xgb_predictor = xgb.deploy(initial_instance_count=1, instance_type='ml.m4.xlarge')
```

## [5]推論実行

ターゲット変数となるY列を抜いたテスト用データを入力として、推論（債務不履行確率を予測）する関数を定義。

```py
xgb_predictor.content_type = 'text/csv'
xgb_predictor.serializer = csv_serializer
xgb_predictor.deserializer = None


def mypredict(data):
    predictions = xgb_predictor.predict(data).decode('utf-8')
     #推定結果を , 区切りでarrayで返却
    return np.fromstring(predictions[1:], sep=',')
```

この関数に、テストデータを入力してみる。
ためしにID=118の、Y列抜きデータをサンプルに。

```py
td = test_data[test_data.index == 118]
td.as_matrix()[:, 1:]
```
中身は↓の感じ

```py
array([[80000, 1, 2, 2, 26, 2, 0, 0, 0, 0, 0, 14029, 15493, 16630, 17055,
        17629, 18186, 2000, 1700, 1000, 1000, 1000, 1000]], dtype=object)
```

このデータを先ほどの推論実行関数に入力してみる

```py
input_df = td

predictions = mypredict(input_df.as_matrix()[:, 1:])
#as_matrix() : convert dataframe to array
#as_matrix()[:, 1:] : 全行の1カラム目以降(ターゲット変数のYカラムを除く) 

#np.set_printoptions(threshold=np.inf)
predictions
```

すると、0.XX のように対象ユーザの債務不履行確率が返却される。
※とりあえず全体の流れ把握が目的で、精度については置いておく

##[6] ハイパーパラメータチューニング

そもそもハイパーパラメータとは、トレーニング時に人が決める設定値。設定の仕方で精度が変わるので、チューニングをする。
チューニング（イケてる値の見つけ方）にも手法がある。
・ランダムサーチ
・グリッドサーチ
・ベイズ最適化
などなど

xGboostのハイパーパラメータ一覧はこちら
https://docs.aws.amazon.com/ja_jp/sagemaker/latest/dg/xgboost_hyperparameters.html

SageMaker APIでチューニング処理も簡単に実装できる。

```py
from sagemaker.tuner import HyperparameterTuner, IntegerParameter, CategoricalParameter, ContinuousParameter

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'
    )

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


# チューナー定義
tuner = HyperparameterTuner(xgb,
                    objective_metric_name='validation:auc',
                    hyperparameter_ranges=hyperparameter_ranges,
                    max_parallel_jobs=10, 
                    max_jobs=100)  

# トレーニング実行
tuner.fit({'train': s3_input_train, 'validation': s3_input_validation})
```

参考）
https://qiita.com/kazuhisa-nagashima/items/6c2b7eb7226f41bb0989



##[7] 外部プログラムから推論実行（エンドポイント連携）

SageMakerにデプロイされた学習済モデルを外部プログラムからたたいて推論結果を得るデモ。
例えば、ローカル環境で↓のようなコードでエンドポイントを指定し、AWS CLIで数行で実行できる。
body部には、推論用の入力データを指定している。

```bash
#!/bin/bash

aws sagemaker-runtime invoke-endpoint \
  --endpoint-name '<エンドポイント名>' \
  --body '80000, 1, 2, 2, 26, 2, 0, 0, 0, 0, 0, 14029, 15493, 16630, 17055,17629, 18186, 2000, 1700, 1000, 1000, 1000, 1000' \
  --content-type "text/csv"  \
  ./tmp/output.csv
```

上記では、./tmp/output.csvに推論結果が出力される。

pythonなら

 ```py
result = boto3.client('sagemaker-runtime').invoke_endpoint(
    EndpointName='<エンドポイント名>',
    Body=<入力値>,
    ContentType='text/csv'
)
```

参考）
https://docs.aws.amazon.com/cli/latest/reference/sagemaker-runtime/invoke-endpoint.html