【Unity】2DでML-Agentsのチュートリアルをやってみた話


# 0.はじめに
この記事は[Life is Tech ! Kansai Advent Calendar](https://qiita.com/advent-calendar/2022/life-is-tech-kansai) の記事です。

ML-Agentsについて色々調べていくと2Dを使った記事が全然なくて困った過去がありました。
なので今回は2DでRay Perception Sensor 2Dを用いて、ML-Agentsの公式チュートリアル的なものを作ってみました!
2Dを久しぶりに触る人やML-Agentsを触ったばっかりの人に見ていただけると幸いです。

以下完成動画です
<iframe width="560" height="315" src="https://www.youtube.com/embed/fBkW5Jpvt4k" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>




# 1.使用環境
- Unity 2021.3.9f1
- ML-Agent Release 20

# 2.学習環境の作成
UnityHubを開き新規2Dプロジェクトから「2DMLRaycast」という名前のプロジェクトを作成します。
![スクリーンショット 2022-12-21 2.34.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/b39b5830-72ba-0a62-c8f6-ec25051a3327.png)

## 2-1 必要なPacakageのインストール
次に作成したプロジェクトにML-Agentsを追加していきましょう！
まず、WindowからPacakage Managerを開きます。次に＋ボタンから`add package from disk`を選択します。
そしてGithubからインストールしてきたml-Agentのフォルダの「com.unity.ml-agents/package.json」を選択してインポートをします。![スクリーンショット 2022-12-21 2.52.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/5a70aac5-a8c1-9850-53ff-9b8d4f3d4e5e.png)
 
## 2-2 Stageの作成

つづいて、Hierarchy ウィンドウから GameObjectを作成していきましょう。
(作成の方法はHierarchy ウィンドウ下の＋ボタンをクリックして下図のように選択してください)
![スクリーンショット 2022-12-21 3.03.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/385160e3-0bde-252f-8178-b822870f4ac3.png)

今回必要な機能はStageとAgentTargetの３つなので順番に作っていきます。

- 1.Stage
`2D Object > Sprites > Square `を作成します。今回は`Stage`という名前にしました。
Position(0,0,0) , Rotation(0,0,0) , Scale(20,10,1)にしましょう。
- 2.Agent
`2D Object > Sprites > Circle `を作成します。今回は`Player`という名前にしました。
Position(0,0,0) , Rotation(0,0,0) , Scale(1,1,1)にしましょう。
そしてAdd Componentで`Circle Collider 2D`と`Rigidbody 2D`を追加しておきます。
- 3.Target 
`2D Object > Sprites > Square `を作成します。今回は`Target`という名前にしました。
Position(2,2,0) , Rotation(0,0,0) , Scale(1,1,1)にしましょう。
そしてAdd Componentで`Box Collider 2D`を追加しておきます。

更に後々使うので、Targetのタグを`Target`に設定します。
![スクリーンショット 2022-12-23 0.23.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/c4f14c51-03bc-da24-48da-6559a72eb1dc.png)


また、この際に`Player`と`Target`のSprite RendererにおけるAdditional SettingsのOrder inLayer　を `1`に設定しておきましょう



![スクリーンショット 2022-12-21 3.22.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/f553b9b2-1241-5e80-ebd9-bddaa94c290d.png)


すべての設置が終了したらこんな感じになります。(なお視認性をあげるために任意の色の設定とPlayerに簡易的な目をつけています)

![スクリーンショット 2022-12-21 3.25.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/989f20ee-ee71-f85e-9d42-bb84dc8ad37c.png)


## 2-3 Agentに必要な設定
次にAgentである`Player`に機械学習ができるように設定をしていきましょう ！
Playerを選択したあとに Add Component を選択し`Behavior Parameters`を追加して以下のように設定します。

![スクリーンショット 2022-12-21 3.36.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/ae9a9be4-1bac-bb49-ff6f-fab98b09b6a4.png)

「Behavior Parameters」 は　Agentに必ず追加する必要があります
- Behavior Name:訓練設定ファイルの名前で使用します
- Vector Observation
  - Space Size : Vector Observationの大きさ
- Continuous
  - Space Size : 行動の大きさ

また、`Decision Requester`もPlayerに追加し、`Decision Period `を 10 に設定します。


## 2-4 Ray Perception Sensor 2D の設定

次にPlayerに`Ray Perception Sensor 2D `を追加し、以下の設定をします。
これはPlayerからRayを飛ばし、ヒットした情報を観測データとして自動でエージェントに送る便利なコンポーネントです。
![スクリーンショット 2022-12-22 22.42.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/d0e1567b-819a-93de-9cc3-2ae4441aff85.png)

上から順番に軽く説明していきます。

- Detectable Tags:観測対象になるタグを選択します
- Rays Per Direction: 左右に飛ばすRayの数を設定します。今回の場合値が「3」なので正面に１本、左右にそれぞれ3本の計7本Rayが飛ぶことになります。
- Max Ray Degrees: 一番外側のRayから正面のRayまでの角度
- Ray Length : Rayの長さ

このままだと、Player自体にRayが干渉してしまうのでPlayerのLayerを`2: Ignore Raycast`に選択して干渉しないようにしましょう！
![スクリーンショット 2022-12-23 0.41.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/29e8cef4-74cc-e636-b02a-d2880362f3d8.png)


Playerのオブジェクトを選択肢して、以下のようにRayが飛んでいるのを確認出来たら準備OKです！
![スクリーンショット 2022-12-22 22.42.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/f6deb302-ef33-1bbd-0e07-976988018dd3.png)

## 2-5 Playerのスクリプトの作成

それではPlayerにアタッチするスクリプトを書いていきましょう！

```C# PlayerAgent.cs
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;

public class PlayerAgent : Agent
{
    Rigidbody2D rBody;
    public Transform Target;

    void Start()
    {
        rBody = this.gameObject.GetComponent<Rigidbody2D>();
    }

    //初期化
    public override void OnEpisodeBegin()
    {
        //プレイヤーの初期化
        this.rBody.angularVelocity = 0;
        this.rBody.gravityScale = 0;
        this.rBody.velocity = Vector3.zero;
        this.transform.localPosition = new Vector3(0, 0.5f, 0);

        // ターゲットを生成
        Target.localPosition = new Vector3(Random.value * 10 - 2, Random.value * 5 - 2, 0);

    }

    //環境情報の収集
    public override void CollectObservations(VectorSensor sensor)
    {
        //自身の座標
        sensor.AddObservation(this.transform.localPosition);

        // 加えた力
        sensor.AddObservation(rBody.velocity.x);
        sensor.AddObservation(rBody.velocity.y);
    }

    private float moveSpeed = 5;
    private float rotateSpeed = 10.0f;

    //行った行動による報酬の決定
    public override void OnActionReceived(ActionBuffers actionBuffers)
    {
        // Actions, size = 2
        Vector2 controlSignal = Vector2.zero;
        controlSignal.x = actionBuffers.ContinuousActions[0];
        controlSignal.y = actionBuffers.ContinuousActions[1];

        //回転させる角度
        float angle = controlSignal.x * rotateSpeed;
        transform.Rotate(new Vector3(0, 0, -angle));

        float angleDir = (transform.eulerAngles.z + 90) * Mathf.Deg2Rad;
        Vector3 dir = new Vector3(Mathf.Cos(angleDir), Mathf.Sin(angleDir), 0.0f);
        rBody.velocity = dir.normalized * moveSpeed * controlSignal.y;


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

        if (distanceToTarget < 1.3f)
        {
            SetReward(1.0f);
            EndEpisode();
        }
        else if (Mathf.Abs(this.transform.localPosition.x) > 9.5f || Mathf.Abs(this.transform.localPosition.y) > 4.5f)
        {
            EndEpisode();
        }
    }

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

このコードをPlayerにアタッチしたあとに、Target の GameObject をドラッグ & ドロップで PlayerAgentに設定します。
![スクリーンショット 2022-12-21 3.50.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/37cb2f57-faca-cfcf-7eba-e4c96419e44f.png)


## 2-6 Heuristicでの動作テスト
ここまで出来たのなら一度Heuristicモードで操作できるか確認してみましょう！
Heuristicモードは`Behavior Parameters`の「Behavior Type」をHeuristic Onlyにして再生をすると操作することが出来ます。
![スクリーンショット 2022-12-23 0.46.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/9341c4b3-99fa-ecf0-dd05-485f38326c3f.png)


# 3 訓練ファイルの作成
続いて訓練ファイルを作成していきましょう！
予めGitHubからクローンしたml-agentフォルダに移動し、config/ppo下に`2DBall.yaml`ファイルを作成し、以下を書いていきます。

```yaml 2DBall.yaml
behaviors:
  2DBall: # Behavior Name
    trainer_type: ppo  # トレーナー種別
    # 学習アルゴリズムの設定
    hyperparameters:
      batch_size: 128
      buffer_size: 256
      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

```

# 4 強化学習の実施

すべて完了したらPythonのml-agentフォルダにて以下のコマンドを実行します。
このときの「run-id」は学習する際のIDで出力先のフォルダ名になります。
仮にIDを上書きで実行したい場合は末尾に` --force`をつけてあげてください
```
$ mlagents-learn config/ppo/2DBall.yaml --run-id=2DBall
```

上記のコマンドを実行したあとにUnity EditorのPlayボタンを押すと学習が始まります。

<iframe width="560" height="315" src="https://www.youtube.com/embed/3h4EIjvzXFA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

ある程度学習させたあとにUnity Editorの再生を停止するとml-agentフォルダの「results/2DBall」に「2DBall.onnx」というモデルが生成されています。

そのモデルをUnityのAssets下に持ってきたあとに、Behavior Parameters のModelにドラッグ＆ドロップしてモデルの設定をします。
その後シーンを実行すると学習モデルを実行することが出来ます!
![スクリーンショット 2022-12-23 1.27.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2386224/75d21ceb-8042-7402-85e3-b4c6dbdaa8cd.png)


# 5 最後に
いかがでしたでしょうか。かなり端折った説明にはなっていますが、2DでML-Agentsを使用する際の参考になれば幸いです。

また余談にはなりますが、私自身この分野について勉強し始めたばかりなのとQiitaで執筆するのが初めてなので、間違いや不明点があればお気軽に知らせてください。

参考記事

https://spi8823.hatenablog.com/entry/2020/03/20/201538

https://note.com/npaka/n/n30707ea92bd1?magazine_key=m50f437a3f5e1