【Unity強化学習】自作ゲームで強化学習

**※2018年6月、ML-Agents v0.4に対応するように修正いたしました。**

#Unity ML-Agents

Unity ML-Agents というUnityを使って強化学習できるライブラリがあります。

[公式](https://github.com/Unity-Technologies/ml-agents)のほうにチュートリアルも載っているのですぐ試してみることができます。

チュートリアルをやってみると、ボールを落とさないように板を動かす学習モデルができると思います。

![ML_Agent_3DBall.gif](https://qiita-image-store.s3.amazonaws.com/0/225485/9936c6c8-5af4-1228-ea82-12a8be2a6d86.gif)

今回は自分でゲームを作ってUnity ML-Agentsで強化学習していきたいと思います。

作るゲームは強化学習では王道ともいえる倒立振子です。
棒を倒さないようにするやつですね。

![ML_Agent_CartPole.gif](https://qiita-image-store.s3.amazonaws.com/0/225485/ef470ee6-b1c2-0543-4371-a0ece9702c1b.gif)

#ゲームを作る

[公式](https://github.com/Unity-Technologies/ml-agents)の方から、ML-Agents をダウンロードしてきます。

unity-environmentをUnityで開きます。

<img width="584" alt="SnapCrab_NoName_2018-2-25_15-16-59_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/ca04f4f1-c125-25e0-e87a-8acfa468147f.png">

CubeとCylinderを作ります。名前はCartとPoleに替えます。
適当に色を付け、Transformは次のように設定します。

<img width="257" alt="SnapCrab_NoName_2018-2-25_15-34-39_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/1c58940c-32d4-447d-1f3e-4c47c6184b67.png">

<img width="257" alt="SnapCrab_NoName_2018-2-25_15-34-24_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/bd578b78-103b-2563-db69-09dbe9071883.png">


<img width="385" alt="SnapCrab_NoName_2018-2-25_15-28-16_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/50b3607b-d5b0-b2e9-d089-110db8cb24f1.png">

それぞれにRigidbodyをとりつけ、CartはIs Kinematicにチェックを入れ、PoleはDragを0.5にします。

Poleを選択し、Component>Physics>Hinge Jointを付けます。

<img width="247" alt="SnapCrab_NoName_2018-2-25_15-49-2_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/5b5649e2-60b8-d770-421d-6490d370e82c.png">

Connected BodyにCartを入れ、他の設定は画像にようにします。

#ML-Agents

![ML_Agents.png](https://qiita-image-store.s3.amazonaws.com/0/225485/2320647e-75cb-0dc7-284d-5137c0cc5d66.png)

ML-Agentsでは画像のようにAgent,Brain,Academyで制御し、外部のPythonを利用して学習を行っていくようです。

Unity Hierarchyで空のゲームオブジェクトを3つ作り、Game,CartPoleBrain,CartPoleAcademyと名付けます。
そして、画像のように配置します。

<img width="129" alt="SnapCrab_NoName_2018-2-25_19-42-19_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/67cc0d78-691c-da86-24c5-a08540fe9965.png">

ML-Agents>Template>ScriptsにTemplateAcademy,TemplateAgentsが入っているので、それらをCartPoleAcademy,CartPoleAgentsに変更、内部のクラス名も同じように変えて、HierarchyのCartPoleAcademy（オブジェクト）とCart（オブジェクト）にそれぞれ取り付けます。

<img width="458" alt="SnapCrab_NoName_2018-2-25_19-55-57_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/1d08dfd1-d282-c989-c592-29b9c4c20f36.png">

<img width="1079" alt="SnapCrab_NoName_2018-2-25_20-1-31_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/e8527acf-2680-fe2b-f90d-4b379ef3bd37.png">

ML-Agents>ScriptsにBrainがあるのでこれをCartPoleBrain（オブジェクト）に取り付けます。

#Cart Pole Agents

CartPoleAgentsのスクリプトを以下のように書き換えます。

```c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MLAgents;

public class CartPoleAgent : Agent
{
    public GameObject pole;
    Rigidbody poleRB;
    float angle_z;
    float cart_x;
    float anguVelo_z;

    public override void InitializeAgent()
    {
        base.InitializeAgent();
        poleRB = pole.GetComponent<Rigidbody>();
    }

    void FixedUpdate()
    {
        
    }

    public void StateTransition()
    {
        
        //ポールの角度が-30~30度の時、報酬+0.01、それ以上傾いたら報酬-1
        if (-30f < angle_z && angle_z < 30f)
        {
            AddReward(0.01f);
        }
        if ((-180f < angle_z && angle_z < -30f) || (30f < angle_z && angle_z < 180f))
        {
            AddReward(-1f);
            Done();
        }

        //カートの位置が-3~3の範囲を越えたら報酬-1
        if (cart_x < -3f || 3f < cart_x)
        {
            AddReward(-1f);
            Done();
        }
    }

    public override void CollectObservations()
    {
        AddVectorObs(cart_x);
        AddVectorObs(angle_z);
        AddVectorObs(anguVelo_z);
    }

    public void MoveAgent(float[] act)
    {
        //0-Left, 1-Right
        int action = Mathf.FloorToInt(act[0]);

        if (action == 0)
        {
            transform.Translate(-0.05f, 0, 0);
        }
        if (action == 1)
        {
            transform.Translate(0.05f, 0, 0);
        }
    }

    public override void AgentAction(float[] vectorAction, string textAction)
    {
        //行動
        MoveAgent(vectorAction);
        //カートの位置、ポールの角度と角速度
        cart_x = transform.localPosition.x;
        angle_z = pole.transform.localRotation.eulerAngles.z;
        //angle_zを-180~180度に変換
        if (180f < angle_z && angle_z < 360f)
        {
            angle_z = angle_z - 360f;
        }
        anguVelo_z = poleRB.angularVelocity.z;
        //状態の遷移
        StateTransition();
    }

    public override void AgentReset()
    {
        //エージェントの状態をリセット
        transform.localPosition = new Vector3(0f, 0f, 0f);
        pole.transform.localPosition = new Vector3(0f, 1f, 0f);
        pole.transform.localRotation = Quaternion.Euler(0f, 0f, 0f);
        poleRB.velocity = new Vector3(0f, 0f, 0f);
        //ポールにランダムな傾きを与える
        poleRB.angularVelocity = new Vector3(0f, 0f, Random.Range(-0.5f, 0.5f));

    }

    public override void AgentOnDone()
    {

    }
}


```

Brain, Agentを次のように設定します。

<img width="256" alt="SnapCrab_NoName_2018-5-19_15-55-42_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/8b7deecc-73e5-38a5-15ac-661891036bbc.png">

<img width="256" alt="SnapCrab_NoName_2018-5-19_15-53-57_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/2c9e2742-fbb4-883b-8627-078482c1f957.png">

きちんと動いて、角度が一定以上になったらリセットされればOKです。
そしたらGame(オブジェクト)を複製して16個にしましょう。

<img width="819" alt="SnapCrab_NoName_2018-2-25_20-52-0_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/dc2a3d81-c5c2-b4c0-e6c4-cf2e8bc204a0.png">

これで学習準備が整いました。

#ビルドして学習

1.CartPoleBrainのBrainTypeをExternalにする
2.画面上部のバーから Edit > Project Settings > Player

Resolution and Presentation > Run in Background がチェックされていることを確認
Resolution and Presentation > Display Resolution Dialog がDisabledににセットされていることを確認

3.File > Build Settingsから内容を確認してビルド。保存先は公式からダウンロードしたml-agents-master > pythonです。名前はCartPoleにしておきましょう。

pythonフォルダでAnacondaプロンプトを開き、次のように入力します。

```python
python learn.py CartPole --train
```

trainer.configというファイルを開くとハイパーパラメータなどの設定ができます。

<img width="326" alt="SnapCrab_NoName_2018-5-19_16-14-18_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/a058f3e7-df9e-bf09-423f-2cfd6ec80b30.png">

基本的にデフォルトのままで、max_stepsだけ20万回にしました。

学習が完了すると、python/models/ppo/ フォルダにCartPole.bytesというファイルが生成されています。
これをUnityのAssetsに持っていきます。

Edit > Project Settings > Player の Other Settings欄へ行き、
Scripting Runtime Version を Experimental(.NET4.6Equivalent)にする
Scripting Define Symbols に ENABLE_TENSORFLOW　と入力する

<img width="230" alt="SnapCrab_NoName_2018-2-25_22-17-39_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/be578f3c-43ef-9af0-96fe-554369b1a652.png">

また、[Using TensorFlowSharp in Unity (Experimental)](https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Using-TensorFlow-Sharp-in-Unity.md)
こちらからTensorflowSharpのUnityPackageをダウンロードして、環境にインポートしましょう。
これは、Unity環境でTensorflowを使うためのプラグインです。

<img width="691" alt="SnapCrab_NoName_2018-5-19_16-28-26_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/3f1e05c5-8ef4-639b-7297-ac7a4f35eca4.png">

CartPoleBrain(オブジェクト)のBrain Type をInternalにする
先ほどのCartPole.bytesファイルをGraph Modelにセット。

<img width="252" alt="SnapCrab_NoName_2018-5-19_16-34-24_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/09a44f6c-fe3b-d9a3-8a97-8aa92e05630b.png">

これで実行してみましょう。

#結果

![CartPole.gif](https://qiita-image-store.s3.amazonaws.com/0/225485/0d767a1a-1ec5-1d3c-7eda-d2a6f1bcbbff.gif)

20万回学習した結果、倒れることはなくなりました。

#参考

1. [Unityで強化学習していたAgentのソースコードを読む話](http://ensekitt.hatenablog.com/entry/2017/12/27/123000)
2. [Unityで強化学習するけど脱サンプルするためにサンプル以下の物を作った話](http://ensekitt.hatenablog.com/entry/2018/01/10/200000)
3. [【Unity】Unityで機械学習する「ML-Agent」を色々と試して得た知見とか](http://tsubakit1.hateblo.jp/entry/2018/02/18/233000)
4. [Unityが機械学習のライブラリを公開したので使ってみる　(和訳付き)](https://qiita.com/Ao_i/items/18dec197434c7f853a92)
5. [プルプルしている倒立振子を安定させる話](http://ensekitt.hatenablog.com/entry/2018/01/22/200000)