・中盤くらい(まだ、衝突ができず赤色になるケースがすこしあるくらい)にエリアでとても苦手な場所や、強いクセがでることがある(そのまま衝突すればよいのにゆっくり回り込んで衝突しようとして、最終的にはタイムアウトする。必ず一定方向から衝突させようとする。衝突前に一旦急ブレーキをかけ、衝突を躊躇する。などなど
・短時間で衝突したときのボーナスがあまりうまく機能していなかった(条件が厳しすぎてほとんど与えられていなかった)ため、もう少し緩い条件で衝突時のボーナスを加算する。
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.0f, 0.0f, 0.0f); //空間の中心です
Vector3 lastPosition = new Vector3(0.0f, 0.0f, 0.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.position, centerOfSpace) > 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.velocity, direction), 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(lastPosition, target.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; // 今回の自分の位置を記憶します
}
}
10. 指定場所にできるExeファイルを実行して動作するか確認する(モデルがないので起動のみの確認。エラーがでなければOK)
11. yamlファイルを調整して、学習回数等を修正する。
12. 必要に応じて、コンパイルしたUnityプログラムを実行させたい場所へ複写する
13. コマンドプロンプトを起動して、多重度指定で学習を実行(例では10)
Unityでのグラフィックを表示しないほうが速いとおもいますが、学習の経過を視覚的にみるのが好きなので表示をしたまま学習しています。報酬の実獲得数値のレポートがほとんどかわらなくても、学習が進むにつれて、本当に職人芸のように洗練されていくのがわかります。
学習に全く関係ない、「音」や「エフェクト」などの「装飾」は不要ですが、OFFにする機能をつけませんでした。そのままやってます。(衝突できなかったとき赤色にする機能だけは本体の機能として必要と感じました)