[Unity]ML-Agentsのサンプルゲームを動かしてみる(Release 12)

10 min読了の目安(約9000字TECH技術記事

概要

ML-Agentsを使って機械学習を実行し、学習結果を反映する手順をまとめています。

ここでは、公式サイトでサンプルとして上がっている「RollerBall」を例に手順をまとめています。
実際にやってみて分かりにくかったところなど補足は加えていますが、基本的な手順は公式サイトにもまとめられていますのでリンクを貼っておきます。
公式サイトはこちら(Release12版)

なお、この記事は以下の記事の続きとなっています。
[Unity]ML-Agentsを使うための環境構築(Release 12)

上記の記事でまとめている基本的な環境構築は終わっているものとして、以下進めていきます。

環境

  • macOS Catalina 10.15.6
  • Unity 2019.4.9f1
  • ML-Agents Release12
  • python 3.8.2

ポイント

  • 前の記事にも書いていますが、バージョンが違うと結構、変わっています。Release12以外のバージョンだと動かない可能性が高いので、公式サイトを参照した方が良いと思います
  • 公式サイトはこちら
    • 「Releases & Documentation」から使用するバージョンのdocsを確認してください

手順

  1. 必要なオブジェクトを配置する
  2. 必要なスクリプトを追加する
  3. 必要なコンポーネントを追加する
  4. 手動実行して動作確認する
  5. 機械学習を実行する
  6. 学習結果を反映する

学習内容の確認

実装を始める前に、学習する内容をまとめておきます。

ここでは以下の内容を学習します。

  • ボールがターゲットに向かって自動的に転がる
  • 転がれるエリアは決まっており、エリア外に出るとアウト

最終的にはこんな感じでボールが動くようになります。
学習後

1. 必要なオブジェクトを配置する

それでは必要なオブジェクトを配置していきます。

必要なオブジェクトは以下の3つです。
それぞれ以下の通りTransformを設定し、名称を変更します。

  • 床(Planeオブジェクト)
    床

  • 転がるボール(Sphereオブジェクト)
    ボール

  • ボールが向かうターゲット(Cubeオブジェクト)
    キューブ

2. 必要なスクリプトを追加する

ボールに学習に必要な情報を渡し、行動を実行するためのスクリプトを追加します。
スクリプトの解説も入れると長くなるので、折りたたんでおきます。必要であれば適宜、参照してください。

スクリプトの説明

public override void OnEpisodeBegin()

エピソードの開始時に呼ばれる処理。
EndEpisode()が実行されるとこの処理が走ります。
ここでは以下の処理を行っています。

  • ボールが落ちた場合はボールの位置を元に戻して、ターゲットをランダムな位置に配置
  • ボールが落ちていない(ターゲットにたどり着いた)場合は、ターゲットの位置のみランダムに配置

public override void CollectObservations(VectorSensor sensor)

Agentに観察値を渡す処理。
ここでは以下のデータを観察値として渡しています。

  • ターゲットの位置
  • 自分の位置
  • 自分のx速度
  • 自分のz速度

位置情報はx座標,y座標,z座標の3つを同時に渡しているので 3×2=6 個のデータです。
速度の値は1つだけなので 1×2=2 個のデータです。
つまり、合計8個のデータを渡してそれを元に学習してください、ということをここでは記述しています。

この値(=8)はあとでアタッチする「Behavior Parameters」コンポーネントの「VectorObservation - SpaceSize」にセットする値になります。

public override void OnActionReceived(ActionBuffers actionBuffers)

「行動の実行」「報酬の取得」「エピソード完了」を行う処理。

actionBuffersには、あとでアタッチする「Behavior Parameters」コンポーネントの「Continuous Actions」で指定した数の値が渡ってきます。
ここでは「2」を指定しているので、2つの値が渡ってきています。

この値を元に行動を実行し、うまくいけば報酬を与えてエピソードを終了。
うまくいかなければ無報酬でエピソードを終了しています。

public override void Heuristic(in ActionBuffers actionsOut)

手動実行時にOnActionReceivedにActionBuffersを渡す処理。
ここでは矢印キーの入力を取得して、それをOnActionReceivedに渡しています。

ここでは以下の通り新しいスクリプトを作成し、RollerAgent(ボール)にアタッチしてください。
スクリプトの名称は「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 (this.transform.localPosition.y < 0)
        {
            this.rBody.angularVelocity = Vector3.zero;
            this.rBody.velocity = Vector3.zero;
            this.transform.localPosition = new Vector3( 0, 0.5f, 0);
        }

        // ターゲットをランダムな位置に配置
        Target.localPosition = new Vector3(Random.value * 8 - 4,
                                           0.5f,
                                           Random.value * 8 - 4);
    }

    public override void CollectObservations(VectorSensor sensor)
    {
        // ターゲットと自分の位置情報
        sensor.AddObservation(Target.localPosition);
        sensor.AddObservation(this.transform.localPosition);

        // 自分の移動速度
        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);

        // 終了条件の設定
        float distanceToTarget = Vector3.Distance(this.transform.localPosition, Target.localPosition);

        // ターゲットに近づいていれば報酬を与えて終了
        if (distanceToTarget < 1.42f)
        {
            SetReward(1.0f);
            EndEpisode();
        }

        // 床から落ちていれば終了
        else if (this.transform.localPosition.y < 0)
        {
            EndEpisode();
        }
    }

    public override void Heuristic(in ActionBuffers actionsOut)
    {
	// 手動実行時の行動
        var continuousActionsOut = actionsOut.ContinuousActions;
        continuousActionsOut[0] = Input.GetAxis("Horizontal");
        continuousActionsOut[1] = Input.GetAxis("Vertical");
    }
}

アタッチしたらRollerAgentの「Target」にTargetオブジェクトをドラッグ&ドロップしておきます。

3. 必要なコンポーネントを追加する

引き続き、RollerAgent(ボール)に必要なコンポーネントを追加していきます。

RollerAgentを選択し、inspectorの「Add Component」から以下の3つのコンポーネントを追加してください。

  • Rigidbody
  • Behavior Parameters
  • Decision Requester

それぞれ以下の通り、設定しておきます。
(変更箇所は赤枠で囲っています)



コンポーネントをアタッチする時の注意点

Decision Requesterを追加する前に、Agentクラスを拡張した自作のクラス(ここではRollerAgentスクリプト)をアタッチしておいた方がいいので注意してください。

Decision Requesterは[RequireComponent(typeof(Agent))]しているので、Agentクラスのコンポーネントがなにもない状態でアタッチすると、自動的にAgentコンポーネントがアタッチされます。

AgentとRollerAgent、両方アタッチされている状態だと正常に動作しないことがあるので、ここの順番には注意してください。

これで学習に必要な設定は完了です!
次は手動で実行し、きちんと動作するか確認していきます。

4. 手動実行して動作確認する

以下の2つの条件を満たしていれば、手動でボールを動かすことができます。

  • BehaviorParametersの「BehaviorType」がDefaultかHeuristic Onlyになっている
  • RollerAgentスクリプトで「Heuristic(in ActionBuffers actionsOut)」をoverrideしている

ここまで同じように進めていれば大丈夫だと思います。
Unityエディタで再生ボタンを押して、矢印キーでボールを動かしてみてください。
ボールが落ちたり、ボールがターゲットに近づいた時にターゲットの位置がランダムに変わっていればOKです。
heuristic

なお、ここでは分かりやすいように各オブジェクトに色をつけていますが、それは今回の学習とは無関係なのでその手順は省いています。

5. 機械学習を実行する

それではいよいよ機械学習を実行します。
手順としては

  1. 訓練の設定ファイルを作成する
  2. 設定ファイルを元に、pythonの仮想環境で訓練を実行する

という流れになります。

5-1 設定ファイルを作成する

以下の通り、訓練の設定ファイルを作成します。

保存場所は多分、どこでもOKです。
ちなみに私は、前回の記事の「1-2 機械学習の実行環境を準備する」で作成したプロジェクト用のフォルダの中においてます。
名称は「RollerBall.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

各設定項目の意味はあまり理解できてないので、公式サイトの説明ページへのリンクだけ貼っておきます。

Training Configuration File(Release12版)

5-2 訓練を実行する

前回の記事の「1-2 機械学習の実行環境を準備する」で作成した仮想環境をactivateした状態で、以下のコマンドをターミナルから実行してください。

mlagents-learn [yamlファイルのパス] --run-id=first

以下のようにUnityのロゴがターミナルに表示されていればOKです。
ターミナルにロゴ

Unityエディタに戻り、再生ボタンを押してください。訓練が始まります。
訓練中

しばらくぼーっと眺めていると、ターミナルに学習の途中経過が表示されます。

ここでは「Mean Reward」が1.0になっていれば毎回、ターゲットにたどり着いて報酬をもらっている、ということになります。そうなっていればそれ以上、学習を続ける必要はないのでターミナルで「Ctrl + c」を押して学習を中断してください。

学習が終了すると「results」フォルダが自動生成されます。
その中に学習済みのモデルと学習経過のデータなどが格納されています。

(参考)既存モデルを使った学習の再開と上書き

既存モデルを使って学習を再開する場合は以下の通り

mlagents-learn [yamlファイルのパス] --run-id=first --resume

既存モデルを上書きする場合はこちら

mlagents-learn [yamlファイルのパス] --run-id=first --force

6. 学習結果を反映する

先ほど生成されたモデルをボールに適用すると、学習したことを反映した行動を取るようになります。
モデルは同様に進めていれば「results/first/RollerBall.onnx」に保存されていると思います。

このファイルをUnityエディタにドラッグ&ドロップしてインポートしてください。
そして、インポートしたモデルをRollerAgent(ボール)の「Behavior Parameters」-「Model」にセットします。
モデルの適用

この状態で再生ボタンを押すと、ボールが自動的にターゲットに向かって動き出します。
学習後

以上で学習の一通りの流れはおしまいです!
お疲れ様でした。