KerasのMNIST CNNでSageMakerの基本を理解する

# はじめに
- SageMakerは、モデルの構築、トレーニング、デプロイの仕組みを提供しています。
- 今回は、KerasのMNIST CNNを最小限の修正で実行し、SageMakerの基本を理解します。
- よって、モデルの構築とトレーニングの処理にフォーカスします。
- デプロイ、モデルチューニングや分散深層学習等は、別の機会に確認します。
- 参考
  - [Amazon SageMaker で簡単に Keras を使う方法](https://aws.amazon.com/jp/blogs/news/amazon-sagemaker-keras/)
  - [Amazon SageMaker Python SDK](https://sagemaker.readthedocs.io/en/stable/index.html)


# 概要

![SageMaker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/e271aeb3-330e-fd57-35d8-6da28b16be5e.png)


# 詳細

## ノートブックインスタンスの作成
- いずれかからノートブックインスタンスの作成を開始します。
![1_create_notebook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/4aa29dbc-3813-b646-52c7-991ee7fa3b9c.png)

- ノートブックインスタンス名は、`keras-mnist-cnn`等のお好みの名前で作成します。
- ノートブックインスタンスのタイプは、`ml.t2.medium`にします。一番安いです。今回はこれで十分です。
- IAMロールは、新しいロールの作成を選びます。
![2_create_notebook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/e4da80ae-918f-3c0b-bd75-b1d192f38151.png)

- 指定するS3バケット - オプションは、なしにします。
- 今回は、自動作成されるS3バケット名に「sagemaker」が含まれるので問題ないです。
![3_create_notebook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/d2cf00e8-4459-cba6-0048-cdaffb515e3e.png)

- IAMロールは、`AmazonSageMaker-ExecutionRole-20190711T221753`で作成されました。これは、作成時間が自動的に含まれます。
![4_create_notebook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/5c939e52-5ba0-c668-4ffe-67f5c464a7e0.png)

- 今回の作業がすべて終わったら、該当のIAMロールとIAMポリシーを削除して構いません。
![5_iam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/2f49c142-dd2d-c93e-4588-19acdf1fd068.png)
![6_iam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/739e8144-34ec-0f75-b4e4-90cadc7ffc72.png)

- しばらくすると、ノートブックインスタンスが `InService`になります。では、`Open Jupyter`でノートブックインスタンスを開きましょう。
![7_create_notebook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/baa5b4b1-7b0b-4cbd-5e71-ed8bda2c1c55.png)
![8_notebook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/10454652-6a35-856a-2165-087f67f0b39b.png)


## mnist_cnn.py
- Text Fileで`mnist_cnn.py`を作成します。
![9_mnist_cnn_py.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/9645ff05-f46d-31f1-ea55-6f63186820ad.png)

- `https://raw.githubusercontent.com/keras-team/keras/master/examples/mnist_cnn.py`の内容をペーストします。
ファイル名は、`mnist_cnn.py`にします。
![10_mnist_cnn_py.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/c8f7d997-8766-ccb9-3a00-93d7556cd51b.png)

- SageMaker用に作成するS3からMNISTデータをダウンロードする為の修正です。

```
from __future__ import print_function
import os
import pickle
import keras
#from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
```

- S3からpickle化したMNISTデータをダウンロードして、x_train、y_train、x_testとy_testを作成します。
- `SM_CHANNEL_TRAINING`のデフォルトは、`/opt/ml/input/data/training`です。こちらに、S3からデータがダウンロードされます。

```
# the data, split between train and test sets
#(x_train, y_train), (x_test, y_test) = mnist.load_data()
fin = open(os.path.join(os.environ['SM_CHANNEL_TRAINING'], 'mnist.pickle'), 'rb')
(x_train, y_train), (x_test, y_test) = pickle.load(fin)
```

- 最後にトレーニング結果の`model.h5`を保存します。
- `SM_OUTPUT_DATA_DIR`のデフォルトは、`/opt/ml/output/data`です。こちらから、S3へデータがアップロードされます。

```
print('Test loss:', score[0])
print('Test accuracy:', score[1])

model.save(os.path.join(os.environ['SM_OUTPUT_DATA_DIR'], 'model.h5'))
```

## mnist_cnn.ipynb
- `mnist_cnn.py`を実行するための`mnist_cnn.ipynb`を作成します。
- `conda_tensorflow_p36`で作成します。
- 先ほど作成した `mnist_cnn.py`もありますね。
![11_mnist_cnn_ipynb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/2754423b-7dd2-b626-6412-e6db58c2c198.png)

- ノートブックの名前を`mnist_cnn`へ修正してください。
- 念の為、`conda_tensorflow_p36`なっているか確認してください。
![12_mnist_cnn_ipynb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/3b8ac18d-37b8-914a-af5f-3bfdbcbfb723.png)

- ノートブックにデータ保存用のフォルダを作成し、MNISTデータを公式S3からダウンロードし、pickle化して保存を行います。
![10_jupyter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/90e83398-e95b-5037-6a94-70a2c283f5b2.png)
![13_data.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/273d4ac4-ee5b-868c-3feb-430091bb0de8.png)
![14_data.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/20a7012b-333b-192a-8adb-83eb81114909.png)

- SageMakerのSessionを作成し、データをS3へアップロードします。
- pathは、上記で作成した`data`を指定します。
- bucket_nameは、default_bucketを使います。慣れてきたら、IAMロール、IAMポリシーを作成した箇所で、任意のバケットを指定してください。
- default_bucketは`sagemaker-ap-northeast-1-アカウントID`の様になります。
- key_prefixは、上記で作成した`data`と同じにした方が、混乱しないでしょう。
- inputsは `s3://sagemaker-ap-northeast-1-アカウントID/data`の様になります。
![15_upload.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/aba21c14-1bb6-ad9d-a82a-fbefa63d8e67.png)

- S3にバケットが出来て、データがアップロードされます。
![16_upload.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/3e8714ae-e880-4410-109f-e4a36f8db041.png)

- IAMロールを取得します。
- roleは、`arn:aws:iam::アカウントID:role/service-role/AmazonSageMaker-ExecutionRole-20190707T224950`でした。
- entory_pointに `mnist_cnn.py`を指定します。
- train_instance_countは `1` にします。
- train_instance_typeは `ml.p2.xlarge` にします。もし、GPUインスタンスが使えない場合は、申請するか、申請不要の `ml.m5.xlarge`等にします。
- framework_versionは `1.12.0` にします。もっと新しいものもありますが、試していません。
- py_versionは `py3` にします。
- 最後に `tf_estimator.fit(inputs)` でトレーニングが開始されます！
![17_fit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/87b84d8c-0ffc-f7da-dc0c-9bc0b8a161fe.png)

- トレーニング開始のメッセージです。
- 開始から、GPUインスタンス起動まで4分弱かかりました。
- S3からGPUインスタンスへ`mnist_cnn.pickle`と`mnist_cnn.py`のダウンロードが行われます。
- 最後に、`framework_version`と`py_version`で指定したコンテナがGPUインスタンスで起動します。

```
2019-07-11 14:41:42 Starting - Starting the training job...
2019-07-11 14:41:43 Starting - Launching requested ML instances......
2019-07-11 14:42:54 Starting - Preparing the instances for training.........
2019-07-11 14:44:29 Downloading - Downloading input data...
2019-07-11 14:45:02 Training - Downloading the training image..
2019-07-11 14:45:24,413 sagemaker-containers INFO     Imported framework sagemaker_tensorflow_container.training
2019-07-11 14:45:24,809 sagemaker-containers INFO     Invoking user script
```

- 下記のメッセージは、トレーニング環境の様々な情報です。
- 解説はしませんが、`job_name`の`sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562`は覚えておいてください。

```
Training Env:

{
    "additional_framework_parameters": {},
    "channel_input_dirs": {
        "training": "/opt/ml/input/data/training"
    },
    "current_host": "algo-1",
    "framework_module": "sagemaker_tensorflow_container.training:main",
    "hosts": [
        "algo-1"
    ],
    "hyperparameters": {
        "model_dir": "s3://sagemaker-ap-northeast-1-アカウントID/sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562/model"
    },
    "input_config_dir": "/opt/ml/input/config",
    "input_data_config": {
        "training": {
            "TrainingInputMode": "File",
            "S3DistributionType": "FullyReplicated",
            "RecordWrapperType": "None"
        }
    },
    "input_dir": "/opt/ml/input",
    "is_master": true,
    "job_name": "sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562",
    "log_level": 20,
    "master_hostname": "algo-1",
    "model_dir": "/opt/ml/model",
    "module_dir": "s3://sagemaker-ap-northeast-1-アカウントID/sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562/source/sourcedir.tar.gz",
    "module_name": "mnist_cnn",
    "network_interface_name": "eth0",
    "num_cpus": 4,
    "num_gpus": 1,
    "output_data_dir": "/opt/ml/output/data",
    "output_dir": "/opt/ml/output",
    "output_intermediate_dir": "/opt/ml/output/intermediate",
    "resource_config": {
        "current_host": "algo-1",
        "hosts": [
            "algo-1"
        ],
        "network_interface_name": "eth0"
    },
    "user_entry_point": "mnist_cnn.py"
}
```

- 下記のメッセージは、上記の環境変数です。`mnist_cnn.py`内で取得することが出来ます。
- 今回は、`SM_CHANNEL_TRAINING`と`SM_OUTPUT_DATA_DIR`を利用していることを、上記の`mnist_cnn.py`で解説しました。

```
Environment variables:

SM_HOSTS=["algo-1"]
SM_NETWORK_INTERFACE_NAME=eth0
SM_HPS={"model_dir":"s3://sagemaker-ap-northeast-1-アカウントID/sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562/model"}
SM_USER_ENTRY_POINT=mnist_cnn.py
SM_FRAMEWORK_PARAMS={}
SM_RESOURCE_CONFIG={"current_host":"algo-1","hosts":["algo-1"],"network_interface_name":"eth0"}
SM_INPUT_DATA_CONFIG={"training":{"RecordWrapperType":"None","S3DistributionType":"FullyReplicated","TrainingInputMode":"File"}}
SM_OUTPUT_DATA_DIR=/opt/ml/output/data
SM_CHANNELS=["training"]
SM_CURRENT_HOST=algo-1
SM_MODULE_NAME=mnist_cnn
SM_LOG_LEVEL=20
SM_FRAMEWORK_MODULE=sagemaker_tensorflow_container.training:main
SM_INPUT_DIR=/opt/ml/input
SM_INPUT_CONFIG_DIR=/opt/ml/input/config
SM_OUTPUT_DIR=/opt/ml/output
SM_NUM_CPUS=4
SM_NUM_GPUS=1
SM_MODEL_DIR=/opt/ml/model
SM_MODULE_DIR=s3://sagemaker-ap-northeast-1-アカウントID
/sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562/source/sourcedir.tar.gz
SM_TRAINING_ENV={"additional_framework_parameters":{},"channel_input_dirs":{"training":"/opt/ml/input/data/training"},"current_host":"algo-1","framework_module":"sagemaker_tensorflow_container.training:main","hosts":["algo-1"],"hyperparameters":{"model_dir":"s3://sagemaker-ap-northeast-1-アカウントID/sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562/model"},"input_config_dir":"/opt/ml/input/config","input_data_config":{"training":{"RecordWrapperType":"None","S3DistributionType":"FullyReplicated","TrainingInputMode":"File"}},"input_dir":"/opt/ml/input","is_master":true,"job_name":"sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562","log_level":20,"master_hostname":"algo-1","model_dir":"/opt/ml/model","module_dir":"s3://sagemaker-ap-northeast-1-アカウントID/sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562/source/sourcedir.tar.gz","module_name":"mnist_cnn","network_interface_name":"eth0","num_cpus":4,"num_gpus":1,"output_data_dir":"/opt/ml/output/data","output_dir":"/opt/ml/output","output_intermediate_dir":"/opt/ml/output/intermediate","resource_config":{"current_host":"algo-1","hosts":["algo-1"],"network_interface_name":"eth0"},"user_entry_point":"mnist_cnn.py"}
SM_USER_ARGS=["--model_dir","s3://sagemaker-ap-northeast-1-アカウントID/sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562/model"]
SM_OUTPUT_INTERMEDIATE_DIR=/opt/ml/output/intermediate
SM_CHANNEL_TRAINING=/opt/ml/input/data/training
SM_HP_MODEL_DIR=s3://sagemaker-ap-northeast-1-アカウントID/sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562/model
PYTHONPATH=/opt/ml/code:/usr/local/bin:/usr/lib/python36.zip:/usr/lib/python3.6:/usr/lib/python3.6/lib-dynload:/usr/local/lib/python3.6/dist-packages:/usr/lib/python3/dist-packages
```

- いよいよ、`mnist_cnn.py`が実行されます。pythonで`mnist_cnn.py`が引数付きで実行されてますね。
- 引数は、`tf_estimator`のハイパーパラメータで自由に指定できます。
- 別の機会に実施したいと考えているので、今回はシンプルを重視し、省きました。

```
Invoking script with the following command:

/usr/bin/python mnist_cnn.py --model_dir s3://sagemaker-ap-northeast-1-アカウントID/sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562/model
```

- あとは、おなじみのトレーニングメッセージになります。

```
Using TensorFlow backend.
x_train shape: (60000, 28, 28, 1)
60000 train samples
10000 test samples
Train on 60000 samples, validate on 10000 samples
Epoch 1/12

2019-07-11 14:45:21 Training - Training image download completed. Training in progress.  128/60000 [..............................] - ETA: 31:50 - loss: 2.3156 - acc: 0.0547
  640/60000 [..............................] - ETA: 6:24 - loss: 2.0624 - acc: 0.2797 
 1152/60000 [..............................] - ETA: 3:35 - loss: 1.8726 - acc: 0.3568
省略
59776/60000 [============================>.] - ETA: 0s - loss: 0.0270 - acc: 0.9917
60000/60000 [==============================] - 8s 129us/step - loss: 0.0270 - acc: 0.9917 - val_loss: 0.0285 - val_acc: 0.9913
Test loss: 0.0284580502319081
Test accuracy: 0.9913
```

- 最後に、課金時間のメッセージが出ます。今回は、164秒でした。東京のp2.xlargeだと、8円くらいでしょうか？
- また、WARNINGは、TensorFlowのSavedModelが保存されていない件になります。
- SavedModelで保存すると、推論APIのデプロイなどで利用することができます。
- 今回はKerasのMNIST CNNでSageMakerのトレーニングまで確認し、基本を理解することを主眼としています。

```
2019-07-11 14:47:05,472 sagemaker_tensorflow_container.training WARNING  No model artifact is saved under path /opt/ml/model. Your training job will not save any model files to S3.
For details of how to construct your training script see:
https://github.com/aws/sagemaker-python-sdk/tree/master/src/sagemaker/tensorflow#adapting-your-local-tensorflow-script
2019-07-11 14:47:05,473 sagemaker-containers INFO     Reporting training SUCCESS
Billable seconds: 164
```


- S3には、`job_name`の`sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562`が作成されています。
- デフォルトでは、各ジョブを区別するために、日時等を加えて自動的に決定されます。もちろん、事前に指定することもできます。

![18_s3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/80c55952-c9aa-ca47-80f6-a179e05101bc.png)


- そして、`sagemaker-tensorflow-scriptmode-2019-07-11-14-41-39-562`の`output`に`output.tar.gz`が保存されます。
- この中に、`model.h5`が保存されています。

![19_output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/4cb688fc-054e-6691-0e3b-cf9f11010c73.png)


- 下記で取り出して、ノートブックに保存しましょう。
- tf_estimatorからjob_nameを取得できます。
- 今回は `sagemaker-tensorflow-scriptmode-2019-07-10-14-00-37-039` ですね。
- そして、boto3を使ってS3から`output.tar.gz`をダウンロードして、tarファイルを解凍します。
- kerasのload_modelで読み込み完了です!

![20_notebook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/52699285-c3ea-3b41-9ac2-d01189c0313e.png)


- 最終的なノートブックのファイルの状態です。

![21_notebook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/266846d4-08d2-ebba-f6f9-92259163e911.png)



# おわりに
- SageMakerでKerasのMNIST CNNを最小限の修正で実行しました。
- `ml.p2.xlarge` を使うことで、**約8円**でトレーニングが出来ます。トレーニング時間は、3分弱でした。
- ただし、GPUインスタンスの起動に4分弱かかります。この辺りは、作業によって許容出来る場合と出来ない場合はあると思います。
- 今後は、PyTorch、KerasとHorovodなどを利用して、分散深層学習に取り組みたいを考えています。
- 今回のファイルは https://github.com/maedamikio/public/tree/master/sagemaker_keras においておきます。