【完結編】AWS Amazon SageMaker つかってみた4 〜GPU利用でサンプルの実行〜

# はじめに
Amazon SageMaker を利用する上で自分が気になったところ、詰まったところを中心にまとめていきます。
この記事では前回の記事で失敗したサンプルの実行について再度まとめます。
前回の記事は[こちら](https://qiita.com/okazy/items/e5b44a56cb745c75018a)
次回の記事は[作成中](#)

# サンプルの場所
Jupyterの導入については[前回の記事](https://qiita.com/okazy/items/beac79862303b287b8f2)を参照

1. Jupyterのホーム画面を開きます。
2. `sample-notebooks` ディレクトリを開きます。
3. `sagemaker-python-sdk` ディレクトリを開きます。
4. `mxnet_gluon_cifar10` ディレクトリを開きます。
5. `cifar10.ipynb` ファイルを開きます。

# サンプルの実行
Jupyter NotebookにはPythonのコードと説明が含まれます。
右に `In [ ]:` と書かれているのがコードで、それ以外は説明です。

## サンプルについて

- 用いるデータセット: CIFAR-10という6万枚の画像データのデータセット
- 学習内容: 34層の深層学習により画像を10種類にクラスタリングする。

### セットアップ
`scikit-image` のライブラリが必要なのでcondaのページから入れてください。

1. `conda tab` のリンクをクリックするか、Jupyterのホームから `Conda` のタブをクリックします。
2. `Available packages` のところのSearchで `scikit-image` を検索します。
3. `scikit-image` を選択します。
4. 右矢印を押下してインストールします。

はじめのpythonコードを実行してみましょう。
以下のコードを選択して、画面上部の `Cell` から `Run Cells` を選択します。

```python3
import os
import boto3
import sagemaker
from sagemaker.mxnet import MXNet
from mxnet import gluon
from sagemaker import get_execution_role

sagemaker_session = sagemaker.Session()

role = get_execution_role()
```

右の表示が `In [*]:` に変わります。
`*` は実行中であることを表します。
実行が終わると `In [1]:` のような表示になります。
数字は実行された順番を表します。

初めて実行すると以下のWarningが表示されます。
こちらの原因と解消方法を知っている人がいれば教えてください。

```
/home/ec2-user/anaconda3/envs/mxnet_p27/lib/python2.7/site-packages/urllib3/contrib/pyopenssl.py:46: DeprecationWarning: OpenSSL.rand is deprecated - you should use os.urandom instead
  import OpenSSL.SSL
```

もう一度実行するとWarningが消えます。

## データのダウンロード
CIFAR10をダウンロードします。

```python3
from cifar10_utils import download_training_data
download_training_data()
```

少し時間がかかります。

## データのアップロード
Amazon SageMakerで学習させるにはまずS3に学習データをアップロードする必要があります。
S3へデータをアップロードするには `sagemaker.Session.upload_data` の関数を使います。
戻り値の `inputs` にはアップロード先のIDが入っており、後ほど学習の際に利用します。

```python3
inputs = sagemaker_session.upload_data(path='data', key_prefix='data/gluon-cifar10')
print('input spec (in this case, just an S3 path): {}'.format(inputs))
```

## `train` 関数の確認

SageMakerで利用可能な学習スクリプトを用意する必要があります。
学習スクリプトは `train` 関数として実装されている必要があります。
学習環境の情報が `train` 関数の引数として渡されます。

```python3
!cat 'cifar10.py'
```

## 学習スクリプトの実行

`MXNet` クラスを用いると分散環境での学習が可能です。
以下の設定をします。

- 学習スクリプト
- IAMロール
- インスタンス数
- インスタンスタイプ

デフォルトでは２個の `ml.p2.xlarge` インスタンスで学習ができるように設定されています。
３行目の `train_instance_count` の値を `1` に変更して１個のインスタンスで学習するように変更してください。（理由は[考察](#%E8%80%83%E5%AF%9F)の項目を参照）
※GPUインスタンスはデフォルトで使用制限があります。制限解除方法については[こちら](https://qiita.com/okazy/items/b32e552cf36c3c815810)の記事にまとめています。

```python3
m = MXNet("cifar10.py", 
          role=role, 
          train_instance_count=1, 
          train_instance_type="ml.p2.xlarge",
          hyperparameters={'batch_size': 128, 
                           'epochs': 50, 
                           'learning_rate': 0.1, 
                           'momentum': 0.9})
```

`MXNet` クラスができましたので、アップロードしたデータを使って学習を開始します。

```python3
m.fit(inputs)
```

学習がスタートしました。
現在実行中の学習はAmazon SageMakerのジョブの画面で確認できます。
学習時間は44分でした。 `ml.p2.xlarge` が1時間\$1.26ですので学習にかかった費用は\$1ほどです。

## 予測

学習が終わりましたので学習結果を使ってエンドポイントを作成します。
学習ではGPUインスタンスを利用しましたが、エンドポイントではCPUのインスタンスも利用できます。

deployの戻り値のpredictorオブジェクトでエンドポイントを呼び出してサンプル画像の予測ができます。

```python3
predictor = m.deploy(initial_instance_count=1, instance_type='ml.m4.xlarge')
```

## CIFAR10のサンプル画像

サンプル画像でCIFAR10のテストをします。

```python3
# load the CIFAR10 samples, and convert them into format we can use with the prediction endpoint
from cifar10_utils import read_images

filenames = ['images/airplane1.png',
             'images/automobile1.png',
             'images/bird1.png',
             'images/cat1.png',
             'images/deer1.png',
             'images/dog1.png',
             'images/frog1.png',
             'images/horse1.png',
             'images/ship1.png',
             'images/truck1.png']

image_data = read_images(filenames)
```

predictorは入力された画像を予測して結果のラベルを返します。
ラベルはfloat値で返ってくるのでintに変換してから表示しています。

```python3
for i, img in enumerate(image_data):
    response = predictor.predict(img)
    print('image {}: class: {}'.format(i, int(response)))
```

## リソースの開放

このサンプルの実行が終わったらインスタンスを開放してエンドポイントを削除するのをお忘れなく。

```python3
sagemaker.Session().delete_endpoint(predictor.endpoint)
```

# 結果

正解率80%

```
image 0: class: 0
image 1: class: 9
image 2: class: 2
image 3: class: 3
image 4: class: 4
image 5: class: 5
image 6: class: 2
image 7: class: 7
image 8: class: 8
image 9: class: 9
```

# 考察

## GPUの圧倒的な処理速度

GPUが得意な画像処理ということもあり60倍程度の処理速度が出ていた。

| インスタンスタイプ | 1秒間に処理できるサンプル数 |
|:---------------:|----------------------:|
| ml.m4.xlarge | 約 22 サンプル/秒 |
| ml.p2.xlarge | 約 1400 サンプル/秒 |

単位時間あたりはCPUよりGPUの方が高いが、処理速度が段違いなので結果的にGPUを使うほうが安上がりになる。

## 並列処理の同期のオーバヘッドが大きい

MXNetでは１回の学習ごとに各インスタンス間で同期を取っているようです。
単体のインスタンスで学習をさせたところ１秒間に11回の学習ができたのに対し、２並列で学習させた場合は毎秒各インスタンス１回ずつの合計２回しか学習ができませんでした。
分散学習をさせると性能が1/5に低下してしまいます。

| インスタンスタイプ | 1秒間に学習できる回数 |
|:---------------:|----------------------:|
| 単体（同期なし） | 約 11 回/秒 |
| ２並列（毎回同期） | 約 2 回/秒 |

今回の学習では１回の学習に要する時間が0.1秒以下の学習であったために毎回同期を取らないといけない並列処理のほうが遅くなる結果となってしまった。

# SageMakerのいいと感じたところ

## ワンストップで実行可能

ノートブックの作成、リソースの確保、学習、エンドポイントの作成までワンストップで行える。

## リソースの最適化

ノートブック、学習、エンドポイントでそれぞれでインスタンスを選択できる。
学習は学習している間だけ料金が発生する。