Unity_Lesson

4-6 Curiosity

「Curiosity」は未知の状態に訪れたことに対して与えられる「報酬シグナル」です。「報酬」が少ない、または希少な環境では、エージェントは「報酬」を見つけることができないため、学習することができません。「Curiosity」を使うことで、エージェントに好奇心を持たせ、未知の状態への探索を促すことができます。


Curiosityの学習環境の準備

「4-4 RaycastObservation」で作成したものをカスタマイズして、コースを反時計回りに周回することを学ぶ学習環境を作成し、「Curiosity」で学習します。

今回の学習環境の強化学習の要素は次のとおりです。

項目 説明
観察 ・Vector Observation(サイズ2)
0:RollerAgentのX速度,1:RollerAgentのZ速度
・Raycast Observation
行動 ・Discrete(サイズ1)
0:移動(0:なし,1:前進,2:後進,3:左回転,4:右回転)
報酬 ・ステップ毎に-0.001
・反時計回りに1周したときに+2.0(エピソード完了)
決定 ・10ステップ毎


RaycastAgentの設定

① 「RaycastAgent」のタグに「Player」という名前のタグを設定

② 「Behavior Parameters」を設定

③ 「RayPerceptionSensor3D」を設定

④ スクリプト「RaycastAgent」を設定

RaycastAgent.cs

using System.Collections.Generic;
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Policies;

// RaycastAgent
public class RaycastAgent : Agent
{
    Rigidbody rBody;
    int lastCheckPoint; // 最終チェックポイント
    int checkPointCount; // チェックポイント通過数

    // ゲームオブジェクト生成時に呼ばれる
    public override void Initialize()
    {
        this.rBody = GetComponent<Rigidbody>();
    }

    // エピソード開始時に呼ばれる
    public override void OnEpisodeBegin()
    {
        // 周回数のリセット
        this.lastCheckPoint = 0;
        this.checkPointCount = 0;
    }

    // 観察取得時に呼ばれる
    public override void CollectObservations(VectorSensor sensor)
    {
        sensor.AddObservation(rBody.velocity.x); // RaycastAgentのX速度
        sensor.AddObservation(rBody.velocity.z); // RaycastAgentのZ速度
    }

    // 行動実行時に呼ばれる
    public override void OnActionReceived(ActionBuffers actionBuffers)
    {
        // RaycastAgentに力を加える
        Vector3 dirToGo = Vector3.zero;
        Vector3 rotateDir = Vector3.zero;
        int action = actionBuffers.DiscreteActions[0];
        if (action == 1) dirToGo = transform.forward;
        if (action == 2) dirToGo = transform.forward * -1.0f;
        if (action == 3) rotateDir = transform.up * -1.0f;
        if (action == 4) rotateDir = transform.up;
        this.transform.Rotate(rotateDir, Time.deltaTime * 200f);
        this.rBody.AddForce(dirToGo * 0.4f, ForceMode.VelocityChange);

        // ステップ毎の報酬
        AddReward(-0.001f);
    }

    // チェックポイントに衝突時に呼ばれる
    public void EnterCheckPoint(int checkPoint)
    {
        // 次のチェックポイントに衝突
        if (checkPoint == (this.lastCheckPoint+1)%4)
        {
            this.checkPointCount++;

            // ゴール
            if (this.checkPointCount >= 4)
            {
                AddReward(2.0f);
                EndEpisode();
            }
        }
        // 前のチェックポイントに衝突
        else if (checkPoint == (this.lastCheckPoint-1+4)%4)
        {
            this.checkPointCount--;
        }

        // 最終チェックポイントの更新
        this.lastCheckPoint = checkPoint;
    }

    // ヒューリスティックモードの行動決定時に呼ばれる
    public override void Heuristic(in ActionBuffers actionBuffers)
    {
        var actionsOut = actionBuffers.DiscreteActions;
        actionsOut[0] = 0;
        if (Input.GetKey(KeyCode.UpArrow)) actionsOut[0] = 1;
        if (Input.GetKey(KeyCode.DownArrow)) actionsOut[0] = 2;
        if (Input.GetKey(KeyCode.LeftArrow)) actionsOut[0] = 3;
        if (Input.GetKey(KeyCode.RightArrow)) actionsOut[0] = 4;
    }
}

⑤ 「RaycastAgent」を設定


チェクポイント✖︎4の設定

CheckPoint.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// チェックポイント
public class CheckPoint : MonoBehaviour
{
    public RaycastAgent agent;
    public int checkPointId;

    // 他のオブジェクトとの交差開始時に呼ばれる
    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.tag == "Player")
        {
            this.agent.EnterCheckPoint(this.checkPointId);
        }
    }
}


Curiosityの学習設定ファイルの設定

今回は「PPO」で学習します。以下のように、ハイパーパラメータを設定してください。

CuriosityEx.yaml

behaviors:
  CuriosityEx:
    # トレーナー種別
    trainer_type: ppo

    # 基本
    max_steps: 10000000
    time_horizon: 128
    summary_freq: 10000
    keep_checkpoints: 5

    # 学習アルゴリズム
    hyperparameters:
      # PPOとSAC共通
      batch_size: 128
      buffer_size: 2048
      learning_rate: 0.0003
      learning_rate_schedule: linear

      # PPO用
      beta: 0.01
      epsilon: 0.2
      lambd: 0.95
      num_epoch: 3

    # ニューラルネットワーク
    network_settings:
      normalize: false
      hidden_units: 512
      num_layers: 2

    # 報酬
    reward_signals:
      # 環境報酬
      extrinsic:
        gamma: 0.99
        strength: 1.0

      # Curiosity報酬
      curiosity:
        gamma: 0.99
        strength: 0.005
        network_settings:
          hidden_units: 64
        learning_rate: 0.0003


Curiosity報酬のハイパーパラメータ

「reward_signals:」下の「curiosity:」下にCuriosity報酬のパラメータを設定します。


Curiosityの学習の実行

mlagents-learn .\config\sample\CuriosityEx.yaml --run-id=CuriosityEx-1 --env=CuriosityEx-1 --num-envs=8

学習結果のグラフは次のとおり


Curiosity専用のグラフ

Curiosityの使用時には、「TensorBoard」でCuriosity専用のグラフも提供されています。


Curiosity Foward Lossグラフ

「Curiosity」のフォワードモデル損失関数の平均を表すグラフです。モデルが2つの観察間で実行される行動をどれだけうまく予測できるか示します。


Curiosity Inverse Lossグラフ

「Curiosity」のモデル損失の逆関数の平均グラフです。モデルが新しい観察をどれだけうまく予測できるか示します。


Curiosity Reward グラフ

「Curiosity」の平均累計報酬を表すグラフです。単純な環境の場合、選択可能な未経験行動がなくなってしまうため、Curiosityの報酬も減っていきます。 「Curiosity」報酬が発生しているタイミングと、「Extrinsic」報酬とのバランスの確認にも利用します。「Curiosity」報酬が多すぎる、または「Extrinsic」報酬が多過ぎる場合は、「strength」で調整してください。


Curiosity Value Estimate グラフ

エージェントが受け取る将来の「Curiosity」の平均累計報酬を表すグラフです。


Curiosityの「あり」と「なし」の比較

「Curiosity」の「あり」と「なし」を比較してみます。「Curiosity」なしは、以下のハイパーパラメータで学習しました。

CuriosityEx_B.yaml

behaviors:
  CuriosityEx:
    trainer_type: ppo

    max_steps: 10000000
    time_horizon: 128
    summary_freq: 10000
    keep_checkpoints: 5

    hyperparameters:
      # PPOとSAC共通
      batch_size: 128
      buffer_size: 2048
      learning_rate: 0.0003
      learning_rate_schedule: linear

      beta: 0.01
      epsilon: 0.2
      lambd: 0.95
      num_epoch: 3

    network_settings:
      normalize: false
      hidden_units: 512
      num_layers: 2

    reward_signals:
      extrinsic:
        gamma: 0.99
        strength: 1.0

学習結果のグラフは、次のとおりです。好奇心がある分、「Curiosity」ありのほうが早く報酬を見つけることができることがわかります。
今回の環境では「Curiosity」なしでも、時間をかけることで報酬を見つけることができています。