Amazon Sagemaker ~trainingからCompileまで~

## 概要

* AWSのSagemakerを使ってモデルをトレーニングし、Compileまでやってみる！

## はじめに
AWSのSagemakerを勉強した機会があったので、せっかくだからまとめたいなと記事に起こしました。
意外とCompileまで説明している人は少なかったので、AWS IoT GreenGrassを使う人とかの参考になればなと思います。

## 実験環境
* Amazon SageMaker Notebooks
* Tensorflow Keras

## Sagemakerとは？
Sagemakerとは何なのかということは他のサイトで詳しく解説されていますので、具体的なことは割愛して、
簡単に言うと、**機械学習の諸々の処理をクラウド上で行うためのツール**みたいなものです。
例えば、アノテーション、モデルの学習推論、コンパイルなどをSagemaker上だけで行うことができます。
本記事では、モデルの学習～コンパイルまでをメインに扱っていきます。

> Sagemakerについて
>* [Amazon SageMakerとは何なのか、使い方について。](https://www.acrovision.jp/service/aws/?p=1237)
>* [Amazon SageMakerの概要とメリット](https://www.skyarch.net/column/amazon-sagemaker/)

今回はSagemakerにあるGround Truthには触れないので参考記事の方を以下に載せておきます。

>* [Amazon SageMaker Ground Truth を試してみる](https://qiita.com/SatoshiGachiFujimoto/items/e4ab624d3527237ca9e9)
>* [[Amazon SageMaker] Ground Truthで物体検出用のデータセットを作成してみました](https://dev.classmethod.jp/articles/amazon-sagemaker-ground-truth-create-dataset/)

↓　Sagemakerのタブ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654472/6af98a2d-741a-1413-eddb-883e16431819.png)

## Sagemaker Notebook
今回使用していくのが、SagemakerのNotebookになります。このNotebookは、ほぼJupyter NotebookとLabみたいなものです。
Notebookインスタンスを起動するときに、NotebookかLabのどちらかで起動することができます。

インスタンスの作成方法はそんなに難しくないと思うので、参考までに以下の記事を載せておきます。

>* [[初心者向け]Amazon SageMakerではじめる機械学習 #SageMaker](https://dev.classmethod.jp/articles/getting-started-with-amazon-sagemaker/)
>* [[初心者向け] はじめてのSageMaker みんな大好きアイリスデータを使って組み込みアルゴリズムで分類してみる](https://dev.classmethod.jp/articles/first-sagemaker-iris-linearlearner/)

少し補足したいことがあったので補足すると、ロールを選択する箇所で「新しいロールを作成」であったり、「既存のロールを使用」という選択肢があります。
一人で、Sagemakerを使う場合は上記のサイトの通りにやるとすんなりできると思いますが、AWSのホストが自分以外のユーザーにある場合には、そのホストの人に

1.  AmazonSageMakerFullAccess 
1.  AmazonS3FullAccess

の最低限２つはIAMのアクセス権限に追加してもらい、そのIAMを使用すると、うまくいくと思います。

### ちょっとSagemaker Studioについて
Sagemaker Notebookとは別に、Notebookを拡張したSagemaker StudioがSagemakerにはあります。
Notebookインスタスでは一度起動してしまうと、Notebook自体のインスタンス(ml.p2.xlargeとか)を変更するためには、一度インスタンスを終了して変更し、再び起動しなければいけませんでした。
しかし、Studioであればインスタンスタイプを起動中も変更できるます。また、推論に使用するエンドポイントがNotebook上で見れたり、学習済みモデルのインポートが、ボタンをポチポチするだけでできたりします。
であれば、Studioを使えばいいのではと思われるかもしれませんが、「なんか動作が重かった」という理由故に、Notebookを使うことにしました笑。
今は、改善されているかもしれませんので、こっちの方が断然使いやすいじゃんという人は、Sagemaker Studioを使ってみてください。

>Sagemaker Studioについて
>* [Amazon SageMaker NotebooksがGAになったので使ってみました](https://dev.classmethod.jp/articles/mrmo-sagemaker-20200430/)

### Sagemakerのコンテナについて
Sagemakerはtrainingをするときには大きく分けて2つの方法があるのかなと思います。
1. 裏でtensorflowやpytorchのコンテナが起動し、そのコンテナ上で学習。
2. ノートブック上で学習

2の場合だと学習に使われるインスタンスが起動したノートブックのインスタンスに依存してしまうので、個人的には1の方がいいのかなと思います。
今回も1の方法で学習を行っているのですが、実はコンテナの中にはそんなにライブラリがインストールされてはいません。

**tensorflowのコンテナの中にあるライブラリ**
|Dependencies|Scipt Mode|Legacy Mode|
|:--|:--|:--|
|boto3|Latest|Latest|
|botocore|Latest|Latest|
|CUDA (GPU image only)|9.0|9.0|
|numpy|Latest|Latest|
|Pillow|Latest|Latest|
|scipy|Latest|Latest|
|sklean|Latest|Latest|
|h5py|Latest|Latest|
|pip|18.1|18.1|
|curl|Latest|Latest|
|tensorflow|1.12|1.12|
|tensorflow-serving-api|1.12|None|
|sagemaker-containers|>=2.3.5|>=2.3.5|
|sagemaker-tensorflow-container|1.0|1.0|
|Python|2.7 or 3.6|2.7|

> [SageMaker TensorFlow Docker containers](https://sagemaker.readthedocs.io/en/v1.33.0/using_tf.html#id8)

そのため、コンテナの中にインストールされていないライブラリをつかいたい場合は、随時インストールする必要があります。
インストール方法としてはこちらも2つあります。
1. Trainingのパラメータの中にあるdependenciesにライブラリのフォルダを指定する
1. Training用の.pyファイルの先頭にコードを書き込む

1の方はうまくいくときと行かないときがあったので、自分は2のほうがいいのかなと思います。また、1の方はノートブック内にインストールされるので、容量を食ってしまいます。

**1の方法**
ライブラリをインストールする用のディレクトリを作り、そこに使用したいライブラリをインストール
```.bash
python3 -m pip install matplotlib --target ./module
```

その後、dependenciesにパスを設定

> 参考サイト：[SageMakerでTensorFlow+Kerasによる独自モデルをトレーニングする方法](https://qiita.com/hideki/items/0098c83b5d8d7fcf9fb2#dependencies)

**2の方法**
学習用のpythonファイルの先頭に`subprocess`を使ってライブラリをインストールするためのコードを書き込む
```.py
import subprocess
import sys
# 必要なmoduleをインストール
def install(package):
    subprocess.check_call([sys.executable, "-q", "-m", "pip", "install", package])
install('tables==3.5.2')
install('matplotlib')
```

### Train.pyの注意点
モデルの学習に入る前に一つだけ注意点があります。
それは、モデルの保存方法に関することなのですが、trainの.pyファイルのモデル保存で適切に保存されるコードを書いてないと、エンドポイントの作成などは行うことができるが、Compileが行えないといった事態が発生してしまいます。

Kerasでモデルを保存するコードは

```.py
model_save_path = os.path.join(args.model_dir, str(db_number))
os.makedirs(model_save_path)
model.save(model_save_path, save_format='tf')

# Freeze saved model
input_node_names = [inp.name.split(":")[0] for inp in model.inputs]
output_node_names = [output.name.split(":")[0] for output in model.outputs]
print("Input names: ", input_node_names)
with tf.Session() as sess:
    loaded = tf.saved_model.load(sess, export_dir=model_save_path, tags=["serve"]) 
    frozen_graph = tf.graph_util.convert_variables_to_constants(sess,
                                                                sess.graph.as_graph_def(),
                                                                output_node_names)
    tf.io.write_graph(graph_or_graph_def=frozen_graph, logdir=".", name="frozen_graph.pb", as_text=False)
```

のようにする必要があります。

>* [Saving Models for SageMaker Neo](https://docs.aws.amazon.com/sagemaker/latest/dg/neo-compilation-preparing-model.html)

### 学習(Training)
それではモデルの学習に入っていきましょう！
今回はkerasを使用しているので、tensorflow kerasを使っています。

まずは今回使用するライブラリのインポート

```.py
import os
import keras
import numpy as np
import sagemaker
from sagemaker.tensorflow import TensorFlow
from sagemaker import get_execution_role
```

**使用するロールとS3バケットの設定**
Sagemakerでは基本的にトレーニングに使用するデータや、モデルの結果をS3から取り出したり保存したりするので、そのS3のバケットの指定を行います。

```.py
role = get_execution_role()
print(role)
sess = sagemaker.Session(default_bucket="s3のバケット")
bucket = sess.default_bucket()
print(bucket)
```

**トレーニングに使用するパラメータの設定**
今回のトレーニングではSagemakerに元から用意されているTensorflowのコンテナと、Script modeというものを使用しています。
Script modeを使わない場合は、Sagemakerのお作法に沿って学習用のスクリプトを記述する必要があります。([お作法の紹介サイト](https://dev.classmethod.jp/articles/sagemaker-tensorflow-iris/))
しかし、Script modeを使用するだけでいつも通りのコードの書き方で書くことができます。
パラメータの[補足](#学習のパラメータについて)は後でします。

```.py
s3_train_data = "s3://path/to/train/file"
s3_output = "s3://path/to/output"
s3_output_checkpoint = "s3://path/to/checkpoint"
print("s3_train_data : ", s3_train_data)
print("s3_output : ", s3_output)
print("s3_output_checkpoint : ", s3_output_checkpoint)

epochs = 70
db = "test"

hyper_param = {
    "batch_size"       : 128,
    "nb_epochs"        : epochs,
    "db"               : db,
    "validation_split" : 0.2,
    "checkpoint"       : s3_output_checkpoint,
}

estimator = TensorFlow(entry_point='train.py',
                       source_dir='source',
                       role=role,
                       framework_version='1.15',
                       hyperparameters=hyper_param,
                       instance_count=1,
                       instance_type='ml.p2.xlarge',
                       script_mode=True,
                       checkpoint_s3_uri = s3_output_checkpoint,
                       base_job_name="job",
                       use_spot_instances=True,
                       max_run=30000,
                       max_wait=30000,
                       output_path=s3_output,
                       py_version='py3')

```

学習用のパラメータ設定が終わったので学習を回していきます。
* `training`はデータセットがあるバケットのパス
* `wait`はTrueにすればノートブック上にもログが出力され、Falseにすればログが出力されなくなります。
* `logs`はAWSにあるCloudWatchにログを出力するか否かを設定します。Trueであれば出力され、Falseであれば出力はされなくなります。

```.py
estimator.fit({'training':s3_train_data}, wait=False, logs=True)
```

ログの確認は
　　![aws1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654472/54ab2721-5ba3-7fe4-600a-9af038263f42.png)

トレーニングジョブのところから、確認したいトレーニングをクリックして、
![aws3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654472/4526f0c8-fe34-9dd3-0e45-86bc7a40b38d.png)

「ログを表示」のところで確認することができます。

また、バケットからファイルをダウンロードしたい場合は、以下のようにしてダウンロードすることができます。
学習のヒストリーとかを見たい場合に使うのかなと思います。

```.py
import boto3

job_name = estimator_age.latest_training_job.name
s3 = boto3.resource('s3')
bucket=s3.Bucket('test')

bucket.download_file(os.path.join("output/"+job_name,"output/output.tar.gz"),"output.tar.gz")
```

#### 学習のパラメータについて
学習のパラメータについて少し補足をしていきます。

##### entry_pointとsource_dir
`entry_point`は学習用のコードが書かれているファイルのパス、`source_dir`は学習用のコードと、その他使用するコードがあるフォルダを指定します。
train.pyの中に別ファイルの関数をimportしたい場合は、`source_dir`で指定したフォルダの中に忘れずにファイルを入れておくようにしましょう。

##### hyperparameters
学習用のファイル、今回であれば`train.py`で使うことができる変数を定義することができます。
ここで定義した変数はコマンドラインのライブラリである`argparse`で使うことができます。
使い方の例として、以下にコードを書いておきます。

```.py
import argparse
def get_args():
    parser = argparse.ArgumentParser(description="This script trains the CNN model for age and gender estimation.",
                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("--input", "-i", type=str, default=os.environ.get('SM_CHANNEL_TRAINING'),
                        help="path to input database npz file")
    parser.add_argument("--db", type=str, default="imdb",
                        help="database name(megaage, imdb, wiki)")
    parser.add_argument("--batch_size", type=int, default=128,
                        help="batch size")
    parser.add_argument("--nb_epochs", type=int, default=90,
                        help="number of epochs")
    parser.add_argument("--validation_split", type=float, default=0.2,
                        help="validation split ratio")
    parser.add_argument("--checkpoint", type=str, required=True, 
                        help="model checkpoint dir path")
    parser.add_argument("--model-dir", type=str, default=os.environ.get('SM_MODEL_DIR'),
                        help="model save dir")

    args, _ = parser.parse_known_args()
    return args

def main():
    
    args = get_args()
    input_path = args.input
    db_name = args.db
    batch_size = args.batch_size
    nb_epochs = args.nb_epochs
    validation_split = args.validation_split
    checkpoint_dir = args.checkpoint

# 学習用のスクリプト
```

##### instance_type
学習を実行するインスタンスのタイプを選択します。
予算に合ったGPU or CPUのインスタンスを選ぶようにしましょう。

##### use_spot_instances
これを`True`にすることで、学習の時間は伸びてしまいますが、料金を節約することができます。

> 参考サイト：[【備忘録】Sagemakerの学習にスポットインスタンスを使う【超カンタン】](https://dev.classmethod.jp/articles/how-to-training-with-spot-instance/)

### 推論(Inference)
学習が終わったあとに、そのモデルで推論を行うことができます。
推論を行うためには、Sagemakerではエンドポイントというものを作成する必要があります。このへんはググればたくさんサイトが出てくると思うので、自分の記事か他のサイトの記事か好きなものを参考にしてみてください。

エンドポイントを作成する方法はSagemakerのメニューからGUIで作成するか、ノートブック上でスクリプトを書くかの2つの方法があります。

メニューの場合はここから↓
![aws4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654472/89e2601e-9e18-bfe4-766a-562fef4063eb.png)

個人的には、スクリプトで書いたほうが流れで作業が出来かつわかりやすいので、スクリプトで書くことをおすすめします。

**training後にそのモデルを使って推論をする場合**

```.py
detector = estimator.deploy(initial_instance_count=1, instance_type="ml.m4.xlarge")
```

**別のtraining結果のモデルで推論をする場合**

```.py
from sagemaker.tensorflow.model import TensorFlowModel

input_model_path = "s3://path/to/own/output.tar.gz"

# Create a TensorFlow SageMaker model
tensorflow_model = TensorFlowModel(model_data = input_model_path, role = role, framework_version='1.15', source_dir='source', entry_point = 'train.py')

detector = tensorflow_model.deploy(initial_instance_count=1, instance_type="ml.m4.xlarge")
```

**endopointのモデルで予測**
以下のコードを推論をするスクリプトの中に記載することで、推論ができます。

```.py
model = detector
results = model.predict(input)
```

推論をする必要がなくなったら、endpointの削除は絶対にする必要があります。
削除しない場合はお金がかかるので要注意です。

```.py
sagemaker.Session().delete_endpoint(detector.endpoint_name)
```

endpointが削除されたかどうか確認したい場合は、Sagemakerのメニューのエンドポイントのところで確認可能です。
![aws5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654472/a50a7c65-95b6-e461-5bbc-00af9b6f9df5.png)

### コンパイル(Compile)
それでは最後に、Sagemakerで学習をしたモデルをコンパイルしていきます。
このコンパイルという過程は、AWSのGreengrassというIoTのサービスを使う上で必要になってくると思います。
ちなみにGreengrassとは

>AWS IoT Greengrass は、クラウドの機能をローカルデバイスに拡張するソフトウェアです。これにより、デバイスは情報源に近いデータを収集および分析して、ローカルイベントに自律的に反応し、ローカルネットワークで互いに安全に通信することができます。ローカルデバイスもAWS IoT Coreにエクスポートし、IoT データをAWS クラウド。AWS IoT Greengrass開発者はAWS Lambda関数と事前ビルドコネクタを使用して、ローカルで実行するためにデバイスにデプロイされるサーバーレスアプリケーションを作成します。

引用：[AWS IoT Greengrass とは](https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/what-is-gg.html)

簡単に言うと「jetsonなどのエッジデバイスとAWSを繋ぐ架け橋」みたいなものです。
と、Greengrassの話はここまでにして、コンパイルに入っていきましょう。

コンパイルもエンドポイント作成と同様に、Sagemakerのメニューあるいはスクリプトで行うことができます。
今回はスクリプトの紹介をしていこうと思います。というのも、メニューはスクリプトをそのまま入れればいいだけなので笑
ちなみに、このコンパイルはSagemaker Neoによるコンパイルというものになります。

>Sagemaker Neoについて
>* [Amazon SageMaker Neoで画像分類モデルを最適化してみる – Amazon SageMaker Advent Calendar 2018](https://dev.classmethod.jp/articles/2018advent-calendar-sagemaker-20181208/)

コードは以下のようになります。
パラメータの設定方法はリファレンスに記載してあります。

>* [Reference](https://sagemaker.readthedocs.io/en/stable/api/inference/model.html#sagemaker.model.Model.compile)
>* [input_shapeについて](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_InputConfig.html)

**training後にそのモデルを使って推論をする場合**

```.py
compile_output = "s3://{}/{}".format(bucket, "output_compile")
print(compile_output)

compile_mode = estimator.compile_model(
    target_instance_family="jetson_nano",
    input_shape={"input_1": [1, 64, 64, 3]},
    output_path=compile_output,
    role=role,
    framework="tensorflow",
    compile_max_run=5000,
    framework_version='1.15'
)
```

**別のtraining結果のモデルで推論をする場合**

```.py
from sagemaker.tensorflow.model import TensorFlowModel

input_model_path = "s3://path/to/own/output.tar.gz"

# Create a TensorFlow SageMaker model
tensorflow_model = TensorFlowModel(model_data = input_model_path,role = role, framework_version='1.15', source_dir='source', entry_point = 'train.py')

job_name = "compile"
compile_output = "s3://{}/{}".format(bucket, "output_compile")

tensorflow_model.compile(
    target_instance_family="jetson_nano",
    input_shape={"input_1": [1, 64, 64, 3]},
    output_path=compile_output,
    role=role,
    job_name=job_name,
    compile_max_run=5000,
    framework="tensorflow"
)
```

コンパイルの成功・失敗はSagemakerの推論メニューで確認できます。

コンパイルが終わったら、あとは煮るなり焼くなり好きにしてもらえればと思います。


## 終わりに
今回は、AWSのSagemakerを使ってTrainingからCompileまでの手順を紹介させていただきました。
学習用のファイルのスクリプトなどは省きましたが、普段どおりの学習のスクリプトを書いてもらえれば大丈夫です。<font color="gray">(というのも、学習用のファイルのスクリプトを書いてくれてる人はネットにたくさんいるので笑)</font>
今後、時間があればjetsonなどでGreengrassを使ってデプロイしてみたいなと考えています。