Unity でサクッと機械学習を体験してみよう【ML-Agents】

「[Applibot Advent Calendar 2021](https://qiita.com/advent-calendar/2021/applibot)」 21日目の記事になります。
前日は [@yucchiy_](https://twitter.com/yucchiy_) さんの [Unityでアプリ内にアセットを組み込む3つの方法](https://blog.yucchiy.com/2021/12/built-in-asset-for-unity/) という記事でした。

# はじめに

様々な場面で AI が活用されるようになってきた昨今、**機械学習**やら**深層学習（DeepLearning）**などの単語を目にする機会も増えました。興味はあるのだけれど、なんとなく難しそう、大変そう… と手が出せていない方、居るのではないでしょうか？（私です）

えいやと試してみたところ、機械学習も Unity も素人の私（サーバサイドエンジニア）ですが、想像以上に簡単に、学習したモデルを動かすまでを体験することが出来ました。

備忘録代わりに手順をまとめたので、ぜひ機械学習に触れたことの無い方も手元で試してみてください。
機械学習ってこんな感じなんだ〜、というふわっとした理解が出来るかと思います。

# この記事の内容

Unity で ML-Agents を使った強化学習を行うチュートリアルといった感じです。
Unity も機械学習も知識がなくとも雰囲気がわかるように書いています。

# ML-Agents とは

https://github.com/Unity-Technologies/ml-agents

[Unity Machine Learning Agents（ML-Agents）](https://unity.com/ja/products/machine-learning-agents) の事で、Unity を使って機械学習を行うことのできるフレームワークです。これにより、深い専門知識なしでも、視覚的にわかりやすく機械学習を体験することができます。

この記事では機械学習の一種である強化学習を ML-Agents を使った一連の流れで体験してみます。

# 1. 環境構築

※ ML-Agents は更新が活発で、リリースサイクルもかなり早いです。使うツールのバージョンに差異があると詰まる可能性が高いのでご注意下さい。記事では、執筆時点で最新の `Release 18` に対応した内容となっています。

## 1-1. Unity のインストール

ML-Agents のリリースに適したバージョンの Unity を使うため UnityHub 経由でインストールを行います。

https://unity.com/ja/download

Unity 公式サイトから UnityHub をダウンロードし、インストールを行った後

https://unity3d.com/jp/get-unity/download/archive

こちらから `2019.4.25` をのバージョンの Unity をインストールします。
（`Release 18` のサンプルがこのバージョンを使っているため）

## 1-2. Python のインストール

バージョンは `3.6` か `3.7` が推奨されているので、今回は `3.7` の最新である `3.7.12` を使います。

https://github.com/pyenv/pyenv

バージョンを簡単に切り替えられるよう、pyenv を使ってインストールすることを推奨します。
（導入方法は他の記事に譲ります…） 

```
pyenv install 3.7.12
pyenv global 3.7.12
```

## 1-3. mlagents-learn のインストール

学習のためのユーティリティである `mlagents-learn` を pip 経由でインストールします。
`Release 18` では `0.27.0` を使っているので、以下のようにインストールします。

```
python -m pip install mlagents==0.27.0
```

以下のコマンドで使い方が表示されるようになれば OK です。

```
mlagents-learn --help
```

## 1-4. ml-agents の Git リポジトリの Clone

https://github.com/Unity-Technologies/ml-agents

ml-agents リポジトリをクローンし `release_18` のタグをチェックアウトします。

```
git clone git@github.com:Unity-Technologies/ml-agents.git
cd ml-agents
git checkout release_18
```

以降、コマンドを実行する際はこのリポジトリ直下で行うものとします。

これで一通りの準備は完了です。

# 2. サンプルを動かしてみる

## 2-1. プロジェクトを開く

Unity Hub を起動してプロジェクト画面の「リストに追加」からクローンしたリポジトリの `Project` ディレクトリを選択します。

![スクリーンショット 2021-12-21 3.03.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/4dd2caf8-3bab-6da8-d7c9-c92353c3047e.png)

![スクリーンショット 2021-12-21 3.04.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/09629d36-407c-fa39-e1fc-8144ca03ce8d.png)

リストに追加されるので、これを起動します。

## 2-2. Unity パッケージの追加

サンプルでは以下の Unity パッケージが必要なので、パッケージマネージャでインストールします。

- `com.unity.ml-agents`
- `com.unity.ml-agents.extensions`

`Window > PackageManager > Add package from disk` の順に選択。

**※ 既にパッケージマネージャに表示されている場合、この手順は不要になります**

![スクリーンショット 2021-12-21 3.10.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/fa72657a-984f-2063-ec00-9ecfaedade96.png)

クローンしたリポジトリ内の

- `com.unity.ml-agents/package.json`
- `com.unity.ml-agents.extensions/package.json`

をそれぞれ選択すれば OK です。

## 2-3. サンプルを眺める

`Assets/ML-Agents/Examples` の下に色々なサンプルがあります。

![スクリーンショット 2021-12-21 3.15.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/9d6ca9d9-4514-6317-7a35-79f488005ac5.png)

試しに `Assets/ML-Agents/Examples/3DBall/Scenes/3DBall` を開いてみましょう。

![スクリーンショット 2021-12-21 3.17.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/bd036a0e-ca4d-928b-490a-b2b51492317a.png)

たくさんの AgentCube 君（顔のついた箱みたいなやつ）が出迎えてくれます！

早速再生ボタンを押して実行しましょう。

![a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/08bdaeb5-1e3b-fa62-0907-c7a3a3328bc2.gif)


ボールを落とさないようバランスを取る AgentCube 君を眺めることが出来ます。かわいいですね。

これは既に学習済のモデルを動かしている状態となっています。

![b.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/56be8395-f952-b9a6-04f8-d03ee0c1aa06.gif)

他にもサッカーゲームをする AgentCube 君や

![c.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/1e375ae0-1219-e5d0-5ffd-b57ae9d920f5.gif)

3マッチパズルと化した AgentCube 君など、豊富なサンプルを見ることが出来ます。

## 2-4. サンプルを自分で学習させてみる

学習はどのように行われるのか、3DBall の例で実際に試してみましょう。

`mlagents-learn` コマンドを使用して学習を行います。以下のコマンドを実行して下さい。

```
mlagents-learn config/ppo/3DBall.yaml --run-id=first3DBallRun
```

`3DBall.yaml` は学習のための構成ファイルで、最適化に大切な**ハイパーパラメータ**と呼ばれるものもここに含まれています。ml-agents リポジトリにはサンプル用の構成ファイルも含まれているので、これをそのまま指定している形です。

![スクリーンショット 2021-12-21 3.45.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/36c64460-060a-d553-8c73-021d4cbaee5d.png)

このように表示されたら Unity 上で 3DBall のシーンを実行します。

![d.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/8a1154f7-fa7d-8f7f-2c96-7dab35f0c954.gif)

**高速で AgentCube 君が動き出しました！**
強化学習では Agent のある行動に対して報酬を与えることで、報酬を最大化するように学習し最適化することができます。この光景は、それを視覚的に見ている状況という訳です。

![スクリーンショット 2021-12-21 4.07.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/68e524df-5554-192a-9b7a-5175d5ad53fb.png)

コンソールには学習の進捗状況が表示されており、ステップ数や得られた報酬の平均値などを知ることが出来ます。
（ステップが増える毎に、報酬の平均値も増えていくのがわかるかと思います。）

適当なタイミングでシーンの再生を停止し、学習を止めて見て下さい。
![スクリーンショット 2021-12-21 4.08.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/3d38436e-3c6d-ff9e-f9e7-bfff09093b38.png)

コンソールに表示されている通り `results/first3DBallRun/3DBall.onnx` に学習したモデルが生成されています。

```
cp results/first3DBallRun/3DBall.onnx Project/Assets/ML-Agents/Examples/3DBall/TFModels/My3DBall.onnx
```

などとコマンドを実行し、アセット内に作ったモデルをコピーします。

![スクリーンショット 2021-12-21 3.54.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/097eea72-14dd-cc91-d3eb-2a75eb3ce5dd.png)

Hierarchy ウィンドウで `3DBall > Agent` のオブジェクトを選択し、
Inspector ウィンドウの `Behavior Parameters > Model` を見て下さい。

![スクリーンショット 2021-12-21 3.57.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/c36ead56-1208-f044-7e03-7659b50ab3f5.png)

`3DBall(NNModel)` とありますね。これが今適用されている学習済モデルです。

これを、先程作成したモデルにドラッグ & ドロップで置き換えます。

![スクリーンショット 2021-12-21 4.01.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/4eaa3811-e6cd-6662-45fe-4afb84381238.png)

これで学習したモデルに差し替える事が出来ました。シーンを再生してみて下さい！

![e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/51a1ef03-8cfd-dd41-2a22-12f8cf01a39b.gif)

差し替えたモデルの出来栄えはいかがでしょうか？
今回はすぐに学習を止めてしまったので、他の学習済のモデルと比べると安定感が無いのがわかるかと思います。
（２行目２列目の AgentCube 君のみ差し替えたモデルを使っています。）

これは学習時間を長くすることで徐々に改善されていくので、自分でもぜひ試してみて下さい。

# 3. 一から学習環境を作る

最後に、いよいよ自分で１から学習環境を作って強化学習を実行してみましょう。

内容は [ML-Agents の公式チュートリアル](https://github.com/Unity-Technologies/ml-agents/blob/release_18_docs/docs/Learning-Environment-Create-New.md)と同じものになります。

## 3-1. シーンの作成

Assets フォルダ直下に学習用のシーンを作成します。（右クリックのコンテキストメニューなどから作成出来ます。）
シーンの名前は何でも良いですが、ここでは `RollerAgent` としました。

![スクリーンショット 2021-12-21 4.21.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/1de943a7-59e6-9042-6d80-34f34ed60f3f.png)

シーンを開き、Hierarchy ウィンドウから GameObjects を作成します。（右クリックのコンテキストメニューなどから作成出来ます。）

![スクリーンショット 2021-12-21 4.27.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/a5451db8-1a04-f414-3191-1a63a70a884d.png)

以下の３つの GameObjects を作ります。

- 1. 床
  - `Plane` を作成し `Floor` という名前にする
  - Position を (0, 0, 0), Rotation を (0, 0, 0), Scale を (1, 1, 1) にする
- 2. ターゲット
  - `Cube` を作成し `Target` という名前にする
  - Position を (3, 0.5, 3), Rotation を (0, 0, 0), Scale を (1, 1, 1) にする
- 3. エージェント
  - `Sphere` を作成し `RollerAgent` という名前にする
  - Position を (0, 0.5, 0), Rotation を (0, 0, 0), Scale を (1, 1, 1) にする
  - RigidBody コンポーネントを追加する（下図の矢印の辺りから追加出来ます）

全てを良い感じに設定すると、こんな感じになります。

![スクリーンショット 2021-12-21 4.44.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/5af3e9af-5421-49c2-0518-6b6b98cb149d.png)


## 3-2. スクリプトの作成

RollerAgent にさらに Script コンポーネントを追加します。

`Add Component` から `New Script` を選択し、名前は `RollerAgent` としておきます。

![スクリーンショット 2021-12-21 4.49.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/733acf65-3f14-fbb5-02ef-3e92c185271c.png)

コンポーネントも追加され、Assets 直下にスクリプト用のファイルも生成されました。これを開きます。
そして、ここにコードを書いていく訳なのですが…

**この記事では体験することが目的なので、完成形のコード全体を貼ってしまいます。**

```csharp:RollerAgent.cs
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;

public class RollerAgent : Agent
{
    Rigidbody rBody;

    void Start()
    {
        rBody = GetComponent<Rigidbody>();
    }

    public Transform Target;
    public override void OnEpisodeBegin()
    {
        // If the Agent fell, zero its momentum
        if (this.transform.localPosition.y < 0)
        {
            this.rBody.angularVelocity = Vector3.zero;
            this.rBody.velocity = Vector3.zero;
            this.transform.localPosition = new Vector3(0, 0.5f, 0);
        }

        // Move the target to a new spot
        Target.localPosition = new Vector3(Random.value * 8 - 4, 0.5f, Random.value * 8 - 4);
    }

    public override void CollectObservations(VectorSensor sensor)
    {
        // Target and Agent positions
        sensor.AddObservation(Target.localPosition);
        sensor.AddObservation(this.transform.localPosition);

        // Agent velocity
        sensor.AddObservation(rBody.velocity.x);
        sensor.AddObservation(rBody.velocity.z);
    }

    public float forceMultiplier = 10;
    public override void OnActionReceived(ActionBuffers actionBuffers)
    {
        // Actions, size = 2
        Vector3 controlSignal = Vector3.zero;
        controlSignal.x = actionBuffers.ContinuousActions[0];
        controlSignal.z = actionBuffers.ContinuousActions[1];
        rBody.AddForce(controlSignal * forceMultiplier);

        // Rewards
        float distanceToTarget = Vector3.Distance(this.transform.localPosition, Target.localPosition);

        // Reached target
        if (distanceToTarget < 1.42f)
        {
            SetReward(1.0f);
            EndEpisode();
        }

        // Fell off platform
        else if (this.transform.localPosition.y < 0)
        {
            EndEpisode();
        }
    }

    public override void Heuristic(in ActionBuffers actionsOut)
    {
        var continuousActionsOut = actionsOut.ContinuousActions;
        continuousActionsOut[0] = Input.GetAxis("Horizontal");
        continuousActionsOut[1] = Input.GetAxis("Vertical");
    }
}
```

簡単に説明すると `Unity.MLAgents.Agent` クラスの持つ以下３つのメソッドを拡張しています。

- `OnEpisodeBegin()`
    - シーンの初期化を行う（ランダムにターゲットを設置するなど）
- `CollectObservations(VectorSensor sensor)`
    - 環境の情報の収集を行う（ターゲットの位置、エージェント自体の位置および速度など）
- `OnActionReceived(ActionBuffers actionBuffers)`
    - 行った行動による報酬の決定など（ターゲットに近いほど高得点！）

詳しくは [公式チュートリアルの説明](https://github.com/Unity-Technologies/ml-agents/blob/release_18_docs/docs/Learning-Environment-Create-New.md) を見て下さい。

また、この後の手動での動作テストのため `Heuristic` メソッドも拡張しています。

このコードをそのまま保存します。

## 3-3. Unity とスクリプトの接続

Hierarchy ウィンドウの RollerAgent を選択し、Script コンポーネントの `Target` の欄に Target の GameObject をドラッグ & ドロップで設定します。

![スクリーンショット 2021-12-21 5.12.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/dc3b03aa-ce9c-4fee-ef04-7eebc8698d69.png)

また、さらに RollerAgent に `コンポーネントの追加` から以下コンポーネントを追加します。

- Decision Requester
    - `Decision Period` を 10 に設定
- Behavior Parameters
    - `Behavior Name` を `RollerBall` に設定
    - `Vector Observation > Space Size` を 8 に設定
    - `Actions > Continuous Actions` を 2 に設定

![スクリーンショット 2021-12-21 5.17.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/c498041f-375f-78b1-317f-a6cf0c2e22e9.png)

これで設定は全て完了です。

## 3-4. 手動での動作テスト

`Behavior Parameters` の `Behavior Type` を `Heuristic Only` にすることでキーボードの入力で Agent を操作する事が出来ます。試しに変更してからシーンを再生してみましょう。キーボードで操作してみて、ターゲットに触れたら再設置されることや、画面外に落ちたらリスポーンする事などを確認しましょう。

![f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/7452ab93-e3f3-f9a7-498b-7503886d6fdf.gif)

確認が出来たら `Behavior Parameters` の `Behavior Type` は `Default` に戻しておきます。 

## 3-5. 学習させてみる

では、サンプルで行ったのと同様 `mlagents-learn` コマンドを使用して学習を行います。その際の構成ファイルはチュートリアル通りに次のものを使ってみます。

```yaml:rollerball_config.yaml
behaviors:
  RollerBall:
    trainer_type: ppo
    hyperparameters:
      batch_size: 10
      buffer_size: 100
      learning_rate: 3.0e-4
      beta: 5.0e-4
      epsilon: 0.2
      lambd: 0.99
      num_epoch: 3
      learning_rate_schedule: linear
    network_settings:
      normalize: false
      hidden_units: 128
      num_layers: 2
    reward_signals:
      extrinsic:
        gamma: 0.99
        strength: 1.0
    max_steps: 500000
    time_horizon: 64
    summary_freq: 10000
```

詳細が気になる方は [公式ドキュメント](https://github.com/Unity-Technologies/ml-agents/blob/release_18_docs/docs/Training-Configuration-File.md) を確認してみて下さい！

`config/rollerball_config.yaml` にファイルを作成し、以下コマンドを実行して学習を開始します。

```
mlagents-learn config/rollerball_config.yaml --run-id=RollerBall
```

待機中になったら Unity でシーンを再生して下さい。高速で学習が進んでいく様子が見られます。のんびり待ちましょう。

![g.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/6b6507dd-4e31-e9aa-816b-f540c310d32e.gif)

適当な時間が経過したら、良き所で停止してモデルを生成します。

サンプル同様に以下のようなコマンドで Asset 直下にコピーし

```
cp results/RollerBall/RollerBall.onnx Project/Assets/RollerBall.onnx
```

RollerAgent の `Behavior Parameters > Model` にドラッグ & ドロップしモデルを設定し、シーンを実行すれば…

![h.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191059/924b7e62-8144-8ef9-a300-7c21df59c310.gif)

自分で学習させたモデルを実行する事が出来ました！

# おわりに

今回の記事では、一通りの ML-Agents を使った機械学習の流れを説明しました。
だいぶ駆け足の説明でしたが、雰囲気は何となく感じられたでしょうか？
思ったよりも簡単に機械学習を体験出来るということが伝わったら幸いです。
みなさまも、年末年始の休暇などで是非試してみてはいかがでしょうか。

# 参考文献

公式 Documents（`Release18`版）

https://github.com/Unity-Technologies/ml-agents/blob/release_18_docs/docs/Readme.md