Unityで強化学習する準備：導入～サンプルの学習・検証

# Unityで強化学習する準備：導入～サンプルの学習・検証

強化学習を実際に自分のロボットで行う際に，様々なプラットフォームが考えられるが，
今回は，UnityのML-Agentsを試していきたい．理由は，Unityも使えるようになりたいということも挙げられるが，ほかには以下のようなものが挙げられる．

- ROS, ROS2も後に導入可能であること
- Unityの物理エンジンは今はNvidiaが開発しているPhysX
    - 摩擦などの細かな設定ができる（より現実世界を模擬することができる）
- そこそこのスペックでも十分動くこと
- Multi Agentsの実現が非常に簡単であること

## 環境

##### コンピュータ
|||
|:-:|:--|
|デバイス|MSI|
|プロセッサ|Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz 2.50GHz|
|実装RAM|16.00 GB (15.9 GB 使用可能)|
|OS|Windows (Windows 10 Home, バージョン：21H2)|
|GPU|GEFORCE MX 150|

##### Unity
|||
|:-:|:--|
|Unity|2022.1.5f1|
|Unity Hub|3.3.0|




## Unity, ML-Agentsの環境構築
環境は以下を参考に作成した．

http://itoshi.main.jp/tech/2022/03/06/unity_ml_agents_newenv/

### python環境の準備

#### Anacondaで仮想環境作成
```bash
conda create -n mlagents python=3.7.11
conda activate mlagents
```

#### PyTorchを使えるように

```bash
conda install pytorch==1.11.0 torchvision==0.12.0 torchaudio==0.11.0 cudatoolkit=11.3 -c pytorch
```

#### mlagentsをセットアップ

```bash
cd <ml-agents-release_19のディレクトリ>
pip install -e ./ml-agents-envs
pip install -e ./ml-agents
```

#### ML-Agents Unity packageの追加
1. 3Dプロジェクトを作成
    - 今回の名前は”RollerBall”としておく
1. プロジェクトにML-Agents Unity packageを追加する
    1. UnityのWindowタブからPackage Managerを開く
    1. Package Managerの左上にある+を押して，Add package from disk...を選択
    1. ml-agents-release19のcom.unity.ml-agents内にあるpackage.jsonを指定
    1. 以下のようになれば，インストール完了

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/f2c684cf-cd35-5ca2-1889-b9f2b5e44ebf.png)


ここまでで，ML-Agentsが使える準備が整った

## ML-Agentsのサンプル

### Unityプロジェクトのセットアップ
先ほどの環境構築ですでに完了．

### 環境の作成

#### 地面
1. ヒエラルキー内で右クリックして3D Object > Planeを選択
2. “Floor”という名前にしておく
3. Floor Planeを選択
4. 以下のようにTransformを設定
        
    ```csharp:Transformに設定
    Position = (0,0,0)
    Rotation = (0,0,0)
    Scale = (1,1,1)
    ```

以下の図のようになれば大丈夫．

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/b1a8ec5f-2bc2-b7f2-ce8c-651b55245950.png)


#### ターゲットキューブ
1. ヒエラルキー内で右クリックして3D Object > Cubeを選択
2. “Target”という名前にしておく
3. “Target”を選択してInspector windowを開く
4. Transformを以下のように設定する
    
    ```csharp:Transformに設定
    Position = (3, 0.5, 3)
    Rotation = (0, 0, 0)
    Scale = (1, 1, 1)
    ```

以下の図のようになれば大丈夫．

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/5412cb23-6ab6-4131-39b8-55aae735acd1.png)


#### エージェントボール
1. ヒエラルキー内で右クリックして3D Object > Sphere
2. “RollerAgent”という名前にしておく
3. RollerAgentを選択してInspector window
4. Transformを以下のように設定する
    
    ```csharp:Transformに設定
    Position = (0, 0.5, 0)
    Rotation = (0, 0, 0)
    Scale = (1, 1, 1)
    ```
    
5. Add Componentをクリック
6. Rigidbody componentをSphereに追加
    a. Add Componentをクリック後に``Rigid``と検索すれば出てくる

以下の図のようになれば大丈夫．

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/c91e8edb-bd75-9b46-4a94-dfa9f2778abc.png)


#### 色付けしてみる
1. project内で右クリックして，create > Materialを選択

    ![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/b271fee4-68dc-fa8b-9f79-96f11df52423.png)


2. それぞれのオブジェクトに対してのMaterialファイルを作成
3. Materialファイルを選択してInspector windowを開く
4. Inspector window内のAlbedo項目で変更

    ![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/13d58113-300c-3af7-6599-b3d1b81ef7ed.png)


5. 作成したMaterialファイルを対象のオブジェクトにドラッグする

![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/50a5bd1b-129a-8423-7ed4-099f22cab2ab.png)



以下の図のようになれば大丈夫．

![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/6d4df4ee-dd9c-dd27-be2a-edca859402c2.png)


#### グルーピング
1. ヒエラルキー内で右クリックして，Create Emptyを選択して，新規のempty GameObjectを作成．（名前はTrainingAreaとでもしておく）
2. TrainingAreaのTransformを以下のようにリセットしておく
    
    ```csharp:Transformに設定
    Position = (0, 0, 0)
    Rotation = (0, 0, 0)
    Scale = (1, 1, 1)
    ```
    
    ![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/99cdf869-a51e-3c49-7157-43ee76e7487b.png)


3. ヒエラルキー内でFloorとTargetとRollerAgentをドラッグしてTrainingAreaへもっていく

以下の図のようになれば大丈夫．

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/1452bb35-4b2f-4c7f-9cf5-b9ce06699c3c.png)


### エージェントの実行
#### スクリプトの準備
1. エージェントのGameObjectを選択
    a. inspector windowが開く
2. Add Componentをクリック
3. New Scriptをクリック
4. スクリプトの名前を``RollerAgent``とする
5. Create and Addをクリック

#### スクリプト
<details><summary>RollerAgent: ソースコード</summary>

```csharp:RollerAgent
using System.Collections.Generic;
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();
		    }
		}

}
```
</details>

#### コード内の変数を反映
1. RollerAgentを選択してInspector windowを開く
2. ヒエラルキー内のTargetをInspector内のRollerAgent Script内のTargetにドラッグ

    ![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/e2c377cc-2e84-4a1a-3e00-0ff15501c6b2.png)

3. Add ComponentボタンからDecision Requester scriptを追加
4. Decision Periodを10に設定
5. Add ComponentボタンからBehavior Parameters scriptを追加
6. Behavior Parametersを以下のように設定
    
    ```csharp: Behavior Parametersに設定
    Behavior Name: RollerBall
    Vector Observation > Space Size = 8
    Actions > Continuous Actions = 2
    ```

### 学習
#### 学習設定（yamlファイル）

<details><summary>学習設定（yamlファイル）</summary>

```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
```
</details>

rollerball_config.yamlファイルをml-agents-release_19のconfigフォルダ内に置く．
 
#### 実行コマンド

```bash: 実行コマンド
mlagents-learn config/rollerball_config.yaml --run-id=RollerBall
```

#### 学習の様子：Single Agent

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

#### TensorBoard
```bash: 起動コマンド
tensorboard --logdir=<mlagentsのresultsフォルダ>
```

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/d86b07e8-6605-a6eb-ac6f-5de7bf81140e.png)


学習結果が出始めると他にも情報が現れてくる

![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/d2bac3a7-4d43-1cf7-6bba-3c9cc0e00a7b.png)


### オプション
#### オプション１：複数のraining Areaを同じシーンに置く
1. TrainginAreaをAssets browerにドラッグ
    prefab生成
    
2. prefabをシーンに欲しいだけドラッグするだけ    
    互いに重ならないように注意

#### 学習の様子：Multiple Agent

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

#### オプション２：同時Unityインスタンスを使った学習
以下のコマンドにより実現可能

```bash
mlagents-learn config/rollerball_config.yaml --run-id=RollerBall --num-envs=2
```

### テスト
学習済みのモデルファイルonnxをAssetsに入れておいて，
それをエージェントの``Behavior Parameters``にドラッグ．

![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/c598369c-49b7-38a7-7902-55aa54292784.png)

後は実行ボタンを押すだけ．

#### 検証の様子：Multiple Agent

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

### 学び：シーンを分けると良い
手作業で毎回テスト用のもの以外を削除するのは大変

↓

test sceneとtrain sceneを作っておけばよい

![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/46e0f6fb-078b-f8a9-c2b4-a87d3cbc940d.png)

Assetsにあるsceneファイルをダブルクリックするだけでtestとtrainを切り替えられる


## 感想
いい感じに，複数のエージェントにより学習をおこなうことができた．
前々から，Unityでの強化学習に興味があった．今までは，別の物理シミュレータで実験をしていたが，細かな設定で難があったり，ライセンス等で使いづらいところもあった．今回は，Unityを使っての環境ということで，Unityについての理解も少し深まり，非常に道が広がった感じで，得した気分である．まだ，サンプルを再現しての理解しかしていないため，ここから実際に自分のロボットを学習できるように，シーンの作成とプログラムを作っていきたい．

## 参考文献

http://itoshi.main.jp/tech/2022/03/06/unity_ml_agents_newenv/

https://github.com/Unity-Technologies/ml-agents/blob/release_18_docs/docs/Learning-Environment-Create-New.md

https://xr-hub.com/archives/12431