Amazon SageMakerをはじめて使ってみる

## やりたいこと
SageMakerの特長や使い方などを学習したかったので、AWSのチュートリアルに沿ってひとまず使い始めてみたかった。

## 参考にしたサイト
* [AWS公式チュートリアル](https://aws.amazon.com/jp/getting-started/hands-on/build-train-deploy-machine-learning-model-sagemaker/)
* [クラスメソッドの記事](https://dev.classmethod.jp/articles/a-10-minute-tutorial-from-building-to-deploying-a-machine-learning-model-with-amazon-sagemaker/)

## やってみる

### S3バケットの作成

まずはS3バケットを作成します。こちらのS3バケットにはチュートリアルで作成するノートブックインスタンスがデータを保存する際に、保存先として使います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/af0b82f8-d1bc-ea40-3ad9-d0a51eac8e9f.png)

### Notebookインスタンスの作成
Amazon SageMakerのコンソールに入り、「Notebook instance >> Create notebook instance」をクリックして、以下の画面からNotebookインスタンスを作成します。こちらのNotebookインスタンスの上で、PythonコードなどをRUNするイメージです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/893423f9-df46-254d-83f3-9607ed7c1721.png)

S3バケットを選択する設定の部分は、先に作成したS3バケット名を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/327a9349-e534-89a7-19fe-1eb2cfaad137.png)

IAMロールは新規に作成し、Root accessはEnableのままとしておきます。ここまで設定したら「Create notebook instance」ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/eb34d604-fb8a-4735-d372-29c08294d41d.png)

notebookインスタンスの作成・起動が開始され、少し待つとSageMakerのコンソール画面ではStatusが「InService」となります。

### Notebookインスタンスを使ってデータの準備をする
notebookインスタンスの起動が完了したので「Open Jupyter」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/58e2f3c4-c1d1-dffb-14de-6facf5a966a0.png)

右上の「New」から`conta_python3`を選択して、Pythonを実行していくnotebookコンソールを表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/d0bdf3cd-4a03-14a9-24e6-03618bd2b2d0.png)

さて、ここからPythonを記述していきます。本記事のサンプルコードは全て参考資料にもある[AWS公式チュートリアル](https://aws.amazon.com/jp/getting-started/hands-on/build-train-deploy-machine-learning-model-sagemaker/)からコピーしてきています。

まずは以下のコードを張り付けてRUNをクリックします。ちなみに`Shift`+`Enter`でもRUNとなります。 以下のコードでは必要なライブラリをインポートしたり、環境変数を定義したりしています。
```python
# import libraries
import boto3, re, sys, math, json, os, sagemaker, urllib.request
from sagemaker import get_execution_role
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import Image
from IPython.display import display
from time import gmtime, strftime
from sagemaker.predictor import csv_serializer

# Define IAM role
role = get_execution_role()
prefix = 'sagemaker/DEMO-xgboost-dm'
my_region = boto3.session.Session().region_name # set the region of the instance

# this line automatically looks for the XGBoost image URI and builds an XGBoost container.
xgboost_container = sagemaker.image_uris.retrieve("xgboost", my_region, "latest")

print("Success - the MySageMakerInstance is in the " + my_region + " region. You will use the " + xgboost_container + " container for your SageMaker endpoint.")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/8fa62ddf-9f9c-b66c-9dd8-9cb85366ed71.png)
Successと出ればOKです。

次に以下のコードを貼り付けます。'your-s3-bucket-name'の個所は、任意のBuket名に置き換えます。先のステップとは別のS3バケットとなるので、新規の名前を付けてください。（そうでないと作成エラーとなります）
また、オプションですが、バケット作成先のリージョンも併せて変更してもOKです。

```python
bucket_name = 'your-s3-bucket-name' # <--- CHANGE THIS VARIABLE TO A UNIQUE NAME FOR YOUR BUCKET
s3 = boto3.resource('s3')
try:
    if  my_region == 'us-east-1':
      s3.create_bucket(Bucket=bucket_name)
    else: 
      s3.create_bucket(Bucket=bucket_name, CreateBucketConfiguration={ 'LocationConstraint': my_region })
    print('S3 bucket created successfully')
except Exception as e:
    print('S3 error: ',e)
```
準備ができたらRUN。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/5c8234d4-aee9-adc2-45ea-a336bc669f53.png)

次に以下のコードを張り付けてRUN。サンプルデータをダウンロートしているのと、分析用に使えるようにPandasというライブラリのデータフレームにデータを入れています。`model_data`の部分です。
```python
try:
  urllib.request.urlretrieve ("https://d1.awsstatic.com/tmt/build-train-deploy-machine-learning-model-sagemaker/bank_clean.27f01fbbdf43271788427f3682996ae29ceca05d.csv", "bank_clean.csv")
  print('Success: downloaded bank_clean.csv.')
except Exception as e:
  print('Data load error: ',e)

try:
  model_data = pd.read_csv('./bank_clean.csv',index_col=0)
  print('Success: Data loaded into dataframe.')
except Exception as e:
    print('Data load error: ',e)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/b3e371b2-7574-423d-503c-4ce7537e7a19.png)

どんなデータかは以下のコードをRUNすれば最初の5行が見れます。
```python
model_data.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/c43bd1f6-4774-fe6c-b8e5-9c96d520cdfa.png)

次に以下のコードをRUNします。データをシャッフルした上で、トレーニング用のデータとテスト（検証）用のデータに7:3の割合で分割してます。
```python
train_data, test_data = np.split(model_data.sample(frac=1, random_state=1729), [int(0.7 * len(model_data))])
print(train_data.shape, test_data.shape)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/b0c5e4c3-dd7b-f8d6-dfdd-cb4bede08cfb.png)
ここまででデータの準備が一旦できました。

### MLモデルのトレーニング
引き続きnotebookに記述していきます。
以下のコードでは、準備したトレーニング用のデータに対して、形式を整えてS3バケットにcsvとしてアップロードしています。RUNします。
```python
pd.concat([train_data['y_yes'], train_data.drop(['y_no', 'y_yes'], axis=1)], axis=1).to_csv('train.csv', index=False, header=False)
boto3.Session().resource('s3').Bucket(bucket_name).Object(os.path.join(prefix, 'train/train.csv')).upload_file('train.csv')
s3_input_train = sagemaker.inputs.TrainingInput(s3_data='s3://{}/{}/train'.format(bucket_name, prefix), content_type='csv')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/0c7700e0-4a38-d0a5-cce8-c4d7d6d9e1ef.png)


次に、sagemakerのセッションを作成して、XGBoostモデル（機械学習の広く使われるモデル）用のインスタンスを作成します。
```python
sess = sagemaker.Session()
xgb = sagemaker.estimator.Estimator(xgboost_container,role, instance_count=1, instance_type='ml.m4.xlarge',output_path='s3://{}/{}/output'.format(bucket_name, prefix),sagemaker_session=sess)
xgb.set_hyperparameters(max_depth=5,eta=0.2,gamma=4,min_child_weight=6,subsample=0.8,silent=0,objective='binary:logistic',num_round=100)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/a36f13b3-6fa8-2208-063d-06aa5377d6f5.png)

トレーニングJobを開始します。以下のコードをRUN。
```python
xgb.fit({'train': s3_input_train})
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/38452378-2a38-07b6-8255-2a4b54ae6bf4.png)

ml.m4.xlarge instanceの上でトレーニングJobを実施しているのですが、思ったよりも待ちました。以下のように「Training secounds, Billable secounds」のログが出力されれば完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/8efc83d3-bc5d-be8b-cb42-c4aa1ed8fbfc.png)

### モデルのデプロイ
MLモデルのトレーニングが終わったあとは、そのモデルをデプロイします。この辺もAmazon Sagemakerですんなりとできちゃうのが便利ですね。
以下のコードを実行します。5分弱くらい待ちました。
```python
xgb_predictor = xgb.deploy(initial_instance_count=1,instance_type='ml.m4.xlarge')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/b7f92b04-3fd5-3ff7-53c8-35979461486b.png)

「------!」ログが確認できれば完了してそうです。（公式ドキュメントもそうだった）
これでモデルに対するエンドポイントが使えるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/ff50d231-0e95-23c2-b4e4-085e4ea024ff.png)

デプロイしたエンドポイントを使っていきます。
テスト用データセットから各顧客が商品に申し込んだか、申し込んでいないかどうかの予測を実施します。
```python
from sagemaker.serializers import CSVSerializer

test_data_array = test_data.drop(['y_no', 'y_yes'], axis=1).values #load the data into an array
xgb_predictor.serializer = CSVSerializer() # set the serializer type
predictions = xgb_predictor.predict(test_data_array).decode('utf-8') # predict!
predictions_array = np.fromstring(predictions[1:], sep=',') # and turn the prediction into an array
print(predictions_array.shape)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/a7f80148-cfd8-cef9-cf24-53cc73b17501.png)
結果は次のコードをRUNして見れます。

### モデルの評価
以下のコードをRUNして、作成しトレーニングしたモデルがどの程度の性能かを評価しています。

```python
cm = pd.crosstab(index=test_data['y_yes'], columns=np.round(predictions_array), rownames=['Observed'], colnames=['Predicted'])
tn = cm.iloc[0,0]; fn = cm.iloc[1,0]; tp = cm.iloc[1,1]; fp = cm.iloc[0,1]; p = (tp+tn)/(tp+tn+fp+fn)*100
print("\n{0:<20}{1:<4.1f}%\n".format("Overall Classification Rate: ", p))
print("{0:<15}{1:<15}{2:>8}".format("Predicted", "No Purchase", "Purchase"))
print("Observed")
print("{0:<15}{1:<2.0f}% ({2:<}){3:>6.0f}% ({4:<})".format("No Purchase", tn/(tn+fn)*100,tn, fp/(tp+fp)*100, fp))
print("{0:<16}{1:<1.0f}% ({2:<}){3:>7.0f}% ({4:<}) \n".format("Purchase", fn/(tn+fn)*100,fn, tp/(tp+fp)*100, tp))
```
テストデータ中の89.5％の顧客データに対して、申込みをしなかった場合は90%、申込みを行った場合は63%の精度で予測ができたと結果がでました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/4e61a616-911d-6de8-d687-addcc5cdddeb.png)

以上までが、SageMaker利用によるメインの部分です。以降はクリーンアップ作業となります。

### クリーンアップ
まずはSageMakerのエンドポイントを削除。
```python
xgb_predictor.delete_endpoint(delete_endpoint_config=True)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/1da841dd-5262-08f2-4585-9eeabc760c3f.png)

次にS3バケットの中身を削除。
以下のコードではS3バケット自体は削除していないので、残ってしまって気持ち悪い方はコンソールからS3バケットも削除してOKです。
```python
bucket_to_delete = boto3.resource('s3').Bucket(bucket_name)
bucket_to_delete.objects.all().delete()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/23525e97-2651-e0a9-bb8a-bf2d7b4ec639.png)

最後にNotebookインスタンスをストップして、削除。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/fce8d503-82fd-cc0d-58d5-5848efb1d1f6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/125f8e45-7331-9290-a7f0-c297887e8378.png)

ここまでが作業となります。

## おわりに
かなり簡単に開発環境の構築や、作成したモデルのデプロイまで実施できた印象です。機械学習のプロジェクトでは、開発環境の構築やメンバー間での環境の共有、アクセスの設定、デプロイ先の環境の構築など、データ分析作業以外にも負担になりそうな設定・構築が、Amazon Sagemakerを使うと大分簡略化できるだろうなと思いました。データ分析エンジニアにとってもハッピーな環境構築ができるのではないでしょうか！