クーの自由研究

マスターのかえるのクーは、弟子達の召喚術により新たな依り代を得てⅡ世として復活しました。

ML-Agentsのチュートリアルを少し改造して動かしてみます

今回はおたまじゃくしのお友達向けです

こんにちわ、こんばんわ。かえるのクーの助手の弟子の「井戸中 」(いとなか )です。かえる族の方はおたまじゃくしさん、人類の方でいえば、中学生、高校生向け、入門したての方向けの内容です。もちろんも入門したて(4日め)です。

f:id:luriAPupileOfKoo:20210504141150p:plain

連休中はずっとUNITY & ML-Agentsで遊んでいます。

ボールを動かして箱に当てる学習のチュートリアルをやってみました。

驚くほど簡単です。が、UNITYで動作させるC#のデバッグの仕方がいまいちわかりません。学習途中に止めたいのですが、よくPython側がタイムアウト?になってしまいます。そのうちちゃんと検索してみます。

さて、そのまま「チュートリアルやってみた」記事を書くのもなんなので、よくあるチュートリアルを少し変更してやってみました。

内容

宇宙空間にある2つの球体が衝突するように学習します。

重力は存在しないものとし、球体は一方(Agent)のみ、力で方向を変更できるものとします。目標(Target)はエピソード毎に微妙に位置を変えます。

移動方向、力はともに3次元(x,y,z)とします。

UNITYでの準備

SpaceAgent:球体:直径1:青色

Target:球体:直径1:緑色

ML-Agentsでの情報

観察(CollectObservationsに指定する値 9つ)

Targetの X, Y, Z 座標

SpaceAgentの X, Y, Z 座標

SpaceAgentの X, Y, Z の速度

以上9つの情報

行動(OnActionReceivedで受け取る値3つ)

SpaceAgentの X, Y, Z へ加える力

終了条件(衝突判定→EndEpisode)

2つの球体が極めて接近したとき(中心距離が1.2)衝突とみなす。

衝突時に報酬を与えてその回のエピソードを終了します。

報酬(Reward)

3次元でとても衝突させにくいため、その前段階でも報酬を与えます。強化学習なので、最終結果だけに与えたいところですが、それでは学習にたいへん時間がかかりそうなので、補助的な報酬も微小に与えます。

Targetまでの前回距離 - Targetまでの今回距離 を報酬とします(おおむね1よりかなり小さい値です。前回距離より遠くなればマイナス報酬となります。)この報酬は判定毎に与えます。

・衝突できた場合 報酬として 1 を与えます。

 

チュートリアル操作

(1)新規作成で、UNITYのプロジェクト名 SpaceBallを入力して起動します。

f:id:np2LKoo:20210504212910p:plain

(2)BlueGreenのマテリアルを作成します。(色指定に使うだけ)

- 1. 左下 ProjectのAssetsフォルダ選択 / 右ボタン/2. Create/3.Material で 4.名称をGreen/5.右側 Inspector Main Maps のAlbedo の色部分をクリック/6. Color R=32, G=255, B=64 とします。(色なのでてきとーでよい)

f:id:np2LKoo:20210504214325p:plain

同様に Blueマテリアル Color R=32 G=64 B=255 をつくります。

f:id:np2LKoo:20210504214715p:plain

(3)青色球体(SpaceAgent)と緑色球体(Target)を作成します。

- 1.左上 Hierachey の展開部分で右ボタン/2. 3D Object/3. Sphere/4.名称を SpaceAgentに変更

f:id:np2LKoo:20210504220734p:plain

- 5. 右 Inspector のMeshRendererのMaterialsを展開/6. AssetsにあるBlueのマテリアルをElement0にドラッグ&ドロップ

- 7. Inspector のTransformのX=2 Y=1として移動

- 8. 同様に Targetを3DObject Sphereで GreenでX=0 Y=0 に作成

f:id:np2LKoo:20210504220833p:plain

以上で球体の配置は完了です。次に青色SpaceAgentに物理演算と学習について定義していきます。

(4) ML-Agentsのパッケージを導入します。

- 1. メニューのWindow/2. Package Manager/3. PackageManager + 部分左クリック/4. Add Package from Disk.../5. ml-agens展開フォルダのcom.unity.ml-agents\6. package.json を選択/7.開く でML-Agentsが導入されます。Package Managerを閉じます。f:id:np2LKoo:20210504222219p:plain

※説明例ではSphere⇒Targetにするのを忘れてました。最終的には緑球をTagetという名称にしています。
(5)青色球体(SpaceAgent)に物理エンジンを紐づけます。

- 1. HierarchyのSpaceAgent選択/2.右 Add Componentボタン / 3. 検索欄に Rigi入力/4. Rigidbodyを選択 (5. Use Gravityはチェックしたまま)

※重力Gravityはoffにするといろいろ不都合なのでONにしておきます。無重力はプログラムで指定することにします。

f:id:np2LKoo:20210504223227p:plain
(6)ML-Agentsのパラメータ定義を設定します。

- 1. HierarchyのSpaceAgent選択/2. 右 Add Componentボタン / 3. 検索欄に Beh入力/4. Behavior Parametersを選択

- 5. Behavior Name をSpaceBall /6. Vector ObservationのSpace Sizeを9 /7. Vector ActionのSpace Typeを Continuous , 8. Space Sizeを 3 に設定f:id:np2LKoo:20210504224516p:plain
(7)スクリプトの設定をします。

- 1. HierarchyのSpaceAgent選択/2. 右 Add Componentボタン /3. 検索欄に SpaceAgent入力/4. New Script/5. Create and add ボタンクリックf:id:np2LKoo:20210504225252p:plain(8)作成されたスクリプトファイル内をコーディングします。

動作構想とおりのコーディングをする。(表示が崩れているのは。ご容赦ください)

ソースの文字コードは UTF-8 とします (更新:2020/05/06再調整しました)

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

public class SpaceAgent : Agent
{
    public Transform target;
    Rigidbody rBody//物理計算オブジェクトを格納します
    long epsodeCount = 0//エピソードの回数
    long actionCount = 0//アクションの回数
    Vector3 centerOfSpace = new Vector3(0.0f0.0f0.0f); //空間の中心です
    Vector3 lastPosition = new Vector3(0.0f0.0f0.0f); //前回判定時の自分:エージェントの位置です

    // 初期化時に一度だけ実行されます
    public override void Initialize()
    {
        this.rBody = GetComponent<Rigidbody>();     // 物理計算オブジェクトを取得
        Physics.gravity = new Vector3(000);
    }

    // エピソード毎の最初に実行されます。
    public override void OnEpisodeBegin()
    {
        this.epsodeCount++; //エピソードの回数をアップします
        this.actionCount = 0//アクションの回数をリセットします
        //前回終了時に、自分自身が空間の中心から 15.6fを超えて離れていた場合は中心から速度ゼロで始めます
        if (Vector3.Distance(this.transform.positioncenterOfSpace) > 15.6f)
        {
            this.rBody.angularVelocity = Vector3.zero;
            this.rBody.velocity = Vector3.zero;
            this.transform.position = new Vector3(0.0f0.0f0.0f);
        }
        float tArea = 10.0f;
        // 目標を指定された出現範囲にランダムで出現させます。概ね中心から15.55f以内です
        target.position = new Vector3(
            (Random.value - 0.5f) * tArea, (Random.value - 0.5f) * tArea,
 (Random.value - 0.5f) * tArea);
        lastPosition = this.transform.position// 最初の自分の位置を覚えます
    }

    // 観測対象のセンサー情報を設定します
    public override void CollectObservations(VectorSensor sensor)
    {
        // このケースでは合計 9データのセンサー情報の設定をします
        sensor.AddObservation(target.position);         // 目標の X, Y, Z 座標 (3データ)
        sensor.AddObservation(this.transform.position); // 自分の X, Y, Z 座 (3データ)
        sensor.AddObservation(rBody.velocity);          // 物理エンジンで計算されている自分の速度 X, Y, Z  (3データ)
    }

    // 行動を起こすタイミングでの行動の値を決定します
    public override void OnActionReceived(float[] vectorAction)
    {
        this.actionCount++;//アクションカウントを累積します。
        // このケースでは合計 3データの行動値を取得して行動として設定します
        Vector3 controlSignal = Vector3.zero;
        controlSignal.x = vectorAction[0]; // ニューラルネットワークから取得した X方向値(このケースでは力)
        controlSignal.y = vectorAction[1]; // ニューラルネットワークから取得した Y方向値(このケースでは力)
        controlSignal.z = vectorAction[2]; // ニューラルネットワークから取得した Z方向値(このケースでは力)
        rBody.AddForce(controlSignal * 1.0f); // 物理エンジンに加える力として加算します 力の加減をいろいろ実験します

        // 今回の目標と自分の距離を求めます
        float nowDistanceToTarget = Vector3.Distance(this.transform.position,
 target.position);
        // 距離が 0.2f 以下であれば衝突できたとみなします。(目標半径0.5f + 表面距離0.2f + 自分半径0.5f = 1.2f)
        if (nowDistanceToTarget <= 1.2f)
        {
            // 短時間で衝突できた場合はボーナス加算は微小報酬を得るよりもお得にしておく。
            float bonusReward = (101 - actionCount) >= 0 ? (101 - actionCount) / 100.0f * 4.0f : 0.0f;  //最大 3.0fのボーナス 
            AddReward(1.0f + bonusReward);    // 達成報酬を 1 与えます。短期間(100アクション以内)に達成できるとボーナスを加算します
            EndEpisode();       // 目的を達成できたので、今回のエピソードを終了します
        }
        // 前回の目標と自分の距離を求めます
        float lastDistanceToTarget = Vector3.Distance(lastPosition,
 target.position);
        // 3次元だとなかなか自然に衝突してくれないので近づくだけで報酬を与えます
        // 前回より目標に近づいていればプラスの報酬を与えます
        // 前回より目標から遠ざかればマイナスの報酬を与えます
        // 目標に近いほど多くの報酬を与えます
        //AddReward((lastDistance - nowDistance) / (nowDistance * nowDistance) * 1.0f); // 定期賞罰報酬の一例です
        AddReward((lastDistanceToTarget - nowDistanceToTarget)
 / nowDistanceToTarget * 1.0f); // 定期賞罰報酬の一例です
        // 目標より規定以上離れてしまえば罰を与えエピソードを終了します
        if (nowDistanceToTarget > 15.6f)
        {
            AddReward(-1.0f);    // マイナス報酬を 与えます
            EndEpisode();
        }
        lastPosition = this.transform.position// 今回の自分の位置を記憶します
    }

}
 

(9)スクリプトの動作を指定します。

- Space Agent Script部の Max Step を 500 にする(この値はいろいろかえる)

- Target に Hierarchyの Target をドラッグ & ドロップする

f:id:np2LKoo:20210504232948p:plain
(10)行動決定のタイミング(単位)を指定します。

- HierarchyのSpaceAgent選択/右 Add Componentボタン / 検索欄に Deci入力/Decision Requester を選択

- Decision Period に 10を入力 (この値はいろいろかえる)

f:id:np2LKoo:20210504142121p:plain

ここまででUNITYの準備は完了です。

(11)学習用のパラメータをyamlファイルで指定します。

ML-Agentsのインストールフォルダの confフォルダに SpaceBall.yamlファイルを作成します。(表示が崩れているのはご容赦ください)(更新:該当バージョンでのパラメータに誤りがあったので修正しました。学習が超絶遅かったのはそのためでした)

default:
    trainerppo
    batch_size1024
    beta5.0e-3
    buffer_size10240
    epsilon0.2
    hidden_units128
    lambd0.95
    learning_rate3.0e-4
    learning_rate_schedulelinear
    max_steps5.0e5
    memory_size128
    normalizefalse
    num_epoch3
    num_layers2
    time_horizon64
    sequence_length64
    summary_freq10000
    use_recurrentfalse
    vis_encode_typesimple
    reward_signals:
        extrinsic:
            strength1.0
            gamma0.99
SpaceBall:
    summary_freq1000
    max_steps4.0e5
    batch_size10
    buffer_size100
    normalizetrue

(12)Pythonの学習エージェントを起動します。

- コマンドプロンプトで、cd ML-Agentsの展開フォルダ とします。

> conda activate ml-agents-vp107

※もしもcondaの設定がまだの場合はここで設定します。

> conda create -n ml-agents-vp107 python=3.7

> conda activate ml-agents-vp107

> pip install -e ./ml-agents-envs

> pip install -e ./ml-agents

※ ここまで

学習プロセスを起動します。

> mlagents-learn ./config/SpaceBall.yaml --run-id=SpaceBall-ppo-1

数値部分は失敗したり、実験が進むごとにアップさせます。

初回は下記がでますが許可をします。

f:id:np2LKoo:20210504235136p:plain

学習エージェントが起動します。Portは5004を使用するようです。Unity側の操作が遅いとタイムアウトすることがあるので、その場合は数字をアップしてもう一度実行します。

f:id:np2LKoo:20210504235237p:plain

(13)UnityのPlayを実行します。

mlagents-learnの実行で以下の表示が出れば、Unityのplayボタン▶をクリックします。

学習が始まりますので、学習の経過が観察できます。

※学習と実行を交互に行う場合、必ず「BehaviorParameters」のModelの状態を確認してから実行してください。

f:id:np2LKoo:20210504235916p:plain

学習の停止はUNITYで一時停止ボタンをクリックして、mlagentsのプロンプト画面でctrl+c します。

(14) 学習の継続をする場合

> mlagents-learn ./config/SpaceBall.yaml --run-id=SpaceBall-ppo-1 --resume

Start training by pressing the Play button in the Unity Editor.

の表示がでてからUnityのPlayボタン▶をクリックします。

データが壊れている場合はエラーが出て続行しません。その場合は数値をカウントアップさせてもう一度最初から学習します。

(15)学習結果の反映

- 再び学習を停止します。

- ML-Agents解凍フォルダ\models\SpaceBall-ppo-1にできるpaceBall.nn ファイルを Unityプロジェクトの Assetsフォルダに複写します。

- Assetsに表示されたSpaceBall.nnファイルを Behevior ParametersのModelにDrag&Dropします。

- メニューのFile/Save をします。モデル変更の内部状態が反映されきらない場合があるようなので、Unityを再起動します。

f:id:np2LKoo:20210504234446p:plain
(16)実行してみます。

- UnityのPlayボタン▶をクリックします。

学習は早送りしているので、実際の球の動作は遅いです。

右側のInspectorのRigidbodyのMass(重さ)を1/10の0.1にすると早く動きます。

逆に10にするとかなり重いです。

それ以上の変化をさせるとこの学習結果ではあまりうまく動かない感じです。

 ※学習済データを実行する場合は、プログラム SpaceBall.csの 

 
    private bool isTraining = false;
 

として実行ください。(学習の最初は簡単(中心近傍)に目標が出現するようにしているため)

学習のすこしばかりのコツ

最初は MaxStep=100, DecisionPeriod=10くらいで学習させます。

すこしばかり、緑の球のまわりをうろつきはじめたら(画面からはみでなくなるくらい)、MaxStep=500, DecisionPeriod=5くらいにします。

かなり学習できてきたら、MaxStep=2000m DecisionPeriod=3くらいにします。

なお、ずっと学習しているとどこかで学習が頭打ちになって停止します。 

(追記)パラメータを調整したら上記の操作はしなくてもちゃんとはやく学習するようになりました。これはやらなくてもOKです。学習の終了は頭打ち判定ではなく、規定の回数(max_steps)によるものでした。

 

興味ある方は是非やってみてください。

報酬の与え方をかえるとアプローチの仕方がかわると思います。