ついに強化学習を体験してみる備忘録

# ついにやります強化学習
https://github.com/Unity-Technologies/ml-agents/blob/release_20/docs/Learning-Environment-Create-New.md
GitHubを参考にやっていきます。また英語ですね。
当然ですが英語が基本なので、もう諦めて英語頑張って解読しましょう。

# UnityProjectの作成とパッケージマネージャーのインストール
ML-agentsがインストールされたUnityProjectを作成してください。
ここまでは前回やりましたね。
わからなくなったら前回の記事を参照してください。
ML-Agentsの環境構築の備忘録(privateなので現状Mokoしか閲覧できませんが）

# Unityのオブジェクト配置
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/0d30fb35-3193-586f-aa6e-7172c8332f2f.png)
上の状態を目指して作成していきます。
Unityに慣れているひとなら朝飯前かもしれませんが、念の為少しだけ記述しておきます。

1.空（から）のオブジェクト(TrainArea)を作成。(position = (0,0,0))
2.Planeを作成
3.Cube(name = Target),Sphere(name = Agent)を作成(位置情報は("自由",0.5,"自由")にしておくと見やすいです）
4.AgentにはRigidbodyを`コンポーネントを追加`しておいてください。
5.緑色と赤色のMaterialを作成し、Cubeには緑、Sphereには赤をアタッチ
6.Target,Agent,PlaneをTrainArea(からのオブジェクト)の子オブジェクトに設定

以上です。本当に念の為って感じですね。
先に少し話しておくと、Positionは正直なんでもいいです、スクリプトで上書きするので。
強いて言えば空のオブジェクトはしっかり`Vector3.zero = (0,0,0)`に設定しておくべきかもしれません。

# C#スクリプトを書いていく
やっとコーディングができますね。結構長い道のりだった気がします。
コードの完成形？のようなものは最初に挙げたGitHubにあるのでそれを参照に作るのが手っ取り早いですが、英語文献なので少しだけ自分なりの解釈を書いておこうと思います。
ちなみにAgentオブジェクト（Sphere)(赤色の球体）にスクリプトをアタッチします、とりあえず切りの良いところまで書いてからアタッチすることをおすすめします。
（理由としては、Agentクラスを継承した状態のスクリプトをアタッチすれば、Behavior ParametorというML-Agentsを使うために必要なコンポーネントがAgentオブジェクトに自動で追加されるからです。もしも先に追加してしまったら、コンポーネントを追加からBehavior Parametorを検索して追加してください。）

スクリプトの全容を先に提示しておきます。
```RollerAgent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//MLAgentを使うために必要なライブラリをインポート
using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;

public class RollerAgent : Agent
{
    //Update()関数は排除するが、Start()関数は残しておく。
    Rigidbody rb;
    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    public Transform target;

    public override void OnEpisodeBegin()//繰り返す初期地点。リスポーン状態にするための関数Startに近い
    {
        if(this.transform.localPosition.y < 0)
        {
            this.rb.angularVelocity = Vector3.zero;//回転速度状態を0に初期設定
            this.rb.velocity = Vector3.zero;//速度情報を0に初期設定
            this.transform.localPosition = new Vector3(0,0.5F,0);//ボールの位置を初期設定
        }

        target.localPosition = new Vector3(Random.value * 8 -4,0.5F,Random.value * 8 -4);
        //Random.range(-4.0F,4.0F)と同じコーディング
    }

    public override void CollectObservations(VectorSensor sensor)//何をブレインに送信するかの関数
    {
        //TargetとAgentの位置情報を観測し追加。
        sensor.AddObservation(target.localPosition);//データ数は座標なので3つ
        sensor.AddObservation(this.transform.localPosition);//上同様にデータ数は3つ

        //Agentの移動情報（速度情報）を観測し追加。
        sensor.AddObservation(rb.velocity.x);
        sensor.AddObservation(rb.velocity.z);

        //累計8つのデータを送信する必要がある→Space Sizeを8に変更（Behavior Parametor)
    }

    public float forceMult = 10F;
    public override void OnActionReceived(ActionBuffers actions)//どんな動きをするか、そしてその行動に対してどのような点数（報酬）を与えるかの関数
    {
        //rbのx,zの２つの情報を操作したいのでContinuous Actionの数字を2に設定しておく(Behavior Parametor)
        //Vector3を使用しても良い。Vector3 controlSignal.x = actions.~~~[0]のように。(ただしVector3は初期化すること。)
        float x = actions.ContinuousActions[0];//Continuousは小数などの細かい値も連続的に取ってくるイメージ。離散的なものも存在するらしい。
        float z = actions.ContinuousActions[1]; 
        
        //実際に動かす。
        rb.AddForce(new Vector3(x,0,z) * forceMult);

        //targetとの距離を観測
        float dist = Vector3.Distance(this.transform.localPosition,target.localPosition);

        if(dist < 1.42F)//距離が一定以上に縮まったら、1.0という報酬を与える。
        {
            SetReward(1.0F);
            EndEpisode();//報酬を与えたらその実験を終了
        }
        else if (this.transform.localPosition.y < 0)//落下したらということ
        {
            EndEpisode();//報酬を与えず終了
        }
    }

    public override void Heuristic(in ActionBuffers actionsOut)//人がテストするときに用いられる関数
    {
        //Huristic Onlyで使えるようになるっぽい。(Behavior Parametorの設定)
        var continuousActionsOut = actionsOut.ContinuousActions;
        continuousActionsOut[0] = Input.GetAxis("Horizontal");//OnActionReceivedとは逆でユーザの入力を参照している。
        continuousActionsOut[1] = Input.GetAxis("Vertical");
    }
}

```
追加するデータの数だけAgentオブジェクトにアタッチされているBehaviorParametorの`SpaceSize`を変更しなければならない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/513a067c-b94a-cddc-5680-7807396d78e8.png)
他にもアクション（動く部分（変数））の数も指定しなければならない。
今回はRigidbodyのx,zの２つを操作するので、Continuous Actionの数字を2に設定しておく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/13fdedda-059f-4e1d-6c35-d31d54e3ea49.png)
Heuristic Onlyを使うときは、Decision Requesterをアタッチしてください。そしてPeriodを10にしておいてください。まぁなんでもいいですが、数字が小さいほど感度が高いです。1なら毎フレームかな？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/68aa163b-4073-07bb-c47f-6a5dea4781f6.png)
Heuristic Onlyにするのを忘れずに
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/6264560f-55e5-04ed-edfe-d0b5923b83e4.png)

# 学習に関するパラメータの設定を行う。
ML-Agentsのインストール先（前回`全て展開`した場所）から`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
      beta_schedule: constant
      epsilon_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
```
この情報をコピペしてください。
ちなみにこの情報は学習に関するパラメータです。
Configの中のppoフォルダのなかにはサンプルシーンのパラメータがあります。それを見てみるのもおもしろいです。
ファイル名はなんでもいいですが、`RollerConfig`にしました。
そしたら拡張子を`.yaml`にしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/3b728acf-2cd8-ba6c-6ac8-152a4d13fa46.png)
ファイル名拡張子オプションをONにしておくと、拡張子を変更しやすいです。

そしたらUnityを開いて、AgentオブジェクトのBehavior Parametor のBehavior Nameを"RollerBall"にしてください。これは先程の`.yaml`ファイルの上から二番目の名前と一致しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/51b3d8b9-0696-72ad-9323-286750eceb06.png)

# Anaconda Promptで操作する
Anaconda Promptを開いてください。
そしたら以下のコマンドを打ってください。
```
conda activate mlagents
cd "~~~~~/ml-agents-release_20"//全て展開をした場所に移動してください。
mlagents-learn config/RollerConfig.yaml --run-id=RollerBall
```
config/"この部分"は自分で別の名前にしている人は自分でそこは変えてください。
そしたら以下のような画面になると思うので、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/51cc2ec8-dcb8-2877-f3c0-bb131bb28d57.png)
UnityProjectを再生すると動き始めます。学習開始です。

Anaconda Navigaterから新しいプロンプトを作成してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/a10776f0-57d8-5885-8d2d-1ecefdc239d9.png)
mlagentsからOpen promptして、
```
tensorboard --logdir results --port 6006
```
を実行してください。
そしたら、httpから始まるところへWebサイトで飛んでください
するとグラフとかが見れると思います。

そしたらAnacondaPromptからctrl+cで一旦止めましょう。
あと先程のコマンドにあったidは重複存在できないため、おなじIDを使いたいときは、
```
mlagents-learn config/RollerConfig.yaml --run-id=RollerBall --force
```
をすればよいですが、毎回上書きされるので、Idを変えるのが効果的ではあります。
ただ今は練習ですし、いちいちIdを変えていたら面倒なので、--forceオプションで強制上書きします。


このままでは学習速度が非常に遅いため、Prefab化してたくさん同時並行で学習させます。

# Prefab化して学習速度を高速化
TrainAreaをPrefabにします。
プロジェクトにドラックアンドドロップしてください。そうしたら青くなると思うのでそれでOKです。
TrainAreaを選択して、ctrl+D(下にコピー)してください。
何個作るかは自身のPCのスペックに依存すると思うので少しずつ増やすとかでもいいです。
X軸、Z軸を15くらいずつずらして並べてください。
自分は16個ほど作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/cdd09b7f-40d7-c250-e1b6-6e91c0ae0b15.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/ccccd64b-f9dc-98bc-9a0e-65d1adf9d5e4.png)

そしたら先程と同じように実行してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536729/10851c32-7deb-5e51-01fb-26031ee4d3bf.png)
こんな感じになりました。
適当に実行させて、終了させたらmlagentsのフォルダから`results`へ行って、今回指定したIDのフォルダへ行くと、`.onnx`という拡張子のファイルが生成されていると思います、それが今後使用できる結果になります。

# 学習済みモデルの使用
さっきの`.onnx`という拡張子のファイルをUnityProjectのAssetsにコピペしてください。
TrainAreaのPrefab設定へ行って、BehaviorParametorのModelにアタッチしてください。
そしたら学習済みモデルが作成できたということになります。
再生すれば動くと思います。(unityの再生のみでOK)

# 参考文献
https://www.youtube.com/watch?v=JidBpKgGoec

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