【Unity】強化学習のチュートリアルをやってみた(ML-Agents v0.8.1)

# はじめに
この記事は、
[ml-agents/Learning-Environment-Create-New.md at master · Unity-Technologies/ml-agents · GitHub](https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Learning-Environment-Create-New.md)
に従って実際に強化学習のチュートリアルをやってみた、というだけのものです。

# 実行環境

- macOS Mojave 10.14.5
- Unity 2019.1.4f1
- ML-Agents Beta 0.8.1

# ML-Agentsの導入

以下のリンク等を参考にしてML-Agentsを導入してください。

- [ml-agents 0.8.0 で自分のプロジェクトで機械学習させる (Mac)(導入編) - Qiita](https://qiita.com/warapuri/items/2fb7211a946e6f607bf5)

- [ml-agents/Installation.md at master · Unity-Technologies/ml-agents · GitHub](https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Installation.md)

- [Unity：ML-Agents 2019年04月(ver0.7.0)での使い方](https://www.fast-system.jp/unity：ml-agents-2019年04月ver0-7-0での使い方/)

- [ml-agents Installation](https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Installation.md) 

# プロジェクトの準備

1. Unityで`RollerBall`という名前のプロジェクトを作成します。
2. ProjectSetting -> Player -> Configuration で**Scripting Runtime Version**が **.NET 4.x Equivalent**になっていることを確認します。
3. `ml-agents-master`フォルダをコピーして`RollerBall/Assets/`にペーストします。

# 環境の作成

### Floorの作成
1. Planeの3D Objectを作成し、`Floor`という名前を付けます。
2. `Floor`のTransformを`Position = (0, 0, 0)`,` Rotation = (0, 0, 0)`,` Scale = (1, 1, 1)`にします。
3. `Floor`のMesh RendererでMaterialを`LightGridFloorSquare`に変更します。
<img width="361" alt="スクリーンショット 2019-05-28 午後6.45.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/4ef1ec6b-1214-3ac3-efd5-b4e500b8f65d.png">


### Targetの作成

1. Cubeの3D Objectを作成し、`Target`という名前を付けます。
2. `Target`のTransformを`Position = (3, 0.5, 3)`, `Rotation = (0, 0, 0)`,` Scale = (1, 1, 1)`にします。
3. `Floor`と同様に`Target`のMaterialを`Block`に変更します。

### Agentの作成

1. Sphereの3D Objectを作成し、`RollerAgent`という名前を付けます。
2. `RollerAgent`のTransformを`Position = (0, 0.5, 0)`, `Rotation = (0, 0, 0)`, `Scale = (1, 1, 1)`にします。
3. これまでと同様に`RollerAgent`のMaterialを`CheckerSquare`に変更します。
4. `Rigidbody`を`RollerAgent`にAdd Componentします。

<img width="622" alt="スクリーンショット 2019-05-28 午後7.47.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/290cc089-5141-67b2-13b2-b883628d7ccc.png">


### Academyの作成

Create Emptyで空のGameObjectを作成し、`Academy`という名前を付けます。

### Academyの実装

1. Create -> C# Scriptで`RollerAcademy.cs`を作成します。
2. `RollerAcademy.cs`を`Academy`にAdd Componentします。
3. `RollerAcademy.cs`を次のように編集します。


```cs
using MLAgents;

public class RollerAcademy : Academy { 

}
 ```

### Brainの追加

1. Assets -> Create -> ML-Agents で`Learning Brain`を作成し、`RollerBallBrain`と名前を付けます。
2. 同様にAssets -> Create -> ML-Agentsで`Player Brain`を作成し、`RollerBallPlayer`と名前を付けます。


### Agentの実装

1. Create -> C# Scriptで`RollerAgent.cs`を作成し、`RollerAgent`にAdd Componentします。
2. `RollerAgent.cs`を次のように編集します。

```cs 
using MLAgents;

public class RollerAgent : Agent
{

}
```

今回の簡単な状況では`Academy`は使用せずに、`Agent`にのみ処理を担当させます。

### Agentの実装(続き)

`RollerAgent.cs`を次のように編集します。

```cs 
using UnityEngine;
using MLAgents;

public class RollerAgent : Agent
{
    Rigidbody rBody;
    void Start () {
        rBody = GetComponent<Rigidbody>();
    }

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

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

インスペクタ のTargetに`Target`オブジェクトを代入します。

ここでは次のことを行なうための処理を書いています。

- `RollerAgent`が`Target`に到着した時、`Done()`を呼び出し、そのターン(エピソード)を終了させる。
- `RollerAgent`が`Target`に到着した時、`Target`をランダムな場所に移動させる。
- `RollerAgent`が`Floor`から落ちた時、その`RollerAgent`を`Floor`の上に戻す。


### 観測

今回、`RollerAgent`が集めて`RollerBallBrain`に送る情報は次のものになります。

- `Target`の位置  
  `AddVectorObs(Target.position);`
- `RollerAgent`の位置  
  `AddVectorObs(this.transform.position);`
- `RollerAgent`の速度(`RollerAgent`が`Target`を通り越したり、`Floor`から落ちないようにするために必要です)  
  `AddVectorObs(rBody.velocity.x);`  
  `AddVectorObs(rBody.velocity.z);`

よって情報を集めて`RollerBallBrain`へ送る処理を担当する、`CollectObservations()`メソッドは次のようになります。

```cs
public override void CollectObservations()
{
    // Target and Agent positions
    AddVectorObs(Target.position);
    AddVectorObs(this.transform.position);

    // Agent velocity
    AddVectorObs(rBody.velocity.x);
    AddVectorObs(rBody.velocity.z);
}
```

この`CollectObservation()`メソッドを`RollerAgent.cs`に追加します。

```cs 

using UnityEngine;
using MLAgents;

public class RollerAgent : Agent
{
    Rigidbody rBody;
    void Start()
    {
        rBody = GetComponent<Rigidbody>();
    }

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

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

    public override void CollectObservations()
    {
        // Target and Agent positions
        AddVectorObs(Target.position);
        AddVectorObs(this.transform.position);

        // Agent velocity
        AddVectorObs(rBody.velocity.x);
        AddVectorObs(rBody.velocity.z);
    }
}
```

### 報酬

`RollerAgent`が`Target`に到着した時、`RollerAgent`に`SetReward()`メソッドを使って1.0の報酬を与え、`Done()`メソッドを実行して終了します。

```cs 

float distanceToTarget = Vector3.Distance(this.transform.position,
                                          Target.position);
// Reached target
if (distanceToTarget < 1.42f)
{
    SetReward(1.0f);
    Done();
}
```

`Floor`から落ちた場合も`Done()`メソッドを実行して終了します。

```C# 
// Fell off platform
if (this.transform.position.y < 0)
{
    Done();
}
```

### AgentAction()メソッドの作成
以上より、`AgentAction()`メソッドは次のようになります。

```C# 
public float speed = 10;
public override void AgentAction(float[] vectorAction, string textAction)
{
    // Actions, size = 2
    Vector3 controlSignal = Vector3.zero;
    controlSignal.x = vectorAction[0];
    controlSignal.z = vectorAction[1];
    rBody.AddForce(controlSignal * speed);

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

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

    // Fell off platform
    if (this.transform.position.y < 0)
    {
        Done();
    }
}
```

したがって、最終的に`RollerAgent.cs`は次のようになります。

```cs
using UnityEngine;
using MLAgents;

public class RollerAgent : Agent
{
    Rigidbody rBody;
    public float speed = 10;

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

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

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

    public override void CollectObservations()
    {
        // Target and Agent positions
        AddVectorObs(Target.position);
        AddVectorObs(this.transform.position);

        // Agent velocity
        AddVectorObs(rBody.velocity.x);
        AddVectorObs(rBody.velocity.z);
    }


    public override void AgentAction(float[] vectorAction, string textAction)
    {
        // Actions, size = 2
        Vector3 controlSignal = Vector3.zero;
        controlSignal.x = vectorAction[0];
        controlSignal.z = vectorAction[1];
        rBody.AddForce(controlSignal * speed);

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

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

        // Fell off platform
        if (this.transform.position.y < 0)
        {
            Done();
        }
    }
}
```

# Unityエディタ側の設定

1. `Academy`のインスペクターのBroadcast Hubに`RollerBallBrain`と`RollerBallPlayer`を追加します。
2. `RollerBallPlayer`を`RollerAgent`のBrainに代入します。
3. Decision Intervalを1から10に変更します。
4. `Target` GameObjectをTargetに代入します。

<img width="364" alt="スクリーンショット 2019-05-28 午後11.20.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/3a845287-b14c-df32-3a68-34dbfff0390d.png">

<img width="367" alt="スクリーンショット 2019-05-28 午後11.17.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/0779013c-26b4-ca98-7e60-a04911313e9c.png">

`RollerBallPlayer`と`RollerBallBrain`のSpace Size(Vector Observation)、Space Type、Space Size(Vector Action)をそれぞれ次のように変更します。

<img width="367" alt="スクリーンショット 2019-05-28 午後11.28.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/214d37c2-e597-2721-df84-6f71cbdea835.png">
<img width="366" alt="スクリーンショット 2019-05-28 午後11.28.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/62de1cdf-9552-e3b6-9535-bdbf21b48eed.png">

# テスト

`RollerBallPlayer`のKey Continuous Player Actionsのサイズを**4**にして、Element0からElement3までを次のように設定します。

| Element | Key | Index | Value |
|:--|:--|--:|--:|
| Element 0 | D | 0 | 1 |
| Element 1 | A | 0 | -1 |
| Element 2 | W | 1 | 1 |
| Element 3 | S | 1 | -1 |

<img width="342" alt="スクリーンショット 2019-05-28 午後11.57.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/f8208a21-df41-9654-7975-b886a0e641fe.png">

Indexは、`AgentAction()`メソッドに渡されるaction配列のindexに対応しています。 Keyを押すと`action[index]`にValueが代入されます。

これでPlayすると**WASD**キーで`RollerAgent`を私達の手で操作できます。
`RollerAgent`が`Target`に近づくと`Target`の位置がランダムな位置に移ることも、`RollerAgent`が`Floor`から落ちると中心の位置に戻ることも確認できます。

![Playertest.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/a2a094e9-1781-a0be-5571-daa8cf7d6c19.gif)

# 学習

`RollerAgent`のBrainを`RollerBallPlayer`から`RollerBallBrain`に切り替えます。

<img width="307" alt="スクリーンショット 2019-05-29 午前11.26.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/1a38d051-a3ca-7957-3ad7-3f2fb005aec0.png">

`Academy`にアタッチされている`RollerAcademy`で、Controlにチェックをします。

<img width="306" alt="スクリーンショット 2019-05-29 午前11.31.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/c5002b69-ff24-bca9-f2df-9f1de2c7a482.png">

ターミナルで`RollerBall/Assets/`に移動して、

```
$ mlagents-learn ml-agents-master/config/trainer_config.yaml  --run-id=RollerBall-1 --train
```

を実行します。

次のように表示されたらUnityエディタでPlayボタンを押します。

```
INFO:mlagents.envs:Start training by pressing the Play button in the Unity Editor.
```

次のように表示されたら無事に学習が開始したことになります。(今回は50000steps学習させます)

```
INFO:mlagents.envs:
'Academy' started successfully!
Unity Academy name: Academy
        Number of Brains: 2
        Number of Training Brains : 1
        Reset Parameters :
		
Unity brain name: RollerBallBrain
        Number of Visual Observations (per agent): 0
        Vector Observation space size (per agent): 8
        Number of stacked Vector Observation: 1
        Vector Action space type: continuous
        Vector Action space size (per agent): [2]
        Vector Action descriptions: , 
Unity brain name: RollerBallPlayer
        Number of Visual Observations (per agent): 0
        Vector Observation space size (per agent): 8
        Number of stacked Vector Observation: 1
        Vector Action space type: continuous
        Vector Action space size (per agent): [2]
        Vector Action descriptions: , 
2019-05-29 12:13:39.143382: I tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
INFO:mlagents.envs:Hyperparameters for the PPO Trainer of brain RollerBallBrain: 
	batch_size:	1024
	beta:	0.005
	buffer_size:	10240
	epsilon:	0.2
	gamma:	0.99
	hidden_units:	128
	lambd:	0.95
	learning_rate:	0.0003
	max_steps:	5.0e4
	normalize:	False
	num_epoch:	3
	num_layers:	2
	time_horizon:	64
	sequence_length:	64
	summary_freq:	1000
	use_recurrent:	False
	summary_path:	./summaries/RollerBall-1-0_RollerBallBrain
	memory_size:	256
	use_curiosity:	False
	curiosity_strength:	0.01
	curiosity_enc_size:	128
	model_path:	./models/RollerBall-1-0/RollerBallBrain
INFO:mlagents.trainers: RollerBall-1-0: RollerBallBrain: Step: 1000. Time Elapsed: 11.076 s Mean Reward: 0.178. Std of Reward: 0.382. Training.
```

学習が終わると、推論モデルである`RollerBallBrain.nn`ファイルが`Assets/models/RollerBall-1-0/`に生成されるので、これを`RollerBallBrain`の**Model**にセットします。

<img width="306" alt="スクリーンショット 2019-05-29 午後0.27.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/e96477d6-8ccc-7f31-2c34-fea1697a805b.png">

また、`RollerAcademy`の**Control**のチェックを外します。<img width="300" alt="スクリーンショット 2019-05-29 午後0.32.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/b68f9d2d-684c-cd05-43f5-6f1cd0224c87.png">

これでUnityのエディタでPlayボタンを押すと、`RollerAgent`が自発的に動きます。

あまり賢いとは言えませんが、`Target`に向かっていこうとする意思は感じられます。

![RollerBallAI.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/8139829c-9de1-6410-50c7-f7db56057dc2.gif)

ターミナルで

```
$ tensorboard --logdir=summaries
```
を実行して、http://localhost:6006 をブラウザで開くと、今回の学習状況を詳細に調べることができます。

<img width="704" alt="スクリーンショット 2019-05-29 午後1.07.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/7b3a9ce4-99f9-cde4-148d-708e6ab1a9e3.png">

<img width="709" alt="スクリーンショット 2019-05-29 午後1.07.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/fb60fe2a-c32c-6dd1-fbb0-81704f76ec6c.png">


<img width="704" alt="スクリーンショット 2019-05-29 午後1.07.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/ca6dbe28-456b-2dc6-525b-d44f6b3389e2.png">


# 参考
- [ml-agents/Learning-Environment-Create-New.md at master · Unity-Technologies/ml-agents · GitHub](https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Learning-Environment-Create-New.md)
- [ml-agents 0.8.0 で自分のプロジェクトで機械学習させる (Mac)(導入編) - Qiita](https://qiita.com/warapuri/items/2fb7211a946e6f607bf5)
- [Training ML-Agents](https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Training-ML-Agents.md#training-config-file)