クーの自由研究

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

強化学習の過程は本当に不思議な感覚を覚えました

えっ!学習させるにはエフェクトは邪魔ですか、そうですか

こんにちわ、こんばんわ。かえるのクーの助手の弟子の「井戸中 」(いとなか )です。

f:id:np2LKoo:20210509235845p:plain

前のページに書ききれなかった(3)強化学習の調整をやっています。

強化学習をやったのは、(ほぼ)今回が初めてですが、まったく魔法であると感じました。自己符号化器を教えてもらったときに最初に感じた感覚に近いかもしれません。この不思議な感覚はおそらく何回もやっていると「あたりまえ」になるのだと思いますが、何度も経験する感覚でないと思います。尊い経験として記憶しておきます。

報酬の調整

強化学習がよりスムーズにすすむように、報酬を調整します。

最初のほうのSpaceBall強化学習では、以下の不満がありました。

・中盤くらい(まだ、衝突ができず赤色になるケースがすこしあるくらい)にエリアでとても苦手な場所や、強いクセがでることがある(そのまま衝突すればよいのにゆっくり回り込んで衝突しようとして、最終的にはタイムアウトする。必ず一定方向から衝突させようとする。衝突前に一旦急ブレーキをかけ、衝突を躊躇する。などなど

・見栄え的に、または感覚的に慎重になりすぎるケースがでてくる。

・クセや慎重な部分がでると、局所最適してしまい、それ以上チャレンジしなくなってしまうことがある。

学習・訓練が意図せず袋小路になってしまうのは、報酬の与え方に改善の余地があるような感じがしています。

改善点

・衝突報酬として、衝突時の(ターゲット方向への)速度が速いと報酬を加算する。

・短時間で衝突したときのボーナスがあまりうまく機能していなかった(条件が厳しすぎてほとんど与えられていなかった)ため、もう少し緩い条件で衝突時のボーナスを加算する。

・学習とは関係ないが、衝突判定距離が 表面0.2以内だと、見た目に違和感があったため衝突判定距離を表面0.05以下(中心距離1.05以下)に修正した。

ソース

以上の改善をした現状のソースです。(ソースの崩れはごめんなさい)

+, -+は前回のサウンド関連調整と今回の強化学習関連の調整です。

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

public class SpaceAgent : Agent
{
    public Transform target;
    public Transform explosion
+    public Transform appearance;
+    public AudioClip expSound;  // 爆裂音
+    public AudioClip appSound;  // 出現音
+    public AudioClip errSound;  // 衝突できなくて残念音
+    public AudioClip upSound;   // 報酬音
+    public AudioClip downSound// マイナス報酬音
+    public AudioClip rewardSound;   // 衝突報酬音

    Rigidbody rBody//物理計算オブジェクトを格納します
    long epsodeCount = 0//エピソードの回数
    long actionCount = 0//アクションの回数
    Vector3 centerOfSpace = new Vector3(0.0f0.0f0.0f); //空間の中心です
    Vector3 lastPosition = new Vector3(0.0f0.0f0.0f); //前回判定時の自分:エージェントの位置です
    Material _material//マテリアルの初期値を退避します
    bool isSuccess//衝突が成功したかどうかのフラグ
+    AudioSource audioAgent;     // Agent SE音用
+    AudioSource audioTarget;    // Target SE音用
+    AudioSource audioExplosion// Explosion SE音用

    // 初期化時に一度だけ実行されます
    public override void Initialize()
    {
        this.rBody = GetComponent<Rigidbody>();     // 物理計算オブジェクトを取得
        Physics.gravity = new Vector3(0, 0, 0);
        _material = GetComponent<Renderer>().material;
        isSuccess = true;
+        audioAgent = GetComponent<AudioSource>();
+        audioTarget = target.GetComponent<AudioSource>();
+        audioExplosion = explosion.GetComponent<AudioSource>();
    }

    // エピソード毎の最初に実行されます。
    public override void OnEpisodeBegin()
    {
        this.epsodeCount++; //エピソードの回数をアップします
        this.actionCount = 0//アクションの回数をリセットします
        if (isSuccess)
        {
            GetComponent<Renderer>().material = _material//オリジナルのマテリアルを設定します。
        }
        else
        {
            Material material = Resources.Load("Red"as Material//ResourcesフォルダにあるRedというオブジェクトをLoadします
            GetComponent<Renderer>().material = material// 赤色のマテリアルを設定します
            audioAgent.PlayOneShot(errSound); // SE:エラー音
        }
        isSuccess = false;

        //前回終了時に、自分自身が空間の中心から 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.0f, 0.0f, 0.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);
+        appearance.position = target.position;
+        var appe = appearance.GetComponent<ParticleSystem>(); //Target出現エフェクト
+        appe.Play(); // エフェクト再生
+        audioTarget.PlayOneShot(appSound); //Target出現音
        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);
-+        // 距離が 1.05f 以下であれば衝突できたとみなします。(目標半径0.5f + 表面距離0.05f + 自分半径0.5f = 1.05f)
-+        if (nowDistanceToTarget <= 1.05f)
        {
            explosion.position = target.position;
            var exp = explosion.GetComponent<ParticleSystem>(); //衝突のエフェクト
            exp.Play();
+            audioExplosion.PlayOneShot(expSound); //衝突音
            isSuccess = true;
            // 短時間で衝突できた場合はボーナス加算は微小報酬を得るよりもお得にしておく。
-+            float bonusReward = (801 - actionCount) >= 0 
? (801 - actionCount) / 800.0f * 4.0f : 0.0f;  //最大 4.0fのボーナス
+            Vector3 direction = target.position - this.transform.position;
+            // 勢いよく衝突した場合の、特別ボーナスを導出する(Targetの中心への相対速度相当の値)
+            float specialReward = Vector3.Distance(
Vector3.Project(rBody.velocitydirection), Vector3.zero); 
-+            AddReward(1.0f + bonusReward + specialReward);    // 達成報酬を 1 与えます。短期間に達成ボーナスと特別ボーナスを加算します
+            audioAgent.pitch = (1.0f + bonusReward + specialReward) / 4.0f//音の高さを調整(高いほど高報酬)
+            audioAgent.PlayOneShot(rewardSound); // SE:衝突報酬音
            EndEpisode();       // 目的を達成できたので、今回のエピソードを終了します
        }
        // 前回の目標と自分の距離を求めます
        float lastDistanceToTarget = Vector3.Distance(lastPositiontarget.position);
        // 3次元だとなかなか自然に衝突してくれないので近づくだけで報酬を与えます
        // 前回より目標に近づいていればプラスの報酬を与えます
        // 前回より目標から遠ざかればマイナスの報酬を与えます
        // 目標に近いほど多くの報酬を与えます
        float distance = lastDistanceToTarget - nowDistanceToTarget;
        AddReward(distance / nowDistanceToTarget * 1.0f); // 定期賞罰報酬の一例です
+        if (actionCount > 0 && (actionCount % 200 == 0))
+        {
+            audioAgent.pitch = 1.0f;//通常ピッチに戻す
+            if (distance > 0f )
+            {
+                audioAgent.PlayOneShot(upSound); // SE:報酬音
+            } else
+            {
+                audioAgent.PlayOneShot(downSound); // SE:マイナス報酬音
+            }
+        }
        // 目標より規定以上離れてしまえば罰を与えエピソードを終了します
        if (nowDistanceToTarget > 15.6f)
        {
            AddReward(-1.0f);    // マイナス報酬を 与えます
            isSuccess = false;
            EndEpisode();
        }
        lastPosition = this.transform.position// 今回の自分の位置を記憶します
    }
}

 

学習の高速化について

以下のサイトに教えていただきました。どうも有難うございます。

現在のWindows版での操作を備忘で貼り付けます。

おおまかな手順

(1)Unityでアプリをコンパイルしてexeファイルを作成する。

(2)mlagents-learnで多重度をそこそこ指定して学習を実行する。

実施内容

Unityアプリのコンパイル

- 0. Unityで学習させるためにBehavior ParametersでModel=None(NN Model):学習済モデルなし、の設定してSaveする

f:id:np2LKoo:20210510012155p:plain

- 1. メニューのEditを選択/2. Project Settings... を選択/3. 起動したProjectSettingsダイアログのPlayerメニュー/4. Resolution and Presentation のResolutionを FullscreenMode=Windowed, Default Screen Width=1024, Default Screen Hight=768を設定

f:id:np2LKoo:20210510010745p:plain

- 5. Fileメニューを選択/6. Build Settings...を選択/7. 起動したBuild SettingsダイアログでPlatfomをPC,MAC&LinuxStandaloneを選択/8. アーキテクチャにあわせた選択肢を選ぶ/9. Build ボタンをクリックしたと表示される実行ファイル格納場所でフォルダの選択をしてコンパイルする。

f:id:np2LKoo:20210510010808p:plain

10. 指定場所にできるExeファイルを実行して動作するか確認する(モデルがないので起動のみの確認。エラーがでなければOK)

11. yamlファイルを調整して、学習回数等を修正する。

12. 必要に応じて、コンパイルしたUnityプログラムを実行させたい場所へ複写する

※複写する場合は同時にできるUnityPlayer.dllやUnityCrashHandler32.exe等も複写する。

13. コマンドプロンプトを起動して、多重度指定で学習を実行(例では10)

> mlagents-learn ./config/SpaceBall.yaml --run-id=SpaceBall-ppo-1 --env=SpaceBall --num-envs=10

--env=に指定するのがプログラム名です。--num-envs=に多重度を設定します。これで Unityプログラムが10個起動して、学習が始まります。

多重度:

の実験環境は6Core/12Threadなので、多重度を 10としました。理屈状はCPUスレッド数以上にしないほうがよい&OSの動作する余地を残すことが必要な感触からそのようにしました。

NNデータの配備

学習が完了したあとのNNモデルファイルの配置&設定は通常と同じです。

学習速度について

適度に多重化すると速いですが、マシンスペック等により、ある程度で頭打ちになりました。

Unityでのグラフィックを表示しないほうが速いとおもいますが、学習の経過を視覚的にみるのが好きなので表示をしたまま学習しています。報酬の実獲得数値のレポートがほとんどかわらなくても、学習が進むにつれて、本当に職人芸のように洗練されていくのがわかります。

あたらめて、エフェクトについて

学習に全く関係ない、「音」や「エフェクト」などの「装飾」は不要ですが、OFFにする機能をつけませんでした。そのままやってます。(衝突できなかったとき赤色にする機能だけは本体の機能として必要と感じました)

10多重ともなると怪しげな精神世界の音がしたので、すぐ音を消しました。

 次回からは必ずエフェクトオフの機能を付けます。(1か所なおすだけのもの)

まとめ

学習について不満だった点は、報酬の与え方の修正と、多重度を上げて充分学習させたことによって満足するものとなりました。

以上で予定していた休日のお題は、めでたくミッションコンプリートです。