Unity ML-Agentsの学習をGoogle Cloud Buildで実行してみた(v0.9.1対応)

Unity ML-Agentsの強化学習はDockerイメージで実行できます。
じゃあGoogle Cloud Buildで実行できるよね？ということを確認してみました。

Unity ML-Agentsのバージョンはv0.9.1となります。


## 前提

GCPプロジェクトや`gcloud` コマンドが利用できる前提です。
コマンドのインストール方法は下記が参考になります。

Cloud SDK のインストール  |  Cloud SDK のドキュメント  |  Google Cloud
https://cloud.google.com/sdk/downloads?hl=JA

Unity ML-Agentsの利用方法やDockerイメージの作成については下記をご参考ください。

MacでUnity ML-Agentsの環境を構築する(v0.9.1対応) - Qiita
https://qiita.com/kai_kou/items/268ccf6f961f8ca8cba8

DockerでUnity ML-Agentsを動作させる(v0.9.1対応) - Qiita
https://qiita.com/kai_kou/items/0c2f3c7d22363fd91e4e

Google Cloud Buildを利用してUnity ML-AgentsのDockerイメージを作成する(v0.9.1対応) - Qiita
https://qiita.com/kai_kou/items/5eabbaa2b564527e0b4e


## 設定ファイルの用意

`gcloud` コマンドからビルド実行する前提となります。

```console
> mkdir 任意のディレクトリ
> cd 任意のディレクトリ
> touch cloudbuild.yaml
```

```yaml:cloudbuild.yaml
steps:
- name: 'gcr.io/$PROJECT_ID/unity-ml-agents:0.9.1'
  args: ['trainer_config.yaml', '--env', '3DBall', '--train', '--run-id', '$BUILD_ID']
  id: 'mlagents-learn'
- name: 'gcr.io/cloud-builders/gsutil'
  entrypoint: 'bash'
  args: ['-c', 'while [ ! -f models/**/*.nn ]; do if [ -d summaries ]; then gsutil cp -r summaries gs://任意のバケット名; fi; sleep 10s; done']
  waitFor: ['-']
- name: 'gcr.io/cloud-builders/docker'
  entrypoint: 'bash'
  args: ['-c', 'if [ ! -f models/**/*.nn ]; then mkdir -p models/error; touch models/error/error.nn; fi']
  waitFor: ['mlagents-learn']
timeout: 3600s
artifacts:
  objects:
    location: 'gs://任意のバケット名/models/$BUILD_ID/'
    paths: [
      'models/**/*'
    ]
```

パラメータの詳細は下記が参考になります。

REST Resource: projects.builds
https://cloud.google.com/cloud-build/docs/api/reference/rest/v1/projects.builds


定義のポイントを説明します。

### Unity ML-Agentsの学習実行

Unity ML-Agentsで学習を開始するステップです。`name` でDockerイメージを指定しています。

```yaml:cloudbuild.yaml(mlagents-learn)
- name: 'gcr.io/$PROJECT_ID/unity-ml-agents:0.9.1'
  args: ['trainer_config.yaml', '--env', '3DBall', '--train', '--run-id', '$BUILD_ID']
  id: 'mlagents-learn'
```

Dockerイメージは事前に作成してGoogle Container Registryへアップする前提です。
作成方法については、下記を参考ください。

Google Cloud Buildを利用してUnity ML-AgentsのDockerイメージを作成する(v0.9.1対応) - Qiita
https://qiita.com/kai_kou/items/5eabbaa2b564527e0b4e

Unityアプリとハイパーパラメータファイルは、`gcloud` コマンド実行時にsourceとして指定します。
`id` は後ステップの`waitFor` で指定するために定義しています。

Dockerイメージで定義している`endpoint` が`mlagents-learn` となるため、`args` には`mlagents-learn` のパラメータを指定しています。
`--run-id` には`$BUILD_ID` を指定し、一意となるようにしています。
`$BUILD_ID` は`$PROJECT_ID` と同じようにビルド開始時に置換される変数となります。


### 途中経過ファイルをコピー

`gsutil` が利用できるイメージを指定して、学習中に`summaries` ディレクトリへ出力されるファイルを定期的にCloud Storageへコピーするステップです。
`waitFor` に`-` を指定するとで、前ステップと同時に並行して実行されます。
`entrypoint` に`bash` を指定して、`args` に`-c` オプションを利用してコマンドを書いてます。

```yaml:cloudbuild.yaml(gsutil)
steps:
- name: 'gcr.io/cloud-builders/gsutil'
  entrypoint: 'bash'
  args: ['-c', 'while [ ! -f models/**/*.nn ]; do date; if [ -d summaries ]; then gsutil cp -r summaries gs://任意のバケット名; fi; sleep 10s; done']
  waitFor: ['-']
```

コマンドを整形してみます。

```console
while [ ! -f models/**/*.nn ]; do
    if [ -d summaries ];
        then gsutil cp -r summaries gs://任意のバケット名;
    fi
    sleep 10s;
    done
```

学習が完了するとモデルが`models/**/*.nn` ファイルとして作成されます。
なので、モデル作成=学習完了するまで、の間、`summaries` フォルダのファイルを定期的にCloud Storageへコピーするコマンドを実行します。
ただ、このままだと学習実行時のエラーなどで、`models/**/*.nn` ファイルが作成されないとステップが終了しないため、次の終了処理のステップで対応しています。


### 終了処理

こちらもbashコマンドを利用しています。
`waitFor` を`mlagents-learn` とすることで、学習実行のステップが終わったら実行されるようにしています。


```yaml:cloudbuild.yaml
- name: 'gcr.io/cloud-builders/docker'
  entrypoint: 'bash'
  args: ['-c', 'if [ ! -f models/**/*.nn ]; then mkdir -p models/error; touch models/error/error.nn; fi']
  waitFor: ['mlagents-learn']
```

コマンドを整形してみてみると、`models/**/*.nn` が存在しなければ、ダミーファイルを作成するコマンドになっています。これで、学習実行がエラーなどで、モデルファイルを作成しない場合もジョブが終了するようになります。

```console
if [ ! -f models/**/*.nn ]; then
    mkdir -p models/error;
    touch models/error/error.nn;
fi
```


### 結果ファイルのコピー

全ステップ完了後、`paths` で指定したファイルをCloud Storageへコピーする定義になります。

```yaml:cloudbuild.yaml(artifacts)
artifacts:
  objects:
    location: 'gs://任意のバケット名/models/$BUILD_ID/'
    paths: [
      'models/**/*'
    ]
```

### タイムアウト設定

Unity ML-Agentsで学習実行する場合、長時間となる可能性があります。
Cloud Buildではステップごと、ジョブ全体としてタイムアウト時間を指定することができます。
ジョブ全体のタイムアウトは指定しないとデフォルトで10分となりますので、定義しています。

```yaml:cloudbuild.yaml(timeout)
timeout: 3600s
```

タイムアウトについての詳細は下記が参考になります。

ビルド構成の概要  |  Cloud Build  |  Google Cloud
https://cloud.google.com/cloud-build/docs/build-config?hl=ja


## ジョブ実行

実際にCloud Buildでジョブ実行させてみます。


### 学習するアプリの準備

学習するアプリは下記記事で利用したML-Agentsに含まれる`3DBall` を利用します。
すでにDockerコンテナで実行できるビルドファイルがある前提です。詳細は下記をご参考ください。

DockerでUnity ML-Agentsを動作させる(v0.9.1対応) - Qiita
https://qiita.com/kai_kou/items/0c2f3c7d22363fd91e4e


学習に必要となるビルドファイルとハイパーパラメータファイルをZIPファイルに圧縮します。
圧縮ファイルはジョブ実行時にワークスペースへコピーされ勝手に展開されます。

```console
# ビルドファイルとハイパーパラメータファイルを圧縮
> cd 3DBallのビルドファイルがあるディレクトリ
> zip -r 3DBall_App.zip 3DBall.x86_64 3DBall_Data trainer_config.yaml

# Cloud Storageへコピー
> gsutil cp 3DBall_app.zip gs://任意のバケット名/3DBall_app.zip

> cd 任意のディレクトリ
> gcloud builds submit \
  --config=cloudbuild.yaml \
  gs://任意のバケット名/3DBall_app.zip

Created [https://cloudbuild.googleapis.com/v1/projects/GCPのプロジェクトID/builds/69ebb6e2-280f-4577-a8b5-f83b95929d8e].
Logs are available at [https://console.cloud.google.com/gcr/builds/69ebb6e2-280f-4577-a8b5-f83b95929d8e?project=xxxxxxxxxxxx].
(略)
Step #0 - "mlagents-learn":     beta:   0.001
Step #0 - "mlagents-learn":     buffer_size:    12000
Step #0 - "mlagents-learn":     epsilon:        0.2
Step #0 - "mlagents-learn":     hidden_units:   128
Step #0 - "mlagents-learn":     lambd:  0.99
Step #0 - "mlagents-learn":     learning_rate:  0.0003
Step #0 - "mlagents-learn":     max_steps:      5.0e4
Step #0 - "mlagents-learn":     memory_size:    256
Step #0 - "mlagents-learn":     normalize:      True
Step #0 - "mlagents-learn":     num_epoch:      3
Step #0 - "mlagents-learn":     num_layers:     2
Step #0 - "mlagents-learn":     time_horizon:   1000
Step #0 - "mlagents-learn":     sequence_length:        64
Step #0 - "mlagents-learn":     summary_freq:   1000
Step #0 - "mlagents-learn":     use_recurrent:  False
Step #0 - "mlagents-learn":     vis_encode_type:        simple
Step #0 - "mlagents-learn":     reward_signals:
Step #0 - "mlagents-learn":       extrinsic:
Step #0 - "mlagents-learn":         strength:   1.0
Step #0 - "mlagents-learn":         gamma:      0.99
Step #0 - "mlagents-learn":     summary_path:   ./summaries/69ebb6e2-280f-4577-a8b5-f83b95929d8e-0_3DBallLearning
Step #0 - "mlagents-learn":     model_path:     ./models/69ebb6e2-280f-4577-a8b5-f83b95929d8e-0/3DBallLearning
Step #0 - "mlagents-learn":     keep_checkpoints:       5
Step #0 - "mlagents-learn": Setting up 1 worker threads for Enlighten.
Step #0 - "mlagents-learn":   Thread -> id: 7f2f44c61700 -> priority: 1
Step #1: Copying file://summaries/69ebb6e2-280f-4577-a8b5-f83b95929d8e-0_3DBallLearning/events.out.tfevents.1566292921.6e8e6580f319 [Content-Type=application/octet-stream]...
/ [1 files][  678.0 B/  678.0 B]
Step #1: Operation completed over 1 objects/678.0 B.
Step #0 - "mlagents-learn": INFO:mlagents.trainers: 69ebb6e2-280f-4577-a8b5-f83b95929d8e-0: 3DBallLearning: Step: 1000. Time Elapsed: 11.002 s Mean Reward: 1.149. Std of Reward: 0.612. Training.
Step #1: Copying file://summaries/69ebb6e2-280f-4577-a8b5-f83b95929d8e-0_3DBallLearning/events.out.tfevents.1566292921.6e8e6580f319 [Content-Type=application/octet-stream]...
/ [1 files][  933.0 B/  933.0 B]
Step #1: Operation completed over 1 objects/933.0 B.
Step #0 - "mlagents-learn": INFO:mlagents.trainers: 69ebb6e2-280f-4577-a8b5-f83b95929d8e-0: 3DBallLearning: Step: 2000. Time Elapsed: 22.591 s Mean Reward: 1.247. Std of Reward: 0.692. Training.
Step #1: Copying file://summaries/69ebb6e2-280f-4577-a8b5-f83b95929d8e-0_3DBallLearning/events.out.tfevents.1566292921.6e8e6580f319 [Content-Type=application/octet-stream]...
/ [1 files][  1.2 KiB/  1.2 KiB]
Step #1: Operation completed over 1 objects/1.2 KiB.
(略)
Finished Step #2
Finished Step #1
PUSH
Artifacts will be uploaded to gs://任意のバケット名 using gsutil cp
models/**/*: Uploading path....
Omitting directory "file://models/24894139-cf12-4f3d-a8ee-1748a5606e51-0/3DBallLearning". (Did you mean to do cp -r?)
Copying file://models/24894139-cf12-4f3d-a8ee-1748a5606e51-0/3DBallLearning.nn [Content-Type=application/octet-stream]...
/ [1/1 files][ 74.8 KiB/ 74.8 KiB] 100% Done
Operation completed over 1 objects/74.8 KiB.
models/**/*: 1 matching files uploaded
1 total artifacts uploaded to gs://任意のバケット名/models/24894139-cf12-4f3d-a8ee-1748a5606e51/
Uploading manifest artifacts-24894139-cf12-4f3d-a8ee-1748a5606e51.json
Artifact manifest located at gs://任意のバケット名/models/24894139-cf12-4f3d-a8ee-1748a5606e51/artifacts-24894139-cf12-4f3d-a8ee-1748a5606e51.json
DONE
------------------------------------------------------------------------------------------------------------------------------

ID                                    CREATE_TIME                DURATION  SOURCE                                                                                        IMAGES  STATUS
24894139-cf12-4f3d-a8ee-1748a5606e51  2019-08-20T09:46:48+00:00  11M49S    gs://GCPのプロジェクトID_cloudbuild/source/1566294405.28-c9f8bcd12b5246cc8e1dc8b04e0534f9.zip  -       SUCCESS
```


Cloud Functionsからもビルド実行可能ですので、Cloud Storageにzipファイルがコピーされたらジョブを登録・実行して、学習させることもこれで可能になります。

Google Cloud Functions(Python)からCloud Buildでビルド実行してみる
https://qiita.com/kai_kou/items/8f040e261c4a50ef8bfc


## まとめ

Dockerを利用して動作させることができると、システム構成に選択の幅が広がって良いですね。メンテナンス性やコスト、想定されるスケールなどを考慮して、適切な選択ができそうです。


## 参考

Cloud SDK のインストール  |  Cloud SDK のドキュメント  |  Google Cloud
https://cloud.google.com/sdk/downloads?hl=JA

REST Resource: projects.builds
https://cloud.google.com/cloud-build/docs/api/reference/rest/v1/projects.builds

ビルド構成の概要  |  Cloud Build  |  Google Cloud
https://cloud.google.com/cloud-build/docs/build-config?hl=ja

Google Cloud Functions(Python)からCloud Buildでビルド実行してみる
https://qiita.com/kai_kou/items/8f040e261c4a50ef8bfc

MacでUnity ML-Agentsの環境を構築する(v0.9.1対応) - Qiita
https://qiita.com/kai_kou/items/268ccf6f961f8ca8cba8

DockerでUnity ML-Agentsを動作させる(v0.9.1対応) - Qiita
https://qiita.com/kai_kou/items/0c2f3c7d22363fd91e4e

Google Cloud Buildを利用してUnity ML-AgentsのDockerイメージを作成する(v0.9.1対応) - Qiita
https://qiita.com/kai_kou/items/5eabbaa2b564527e0b4e