Google Cloud Machine Learning EngineでUnity ML-Agentsを動かす

**本記事のUnity ML-Agentsバージョンは0.4.0となります。
バージョン0.5.0がリリースされましたが、一部コマンドやディレクトリ構成に変更があったため、バージョン0.5.0の場合、動作しない可能性があります。
絶賛記事更新作業中です(2018/09/13)**

## 概要

Unity ML-Agents(以下　ML-Agents)をクラウド上で動作させるのに適切な環境を探しています。
そのなかで、Google Cloud Machine Learning Engine(以下 Cloud ML)上で動作するものなのか、検証してみました。

ML-AgentsはTensorflowを利用した強化学習ができるライブラリです。
なので、Tensorflowなどの機械学習実行エンジンであるCloud MLで動かないわけがない。(はず)


### Google Cloud Machine Learning Engineとは

Google Cloud Machine Learning Engine は”エンジン”であって開発環境ではない ～制約に気を付けよう～
https://www.gixo.jp/blog/10960/

>ML Engine を一言で説明するなら「機械学習を実行するためのクラウドサービス」です。そのため、DataRobotなどの他のクラウドサービスのようにサービス上で開発は行えません。


### Unity ML-Agents

Unity Machine Learning Agentsベータ版
https://unity3d.com/jp/machine-learning

>Unity ML-Agentsは、新世代のロボット、ゲームをはじめさまざまな分野において、迅速かつ効率的に新しいAIアルゴリズムの開発を行い、テストする柔軟な方法を提供します。


## 手順

### 前提条件

Cloud MLやUnity、ML-Agentsの環境構築が済んでいる前提です。
まだ環境がないという方は下記をご参考ください。


MacでCloud Machine Learning Engineを利用してみる
https://qiita.com/kai_kou/items/0952c9b28efe19f2f680


Macでhomebrewを使ってUnityをインストールする(Unity Hub、日本語化対応)
https://qiita.com/kai_kou/items/445e614fb71f2204e033


MacでUnity ML-Agentsの環境を構築する
https://qiita.com/kai_kou/items/6478fa686ce1af5939d8


### Unityでアプリをビルドする

ML-Agentsでは実際にUnityアプリを動作させつつ強化学習がすすみます。
MacであればMac用、WindowsであればWindows用にアプリをビルドするわけですが、今回はCloud ML上で動作させる必要があるので、Linux用のビルドとなります。

アプリはML-Agentsに含まれる[3DBall]アプリを利用します。
手順は下記記事のDockerに関する手順以外をすすめてビルドします。

MacでUnity ML-AgentsをDockerで動作させる
https://qiita.com/kai_kou/items/df3f3518a5a27b66f617


```unity-volume``` フォルダに以下ファイルとフォルダが用意できたと思います。

```sh
> ll ml-agents/unity-volume/
3DBall.x86_64
3DBall_Data
```


### Cloud ML上で実行するための準備

Cloud MLで実行するための下準備です。

#### 変数設定

Cloud Storageのバケット名やCloud MLを利用するリージョンなどを変数に設定します。
```BUCKET_NAME```と```JOB_NAME```は任意で指定してください。

```sh:fish
> set -x BUCKET_NAME 任意のバケット名
> set -x REGION us-central1
> set -x JOB_NAME gcp_ml_cloud_run_01
```

```sh:bash
> export BUCKET_NAME=任意のバケット名
> export REGION=us-central1
> export JOB_NAME=gcp_ml_cloud_run_01
```

#### ハイパーパラメータの指定

今回は動作確認が目的となりますので、学習のステップを少なくします。
ML-Agentsライブラリに```trainer_config.yaml```というハイパーパラメータを指定するYAMLがあるので、そちらで```max_steps```の指定をして5,000ステップ実行して終了するようにします。

```sh
> cd 任意のディレクトリ/ml-agents

> vi python/trainer_config.yaml

Ball3DBrain:
    normalize: true
    batch_size: 64
    buffer_size: 12000
    summary_freq: 1000
    time_horizon: 1000
    lambd: 0.99
    gamma: 0.995
    beta: 0.001
+   max_steps: 5000
```

#### アプリとハイパーパラメータファイルのアップロード

Cloud MLでは学習に利用するファイルなどをCloud Storageに置いて利用することになります。
今回はUnityアプリのファイル・フォルダとハイパーパラメータ設定ファイルをCloud Storageにコピーしておきます。
また、Linux用ビルドはDataフォルダがないとアプリが起動しません。
フォルダのままだと取り回しが面倒なので、ZIPファイルに圧縮しておきます。


```sh
> cd 任意のディレクトリ/ml-agents

# Dataフォルダの圧縮
> zip -r 3DBall_Data.zip unity-volume/3DBall_Data/

# Cloud Storageのバケットにコピー

# Unityアプリファイル
> gsutil cp unity-volume/3DBall.x86_64 gs://$BUCKET_NAME/data/3DBall.x86_64

> gsutil cp unity-volume/3DBall_Data.zip gs://$BUCKET_NAME/data/3DBall_Data.zip

# ハイパーパラメータ設定ファイル
> gsutil cp python/trainer_config.yaml gs://$BUCKET_NAME/data/trainer_config.yaml
```

#### 学習実行ファイルの準備

ML-Agentsに含まれている```python/learn.py```が学習を開始するためのファイルになりますが、これをCloud MLで動作できるように以下の準備をします。

- Pythonのパッケージ化
- Cloud Storage連携実装追加


##### Pythonのパッケージ化

Cloud MLにPythonでパッケージ化されたファイルをアップして実行するので、その準備をします。

```sh
# Pythonフォルダのsetup.pyがCloud MLで動作するようにファイルを追加
> touch python/MANIFEST.in
> vi python/MANIFEST.in
+include requirements.txt

# learn.py実行用のパッケージ作成
> mkdir python/tasks
> touch python/tasks/__init__.py
> cp python/learn.py python/tasks/learn.py
```

##### learn.pyのカスタマイズ

```learn.py```にCloud Storageからファイルを取得、学習結果をCloud Storageへ保存する実装を追加します。
今回、ファイル名を固定していますが、とりあえず動作検証のためなので、あしからず。
Cloud Storageからのファイル取得・アップに```gsutil```コマンドを利用していますが、ここはPythonで実装してもOKです。
手抜きできるところは徹底的に^^

```sh
> vi python/tasks/learn.py
```

```python:python/tasks/learn.py
    # import追加
    import shlex
    import subprocess
    import zipfile    
    # import追加ここまで
    
    (略)

    # General parameters
    (略)
    fast_simulation = not bool(options['--slow'])
    no_graphics = options['--no-graphics']

    # ここから追加
    # Stackdriver Loggingでログ確認できるようにする
    logging.basicConfig(level=logging.INFO)
    
    # Cloud Storageのパス指定
    trainer_config_file = 'gs://任意のバケット名/data/trainer_config.yaml'
    app_file = 'gs://任意のバケット名/data/3DBall.x86_64'
    app_data_file = 'gs://任意のバケット名/data/3DBall_Data.zip'

    # 実行環境のに保存する際のパスを指定
    file_name = env_path.strip().replace('.x86_64', '')
    cwd = os.getcwd() + '/'
    app_data_local_dir = os.path.join(cwd, file_name + '_Data')
    app_local_file = os.path.join(cwd, file_name + '.x86_64')
    app_data_local_file = app_data_local_dir + '.zip'

    # Cloud Storageからコピー、Cloud ML環境だとgsutilが利用できて楽
    logger.info(subprocess.call(['gsutil', 'cp', trainer_config_file, cwd]))
    logger.info(subprocess.call(['gsutil', 'cp', app_file, cwd]))
    logger.info(subprocess.call(['gsutil', 'cp', app_data_file, cwd]))

    # Unityアプリ動作用にDataファイルを展開する
    with zipfile.ZipFile(app_data_local_file) as existing_zip:
        existing_zip.extractall(cwd)

    # Unityアプリが実行できるように権限を付与する
    logger.info(subprocess.call(shlex.split('chmod u+x ' + app_local_file)))
    # ここまで追加

    (略)

    tc.start_learning()

    # ここから追加
    # モデルファイルをCloud Storageへアップ
    # TODO: 圧縮する
    models_local_path = './models/{run_id}'.format(run_id=run_id)
    models_storage_path = 'gs://任意のバケット名/{run_id}/models'.format(run_id=run_id)
    logger.info(subprocess.call(['gsutil', 'cp', '-r', models_local_path, models_storage_path]))
    # ここまで追加
```

##### パッケージ作成

Cloud MLの操作に利用する```gcloud```コマンドにはパッケージを自動作成する機能もありますが、ここでは手動でパッケージングします。

```sh
> cd python
> python setup.py sdist
(略)
Writing unityagents-0.4.0/setup.cfg
Creating tar archive
removing 'unityagents-0.4.0' (and everything under it)

> ll dist
total 96
-rw-r--r--  1 user  users    45K  8  6 11:14 unityagents-0.4.0.tar.gz
```

### Cloud MLで実行してみる

さて、下準備が完了しましたので、Cloud MLへジョブ登録して学習を実行してみましょう。

パラメータ指定に```--```がありますが、これ以降のパラメータが```--module-name```で指定した```learn.py```にパラメータとして渡されるので、取ってはいけません。(1敗)

```sh
> cd 任意のディレクトリ/ml-agents
> gcloud ml-engine jobs submit training $JOB_NAME \
    --python-version=3.5 \
    --runtime-version 1.8 \
    --module-name tasks.learn \
    --packages python/dist/unityagents-0.4.0.tar.gz \
    --region $REGION \
    --staging-bucket gs://$BUCKET_NAME \
    -- \
    3DBall \
    --run-id $JOB_NAME \
    --train

Job [gcp_ml_cloud_run_01] submitted successfully.
Your job is still active. You may view the status of your job with the command

  $ gcloud ml-engine jobs describe gcp_ml_cloud_run_01

or continue streaming the logs with the command

  $ gcloud ml-engine jobs stream-logs gcp_ml_cloud_run_01
jobId: gcp_ml_cloud_run_01
state: QUEUED


Updates are available for some Cloud SDK components.  To install them,
please run:
  $ gcloud components update
```
はい。

ジョブが登録されたか確認しましょう。

```sh
> gcloud ml-engine jobs describe $JOB_NAME

createTime: '2018-08-06T02:20:53Z'
etag: sm-_r3BqJBo=
jobId: gcp_ml_cloud_run_01
startTime: '2018-08-06T02:21:24Z'
state: RUNNING
trainingInput:
  args:
  - 3DBall
  - --run-id
  - gcp_ml_cloud_run_01
  - --train
  packageUris:
  - gs://任意のバケット名/gcp_ml_cloud_run_01/xxxx/unityagents-0.4.0.tar.gz
  pythonModule: tasks.learn
  pythonVersion: '3.5'
  region: us-central1
  runtimeVersion: '1.8'
trainingOutput:
  consumedMLUnits: 0.01
```

ジョブ実行されたかログを見てみましょう。

```sh
> gcloud ml-engine jobs stream-logs $JOB_NAME

(略)
INFO    2018-08-06 11:22:50 +0900       master-replica-0                List of nodes to export :
INFO    2018-08-06 11:22:50 +0900       master-replica-0                        action
INFO    2018-08-06 11:22:50 +0900       master-replica-0                        value_estimate
INFO    2018-08-06 11:22:50 +0900       master-replica-0                        action_probs
INFO    2018-08-06 11:22:51 +0900       master-replica-0                Restoring parameters from ./models/gcp_ml_cloud_run_01/model-5001.cptk
INFO    2018-08-06 11:22:51 +0900       master-replica-0                Froze 16 variables.
(略)
INFO    2018-08-06 11:22:54 +0900       master-replica-0                Module completed; cleaning up.
INFO    2018-08-06 11:22:54 +0900       master-replica-0                Clean up finished.
INFO    2018-08-06 11:22:54 +0900       master-replica-0                Task completed successfully.
INFO    2018-08-06 11:27:32 +0900       service         Job completed successfully.
```
はい。

これで、ML-AgentsがCloud ML上で実行されて、```models```フォルダに結果ファイルが出力されているはずです。
```learn.py```で```models```フォルダをCloud Storageにコピーしてるので、フォルダをローカルにコピーして、Unityアプリにbytesファイルを組み込んで動作させることができます。


```sh
> gsutil ls gs://任意のバケット名/$JOB_NAME/models

gs://任意のバケット名/gcp_ml_cloud_run_01/models/3DBall_gcp_ml_cloud_run_01.bytes
gs://任意のバケット名/gcp_ml_cloud_run_01/models/checkpoint
gs://任意のバケット名/gcp_ml_cloud_run_01/models/model-5001.cptk.data-00000-of-00001
gs://任意のバケット名/gcp_ml_cloud_run_01/models/model-5001.cptk.index
gs://任意のバケット名/gcp_ml_cloud_run_01/models/model-5001.cptk.meta
gs://任意のバケット名/gcp_ml_cloud_run_01/models/raw_graph_def.pb
```

tensorboardでも結果が確認できます。

```sh
> gsutil cp -r gs://任意のバケット名/$JOB_NAME/models ローカルの任意のフォルダ/
> tensorboard --logdir=ローカルの任意のフォルダ/models
```

実行できることが確認できました。やったぜ^^

あとは、```learn.py```をもう少し実用的に改修したり、Cloud Pub/Subや、Cloud Functionsなどを利用すれば、Cloud StorageにUnityアプリをアップすれば、自動的に強化学習が実行される素敵環境が構築できそうです^^


## 参考

Google Cloud Machine Learning Engine は”エンジン”であって開発環境ではない ～制約に気を付けよう～
https://www.gixo.jp/blog/10960/

Unity Machine Learning Agentsベータ版
https://unity3d.com/jp/machine-learning

MacでCloud Machine Learning Engineを利用してみる
https://qiita.com/kai_kou/items/0952c9b28efe19f2f680

Macでhomebrewを使ってUnityをインストールする(Unity Hub、日本語化対応)
https://qiita.com/kai_kou/items/445e614fb71f2204e033

MacでUnity ML-Agentsの環境を構築する
https://qiita.com/kai_kou/items/6478fa686ce1af5939d8

MacでUnity ML-AgentsをDockerで動作させる
https://qiita.com/kai_kou/items/df3f3518a5a27b66f617

トレーニング アプリケーションのパッケージング
https://cloud.google.com/ml-engine/docs/tensorflow/packaging-trainer?hl=ja

【mac】zipファイル操作コマンド
https://qiita.com/griffin3104/items/948e38aab62bbb0d0610

Linuxの権限確認と変更（超初心者向け）
https://qiita.com/shisama/items/5f4c4fa768642aad9e06

17.5.1. subprocess モジュールを使う
https://docs.python.jp/3/library/subprocess.html

CloudMLからgoogle cloud storageのファイルにアクセスする
https://qiita.com/mikebird28/items/543581ef04476a76d3e0

Google Cloud ML Engine 上でPythonを実行する時のコツ
https://rooter.jp/ml/google-cloud-ml-engine-python-tips/

Pythonでzipファイルを圧縮・解凍するzipfile
https://note.nkmk.me/python-zipfile/

gsutilコマンド全部試したので解説する(part1)
https://www.apps-gcp.com/gsutil-command-explanation-part1/