AWS CodeBuildでUnity ML-Agentsを動作させる(v0.9.1対応)

Unity ML-Agents(v0.9.1)の学習をAWS CodeBuildを利用して実行してみました。


UnityやUnity ML-Agentsの環境構築、Dockerを用いた学習方法などは下記をご参考ください。

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

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


## 前提

下記記事を参考にUnity ML-Agents(v0.9.1)のDockerイメージをAmazon Elastic Container Registry(ECR)にPushしている前提です。

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


## 手順

### AWS CloudFormation(CFn)で環境構築する

CFnを利用して、必要となるリソースを作成します。

- InputBucket: S3バケット。Unityアプリのビルドファイル置き場
- OutputBucket: S3バケット。アーティファクト(modelsなど)をアップロード
- CodeBuildServiceRole: CodeBuildで利用するサービスロール
- CodeBuildProject: CodeBuildのプロジェクト


Dockerイメージのビルドで作成したテンプレートとほぼ同じです。


```yaml:template.yaml
---
AWSTemplateFormatVersion: 2010-09-09
Description: Learning Unity ML-Agents

Parameters:
  ProjectName:
    Description: Project Name for CodeBuild
    Default: ml-agents-learning-test
    Type: String
  InputBucketName:
    Description: Input Bucket Name
    Default: ml-agents-learning-input
    Type: String
  OutputBucketName:
    Description: Output Bucket Name
    Default: ml-agents-learning-output
    Type: String

Resources:
  InputBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${InputBucketName}
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

  OutputBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${OutputBucketName}
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codebuild.amazonaws.com
            Action:
                - sts:AssumeRole
      Policies:
        - PolicyName: !Sub CodeBuildPolicy-${ProjectName}
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Resource:
                  - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectName}
                  - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectName}:*
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
              - Effect: Allow
                Resource:
                  - !Sub arn:aws:s3:::codepipeline-${AWS::Region}-*
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
              - Effect: Allow
                Resource:
                  - !Sub arn:aws:s3:::${InputBucketName}/*
                Action:
                  - s3:GetObject
                  - s3:GetObjectVersion
              - Effect: Allow
                Resource:
                  - !Sub arn:aws:s3:::${OutputBucketName}/*
                Action:
                  - s3:PutObject

  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Ref ProjectName
      Description: this is a test prj
      ServiceRole: !Ref CodeBuildServiceRole
      Artifacts:
        Location: !Ref OutputBucketName
        Type: S3
        Name: artifacts
        Path: !Sub ${ProjectName}
        NamespaceType: BUILD_ID
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/standard:2.0
        PrivilegedMode: True
      Source:
        Location: !Join [ "/", [ !Ref InputBucketName, "dummy.zip" ] ]
        Type: S3
      TimeoutInMinutes: 120
      Tags:
        - Key: Name
          Value: !Ref ProjectName
```

テンプレートが用意できたらAWS CLIでスタック作成します。サービスロールを作成するので`--capabilities CAPABILITY_IAM` が必要になります。
`--stack-name` や`--region` はお好みで。`--parameters` を指定するとバケット名やプロジェクト名も指定できます。

```console
> cd テンプレートがあるディレクトリ
> aws cloudformation create-stack \
  --stack-name ml-agents-learning \
  --template-body file://template.yaml \
  --capabilities CAPABILITY_IAM \
  --region <YOUR REGION> \
  --parameters '[
      {
        "ParameterKey": "ProjectName",
        "ParameterValue": "ml-agents-learning-test"
      },
      {
        "ParameterKey": "InputBucketName",
        "ParameterValue": "ml-agents-learning-input"
      },
      {
        "ParameterKey": "OutputBucketName",
        "ParameterValue": "ml-agents-learning-output"
      }
  ]'

```

スタック実行が完了したか確認します。`"CREATE_COMPLETE"` になったらOK。

```console
> aws cloudformation describe-stacks \
  --region <YOUR REGION> \
  --stack-name ml-agents-learning \
  --query "Stacks[0].StackStatus"

"CREATE_COMPLETE"
```

### CodeBuildのビルド実行準備

作成したCodeBuildのプロジェクトでビルド実行するための前準備をします。


#### S3バケットにUnityアプリのビルドファイルをアップロードする

ML-Agentsで学習するUnityアプリのビルド方法は下記を参考にしてください。

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

手元に3DBallのビルドファイルがある前提で。

```console
> ls
3dball.x86_64       3dball_Data         trainer_config.yaml

> zip -r 3dball.zip 3dball.x86_64 3dball_Data trainer_config.yaml


> aws s3 cp 3dball.zip s3://ml-agents-learning-input/
upload: ./3dball.zip to s3://ml-agents-learning-input/3dball.zip
```

#### buildspec.yamlを用意する

CodeBuildのプロジェクトでビルド実行する際の手順を記述した`buildspec.yaml` を用意します。
`mlagents-learn` コマンドで学習実行して、終わったら`summaries` と`models` フォルダを`artifacts` としてOutputのバケットにアップロードします。

```yaml:buildspec.yaml
version: 0.2

phases:
  install:
    runtime-versions:
      docker: 18
  build:
    commands:
      - mlagents-learn trainer_config.yaml --train --env=3dball
artifacts:
  files:
    - ./summaries/**/*
    - ./models/**/*
```

### ビルド実行する

`buildspec.yaml` が用意できたらCodeBuildのプロジェクトでビルド実行します。
`--image-override` でDockerイメージを上書き、`--source-location-override` でUnityアプリのビルドファイルを指定します。ZipファイルはCodeBuildで自動的に展開されます。

```console
> aws codebuild start-build \
  --region <YOUR REGION> \
  --project-name ml-agents-learning-test \
  --buildspec-override file://buildspec.yaml \
  --image-override <YOUR AWS ACCOUNT ID>.dkr.ecr.<YOUR REGION>.amazonaws.com/ml-agents:0.9.1 \
  --source-location-override ml-agents-learning-input/3dball.zip
```

学習実行している様子。
<img width="1236" alt="スクリーンショット 2019-10-24 13.08.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48549/399fdee5-9b60-dd2b-9a84-1f8285dcf56d.png">


`aws codebuild batch-get-builds` コマンドでビルドが完了したか確認します。各フェーズのステータスが`SUCCEEDED` で最後に`"phaseType": "COMPLETED"` があったらビルド完了です。

```console
aws codebuild batch-get-builds \
  --region <YOUR REGION> \
  --ids ml-agents-learning-test:4f28bf27-194b-4c3d-974c-b553897f290c \
  --query "builds[0].phases"

[
    {
        "phaseType": "SUBMITTED",
        "phaseStatus": "SUCCEEDED",
        "startTime": 1571890350.391,
        "endTime": 1571890350.693,
        "durationInSeconds": 0
    },
    {
        "phaseType": "QUEUED",
        "phaseStatus": "SUCCEEDED",
        "startTime": 1571890350.693,
        "endTime": 1571890351.886,
        "durationInSeconds": 1
    },
    {
        "phaseType": "PROVISIONING",
        "phaseStatus": "SUCCEEDED",
        "startTime": 1571890351.886,
        "endTime": 1571890405.483,
        "durationInSeconds": 53,
        "contexts": [
            {
                "statusCode": "",
                "message": ""
            }
        ]
    },
    {
        "phaseType": "DOWNLOAD_SOURCE",
        "phaseStatus": "SUCCEEDED",
        "startTime": 1571890405.483,
        "endTime": 1571890406.992,
        "durationInSeconds": 1,
        "contexts": [
            {
                "statusCode": "",
                "message": ""
            }
        ]
    },
    {
        "phaseType": "INSTALL",
        "phaseStatus": "SUCCEEDED",
        "startTime": 1571890406.992,
        "endTime": 1571890407.158,
        "durationInSeconds": 0,
        "contexts": [
            {
                "statusCode": "",
                "message": ""
            }
        ]
    },
    {
        "phaseType": "PRE_BUILD",
        "phaseStatus": "SUCCEEDED",
        "startTime": 1571890407.158,
        "endTime": 1571890407.354,
        "durationInSeconds": 0,
        "contexts": [
            {
                "statusCode": "",
                "message": ""
            }
        ]
    },
    {
        "phaseType": "BUILD",
        "phaseStatus": "SUCCEEDED",
        "startTime": 1571890407.354,
        "endTime": 1571890828.698,
        "durationInSeconds": 421,
        "contexts": [
            {
                "statusCode": "",
                "message": ""
            }
        ]
    },
    {
        "phaseType": "POST_BUILD",
        "phaseStatus": "SUCCEEDED",
        "startTime": 1571890828.698,
        "endTime": 1571890828.834,
        "durationInSeconds": 0,
        "contexts": [
            {
                "statusCode": "",
                "message": ""
            }
        ]
    },
    {
        "phaseType": "UPLOAD_ARTIFACTS",
        "phaseStatus": "SUCCEEDED",
        "startTime": 1571890828.834,
        "endTime": 1571890830.138,
        "durationInSeconds": 1,
        "contexts": [
            {
                "statusCode": "",
                "message": ""
            }
        ]
    },
    {
        "phaseType": "FINALIZING",
        "phaseStatus": "SUCCEEDED",
        "startTime": 1571890830.138,
        "endTime": 1571890832.33,
        "durationInSeconds": 2,
        "contexts": [
            {
                "statusCode": "",
                "message": ""
            }
        ]
    },
    {
        "phaseType": "COMPLETED",
        "startTime": 1571890832.33
    }
]
```

`summaries` と`models` フォルダがアップロードされているか確認します。

```console
> mkdir logs
> aws s3 sync s3://ml-agents-learning-output/ml-agents-learning-test ./logs
> tree logs
logs/
└── 4f28bf27-194b-4c3d-974c-b553897f290c
    └── artifacts
        ├── models
        │   └── ppo-0
        │       ├── 3DBallLearning
        │       │   ├── checkpoint
        │       │   ├── frozen_graph_def.pb
        │       │   ├── model-50000.cptk.data-00000-of-00001
        │       │   ├── model-50000.cptk.index
        │       │   ├── model-50000.cptk.meta
        │       │   ├── model-50001.cptk.data-00000-of-00001
        │       │   ├── model-50001.cptk.index
        │       │   ├── model-50001.cptk.meta
        │       │   └── raw_graph_def.pb
        │       └── 3DBallLearning.nn
        └── summaries
            ├── ppo-0_3DBallLearning
            │   └── events.out.tfevents.1571890414.387f1a8a8f4a
            ├── ppo-0_3DBallLearning.csv
            └── ppo-0_timers.json

7 directories, 13 files
```

ファイルアップロードされていることが確認できました。やったぜ


## 参考

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

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

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