ML-Agents入門

# はじめに

※この記事は[RICORA Advent Calendar](https://adventar.org/calendars/6389)  12/24 の記事になります。

本記事ではUnityの深層強化学習パッケージのML-Agentsの導入と簡単なモデルの実装を行います。

パッケージなどのバージョンは以下の通りです。少なくともML-Agentsのバージョンは合わせてください。最新バージョンだと色々変わりすぎてて動きません。

* Unity: 2021.2.0f1
* ML-Agents: Release3 (1.1.0)
* Python: 3.7.10

以下Pythonライブラリ

* mlagents: 0.17.0
* mlagents-env: 0.17.0
* gym_unity: 0.17.0

さっそくML-Agentsのインストールから始めていきましょう！

## ML-Agentsのインストール

UnityとPythonのインストールは各自でお願いします。PythonはAnacondaを使って入れることをおススメします。

[ML-AgentsのGithub](https://github.com/Unity-Technologies/ml-agents/tree/release_3)からまずはパッケージ群をダウンロードしてきましょう。
![github_download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/45acc88a-2ea4-cd5a-e238-33936aa7c6ff.png)
解凍したらUnityフォルダ内に配置しておくと扱いやすいと思います。名前もml-agentsに変更しておきましょう。

次はUnity内でパッケージのインポートを行います。

パッケージマネージャーを選択してml-agentsのcom.unity.ml-agentsフォルダにあるpackage.jsonをインポートします。

![PackageManager.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/d9cc310f-fe6b-1f33-9379-accf5a4ad6c0.png)

![AddPackage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/840671f3-bbe7-db9c-5d8a-c7349114f544.png)

ML Agents 1.1.0-previewというものが追加されていればOKです。

あとはPythonに必要なライブラリをインストールしましょう。

```shell
pip install mlagents=0.17.0
pip install mlagents-env=0.17.0
pip install gym_unity=0.17.0
```

これでML-Agentsの環境が整いました。次に簡単なデモを作ってML-Agentsによる作業の流れを体験してみましょう！

## ML-Agentsプロジェクトの作成

デモとしてボールがターゲットを追跡する簡単なモデルを作ってみましょう。プロジェクト名はBall Agentとしています。

### モデルの作成

まずはモデル作りからです。床とエージェントとターゲットを作成します。

Hierarchyウィンドを右クリックしてPlaneを作成します。

![plane.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/b56d5284-5941-d7af-ecf2-cbc3142d26fa.png)

Positionは(0,0,0)にしましょう。

![plane_pos.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/8b2c5612-1bc9-a74e-3b73-d0b86030fcfe.png)

同様にCubeとSphereも追加しましょう。両方とも高さは0.5にしてます。実は初期位置はあまり関係ないので、適当な見やすい位置に置いておきましょう。

![sphere_cube.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/d2ab11d7-02df-c566-676a-669f7cb5d725.png)

HierarchyウィンドからSpehreを右クリックしてRenameから名前の変更を行いましょう。Sphere⇒Agent、同様にCube⇒Targetと変更します。

![rename.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/2c1aa654-cbb4-f061-b8f6-e0b66352358e.png)
次に物体が少し見づらいのでマテリアルを適用して色を付けてみましょう。

プロジェクトウィンドのてきとうな位置を右クリックしてCreateのMaterialを選択します。名前はGreenにします。
![material.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/adcd2b07-b97e-53c1-d7f9-b95d9b1f6134.png)

マテリアルができたらInspectorウィンドのAlbedoを選択して緑っぽい色を選んでください。
![green.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/115a3ea2-77bd-b018-2d68-d72e7bcc82be.png)

同様に黄色も作りましょう。

色が作れたら物体に直接ドラッグ＆ドロップして色を適用します。

![attempt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/a14be66c-2098-58a8-f1f7-a82e6d1de16a.png)

これで物体の視認性が良くなりました。次は物体の中身を作っていきましょう。

### コンポネントを付与する

モデルは配置しただけでは何も起きません。物理演算やML-Agentsを適用をするためには、コンポネントというものをモデルに追加する必要があります。

まずAgentに必要なコンポネントを追加していきます。

Agentを選択してInspectorウィンドからAdd Componentを選択してください。

![inspector.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/61d0fec5-8528-587c-343a-412b6ff844d7.png)

検索からBehavior Parametersを探して追加します。

追加されたら下の図で赤く囲った部分の項目を同じように変更してください。

![behavior_parameters.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/b6f43b3d-97e6-82ed-ab98-c449c0aa5cc6.png)

Vector ObsevationのSpace Sizeは観測する値が何個あるかを表します。Vector ActionのSpace Typeは受け取る入力の種類です。Continuousは連続値を返します。Space Sizeは何個値を入力するかを表します

同様にRigidBodyもAdd Componentから追加します。特に設定はしません。

同様にAdd ComponentからNew scriptを選択してBallAgentという名前でスクリプトファイルを生成します。

プロジェクトウィンドにBallAgentというスクリプトが追加されてるので。ダブルクリックして編集します。
![script.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/a4fe7319-711e-b0b7-8bef-36516212cf61.png)


スクリプトを以下のように変更します。

```C#:BallAgent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;

public class BallAgent : Agent {
    public Transform target;
    Rigidbody rigid;

    public override void Initialize() {
        this.rigid = GetComponent<Rigidbody>();
    }

    public override void OnEpisodeBegin() {
        if (this.transform.position.y < 0) {
            this.rigid.angularVelocity = Vector3.zero;
            this.rigid.velocity = Vector3.zero;
            this.transform.position = new Vector3(0.0f, 0.5f, 0.0f);
        }
        target.position = new Vector3(Random.value*8-4, 0.5f, Random.value*8-4);
    }

    public override void CollectObservations(VectorSensor sensor) {
        sensor.AddObservation(target.position);
        sensor.AddObservation(this.transform.position);
        sensor.AddObservation(rigid.velocity.x);
        sensor.AddObservation(rigid.velocity.z);
    }

    public override void OnActionReceived(float[] vectorAction) {
        Vector3 controlSignal = Vector3.zero;
        controlSignal.x = vectorAction[0];
        controlSignal.z = vectorAction[1];
        rigid.AddForce(controlSignal * 10);
        float distanceToTarget = Vector3.Distance(this.transform.position, target.position);
        if (distanceToTarget < 1.42f) {
            AddReward(1.0f);
            EndEpisode();
        }
        if (this.transform.position.y < 0) {
            EndEpisode();
        }
     }
}

```

オーバーライドした関数について大まかな説明します。

**Initialize**
トレーニングの最初に呼び出されます。ここではRigidbodyのインスタンスを取得しています。

**OnEpisodeBegin**
エピソードの開始時に呼び出され。ここでは主にトレーニングの環境を準備します。今回はターゲットのランダムな位置変更とエージェントの回転、速度のリセットを行っています。

**CollectObservations**
観察を行うときに呼び出されます。ここではターゲットの座標(x,y,z)、エージェントの座標(x,y,z)、エージェントの速度(x,z)の合計8個の値を受け取っています。ここで受け取る値の数はBehavior Parametersで設定したSpace Sizeと同じにする必要があることに注意してください。

**OnActionReceived**
行動を指示するときに呼び出されます。ここでは受け取った値でエージェントに力(x,z)を与えています。またエージェントが終了条件を満たしていた時にエピソードを終了する処理を行っています。今回はエージェントがターゲットに一定距離近づくと報酬を受け取って終了、またはエージェントが床から落ちると報酬なしで終了となっています。

これでスクリプトの準備ができました。スクリプト内のtargetの割り当てをUnityの方から行います。

AgentのInspectorウィンドを開いてBall AgentのスクリプトのTargetにHieracchyウィンドのTargetをドラッグ＆ドロップします。TargetがNoneからTargetに変更されたら成功です。
同時にBall AgentのスクリプトのMax Stepを1000に設定してください。

![target_attempt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/7565727e-5d95-17cb-6706-1e618845505b.png)

最後にDecision RequesterをAdd Componentから追加しましょう。Decision Periodは10に設定します。これは何フレーム毎に行動を行うかを設定するものです。数値を小さくすると行動を行う回数が増えます。

![decision_requester.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/f701bdad-9535-9de5-f7c5-9285451b16d7.png)

これで環境の準備が終わりました。トレーニングを実行していきましょう！

## ML-Agentsトレーニングの方法

トレーニングを開始する前に必要なパラメーターを記述してyamlファイルを準備します。

```yaml:BugAgent.yaml
behaviors:
  Agent:
    trainer_type: ppo
    hyperparameters:
      batch_size: 10
      buffer_size: 100
      learning_rate: 0.0003
      beta: 0.005
      epsilon: 0.2
      lambd: 0.95
      num_epoch: 3
      learning_rate_schedule: linear
    network_settings:
      normalize: true
      hidden_units: 128
      num_leyers: 2
      vis_encode_type: simple
    reward_signals:
      extrinsic:
        gamma: 0.99
        strength: 1.0
    keep_checkpoints: 5
    checkpoint_interval: 50000
    max_steps: 50000
    time_horizon: 64
    summary_freq: 1000
    threaded: true
```
ml-agent内のconfigフォルダーにBallAgentフォルダーを作って、そこにBallAgent.yamlを作りました。

次にコマンドプロンプトを起動してml-agentフォルダーに移動して次のコマンドを実行します。

```shell
mlagents-learn ./config/BallAgent/BallAgent.yaml --run-id=firstRun
```
先ほど作成したyamlファイルの指定と保存するidを決めます。

一度実行したあとでやり直そうとするとすでにフォルダーが生成されているためエラーになってしまいます。
実行を途中からやり直したいときは先ほどのコマンドの後ろに--resumeを付けてください。

```shell
mlagents-learn ./config/BallAgent/BallAgent.yaml --run-id=firstRun --resume
```

実行後に次のように表示されれば成功です。後はUnityのプレイボタンを押すと学習が開始されます。

![dos.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/dd5d7096-31f2-d76b-683e-9279ff8996b3.png)

実行後の様子です。爆速で球が動いていたらトレーニングができています。

![play.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/64c83b4e-492b-fb2c-6ff2-daa868e292c3.png)

トレーニング中はターミナルにsummaryが表示されます。mean rewardが上がっていく様子が分かると思います。

![training_dos.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/bd140243-ee48-1064-823d-3385e11859d6.png)

今回は比較的簡単なタスクなので、ステップ数を5万回に設定しましたが早い段階でmean rewardが1に収束しているのが分かると思います。

トレーニングが終了したら結果を確認してみましょう。

## トレーニング後のフィードバック

トレーニングが完了するとml-agentsフォルダーのresultsフォルダー内にidで指定した名前のフォルダーが生成されているのが分かると思います。今回はfirstRunという名前で保存されているはずです。

firstRunフォルダーを開いてAgent.nnというファイルをUnity内のProjectウィンド内にドラッグ&ドロップします。ニューラルネットのアイコンでAgentというファイルが出現します。

![agentnn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/46c04803-94e8-e464-cbe5-b4972b8f725c.png)

AgentのInspectorウィンドを開いてBehavior ParametersのModelのところにAgentをドラッグ&ドロップします。

![model_attach.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/15c677b2-0f70-55aa-a37e-86cf9d9ee43c.png)

ModelがNoneからAgentに変わったらPlayボタンを押して実行してみましょう。
トレーニング中とは違い通常の速度で推論が行われます。トレーニングが成功していればボールが床から落ちずに出現したターゲットにを追いかける様子が見れると思います。

次にトレーニング中の報酬の推移などを見てみましょう。

ターミナルを起動してml-agentsに移動します。そこで次のコマンドを実行します。

```shell
tensorboard --logdir=./results/firstRun
```
すると次のように表示されるので、赤線を引いた部分のURLをコピーしてウェブブラウザで開きます。

![tensorbaord_dos.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/9ce9c4aa-c8f1-49cb-745b-7426a0773968.png)

すると次のようにTensorBoardが表示されます。Culmitive Rewardは報酬の推移、Episode Lengthはエピソードの長さを表しています。報酬が1に収束していることとエピソードの長さが短くなっていることが確認できると思います。

![tensorbaord.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627384/47572b49-7851-91bc-c973-9c21ba5d04ea.png)

このしたにLossやPolicyの情報も表示されます。興味があればそちらも見てみましょう。

## まとめ

ML-Agentsの導入から簡単なモデルの作成とトレーニングのデモを行いました。

[次の記事](https://qiita.com/syunnsyunn74/items/35019d79a4b23475f993)では4足歩行のモデルを作成し、そのトレーニングを行ってみたいと思います。お楽しみに！！！