Google Cloud BuildでUnity ML-Agentsを動かしてみる(v0.5.0対応)

## 追記

v0.9.1で動作させる方法をまとめました。

Unity ML-Agentsの学習をGoogle Cloud Buildで実行してみた(v0.9.1対応) - Qiita
https://qiita.com/kai_kou/items/eec215a3927b172f19e2


## はじめに

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


## 前提

GCPプロジェクトや```gcloud``` コマンドが利用できる前提です。

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

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

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

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

## 設定ファイルの用意

```gcloud``` コマンドからビルド実行する前提の定義となります。
Unity ML-Agentsで強化学習すると```models``` と```summaries``` フォルダに学習結果や学習過程の情報が出力されます。
Dockerコンテナで実行しているとそれらのファイルが参照しにくいので、Cloud Storageへコピーするコマンドを定義しています。

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

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


```yaml:cloudbuild.yaml
steps:
- name: 'gcr.io/$PROJECT_ID/unity_ml_agents:0.5.0a'
  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/**/*.bytes ]; 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/**/*.bytes ]; then mkdir -p models/error; touch models/error/error.bytes; fi']
  waitFor: ['mlagents-learn']
artifacts:
  objects:
    location: 'gs://任意のバケット名/models/$BUILD_ID/'
    paths: [
      'models/**/*'
    ]
```

それぞれ分けて説明します。

### Unity ML-Agents実行

Unity ML-Agentsで強化学習を開始するステップです。
```name``` でDockerイメージを指定しています。Dockerイメージ作成については、下記を参考ください。

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


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

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

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

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

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

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

コマンドを整形してみます。
学習完了時に```models/**/*.bytes``` ファイルが作成されるので、作成されるまで、```summaries``` フォルダのファイルをCloud Storageへコピーするだけのコマンドになります。
ただ、このままだと、学習実行時にエラーなどで、```models/**/*.bytes``` ファイルが作成されないと終了しないため、次のコマンドで対応しています。

```sh
while [ ! -f models/**/*.bytes ]; do
    if [ -d summaries ];
        then gsutil cp -r summaries gs://任意のバケット名;
    fi
```

### 終了処理

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


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

コマンドを整形して見てみると、```models/**/*.bytes``` が存在しなければ、ダミーファイルを作成する、というコマンドです。
これで、前ステップが強化学習でエラーが発生しても終了するようにしています。

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

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

全ステップが完了後、```paths``` で指定したファイルをCloud Storageへコピーする定義になります。
こちらは```bash``` コマンドで定義してもよい感じです。

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

### ビルド実行

用意した定義ファイルで、Cloud Buildでジョブ実行させてみます。
unityアプリ.zipはUnityアプリファイル・フォルダとハイパーパラメータファイルを含む圧縮ファイルを指定しています。
圧縮ファイルの場合、ビルド実行時にワークスペースへコピー・展開を勝手に行ってくれます。

```sh
> gcloud builds submit --config=cloudbuild.yaml gs://任意のバケット名/unityアプリ.zip
```

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

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


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


## 参考

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

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

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

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

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