質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

781閲覧

unityのml-agentでエピソード実行中はアクションを起こさないようにしたい

545498_ho

総合スコア4

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2022/06/04 06:57

編集2022/06/05 08:51

前提

unityでml-agentを用いた実装をしています。
ゴールは下の図にあるように、上の石が下の石にうまく乗るように学習させることです。

イメージ説明

実現したいこと

エピソード中は動かずにエピソードが始まるタイミングで位置と角度が決まるようにしたいです。

該当のソースコード

このコードで行動を決めるとエピソード中に石が動いて落ちないように学習してしまいます。
そうではなく、エピソード中に石は動かず、初期位置を学習してうまく乗る部分を探し出してほしいです。

public class RockAgent : Agent { public GameObject rock; public GameObject rockB; Rigidbody m_Rb_rock; Rigidbody m_Rb_rockB; public override void Initialize() { m_Rb_rock = rock.GetComponent<Rigidbody>(); m_Rb_rockB = rockB.GetComponent<Rigidbody>(); } public override void OnEpisodeBegin() { rock.transform.localPosition = new Vector3(0f, 5f, 0f); rock.transform.rotation = Quaternion.Euler(0f, 0f, 0f); m_Rb_rock.velocity = Vector3.zero; m_Rb_rock.angularVelocity = Vector3.zero; rockB.transform.localPosition = new Vector3(0f, 0.1f, 0f); rockB.transform.rotation = Quaternion.Euler(0f, 0f, 0f); m_Rb_rockB.velocity = Vector3.zero; m_Rb_rockB.angularVelocity = Vector3.zero; } public override void CollectObservations(VectorSensor sensor) { sensor.AddObservation(rock.transform.localPosition); sensor.AddObservation(rock.transform.localEulerAngles); } public override void OnActionReceived(ActionBuffers actionBuffers) { float turnX = 0f; if (actionBuffers.DiscreteActions[0] == 1f) { turnX = -1f; } else if (actionBuffers.DiscreteActions[0] == 2f) { turnX = 1f; } rock.transform.Rotate(200f * turnX * Time.deltaTime, 0, 0, Space.World); float turnY = 0f; if (actionBuffers.DiscreteActions[1] == 1f) { turnY = -1f; } else if (actionBuffers.DiscreteActions[1] == 2f) { turnY = 1f; } rock.transform.Rotate(0, 200f * turnY * Time.deltaTime, 0, Space.World); float turnZ = 0f; if (actionBuffers.DiscreteActions[2] == 1f) { turnZ = -1f; } else if (actionBuffers.DiscreteActions[2] == 2f) { turnZ = 1f; } rock.transform.Rotate(0, 0, 200f * turnZ * Time.deltaTime, Space.World); if (MaxStep > 0) AddReward(1f / MaxStep); if (rock.transform.position.y < 0f) { SetReward(-1.0f); EndEpisode(); } }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Bongo

2022/06/04 16:03

ご提示いただいたソースコードが、中身が空のOnActionReceivedだけのようなのですが、投稿時のミスでしょうか?
545498_ho

2022/06/05 08:52

ありがとうございます。全体のコードを載せました。
guest

回答1

0

ベストアンサー

エージェントにDecisionRequesterはアタッチされているでしょうか?あれがあると設定した周期で自動的に行動決定要求を出してくるので、今回の目的には邪魔になるかと思います。もしDecisionRequesterがあれば、ひとまずそれは削除してしまうのがいいでしょう。
その上で、下記のようにOnEpisodeBeginで一発だけRequestDecisionを実行してはいかがでしょうか。

C#

1using Unity.MLAgents; 2using Unity.MLAgents.Actuators; 3using Unity.MLAgents.Sensors; 4using UnityEngine; 5 6public class RockAgent : Agent 7{ 8 // 後述のrockに付けるタグ 9 // 別途Tags & Layersでこの名前のタグを登録しておく必要があります 10 static readonly string RockTag = "Rock"; 11 12 // 後述の1エピソードの最大時間 13 static readonly float EpisodeTime = 10.0f; 14 15 public GameObject rock; 16 public GameObject rockB; 17 18 // 後述のrockBが置かれている地面オブジェクト 19 public GameObject ground; 20 21 Rigidbody m_Rb_rock; 22 Rigidbody m_Rb_rockB; 23 24 // 後述の岩モデルの初期位置 25 Vector3 initialPosition; 26 Vector3 initialPositionB; 27 28 // 後述のエピソード開始フラグ 29 bool episodeBegun; 30 31 // 後述のエピソード中断二乗距離 32 float distanceThreshold; 33 34 // 後述の残り時間 35 float timeLeft; 36 37 // rockがrockBを押しのけるようにして地面の上に乗り、地面から落ちることなく 38 // いつまでもエピソードが終わらないケースがあるようだったので、地面に 39 // 下記CollisionDetectorをアタッチすることでrockが地面に触れたのを検出し 40 // エピソードを終了させることにしました 41 [DisallowMultipleComponent] 42 [RequireComponent(typeof(Collider))] 43 private class CollisionDetector : MonoBehaviour 44 { 45 public RockAgent Agent { get; set; } 46 47 void OnCollisionEnter(Collision collision) 48 { 49 if (collision.gameObject.CompareTag(RockTag)) 50 { 51 Agent.StopEpisode(); 52 } 53 } 54 } 55 56 // CollisionDetectorからもエピソードの停止が行えるよう 57 // 停止処理を単独のメソッドとして分離しました 58 void StopEpisode() 59 { 60 episodeBegun = false; 61 SetReward(-1.0f); 62 EndEpisode(); 63 } 64 65 public override void Initialize() 66 { 67 m_Rb_rock = rock.GetComponent<Rigidbody>(); 68 m_Rb_rockB = rockB.GetComponent<Rigidbody>(); 69 70 // 前述のCollisionDetectorを地面にアタッチしておきます 71 var detector = ground.AddComponent<CollisionDetector>(); 72 detector.Agent = this; 73 74 // rockにはタグを付けておきます 75 rock.tag = RockTag; 76 77 // 今回の実験では、ご質問者さんの岩モデルとは異なる岩モデルを代用品として 78 // 用意したため、rockの初期位置が(0, 5, 0)、rockBが(0, 0.1, 0)で決め打ちだと 79 // 私の岩モデルでは少々不都合でした 80 // そこで、シーン上に配置した時の位置を初期位置とするようにしました 81 initialPosition = rock.transform.localPosition; 82 initialPositionB = rockB.transform.localPosition; 83 84 // rockとrockBの初期距離の4倍を限界としました 85 distanceThreshold = ((initialPosition - initialPositionB) * 4.0f).sqrMagnitude; 86 } 87 88 public override void OnEpisodeBegin() 89 { 90 rock.transform.localPosition = initialPosition; 91 rock.transform.rotation = Quaternion.Euler(0f, 0f, 0f); 92 m_Rb_rock.velocity = Vector3.zero; 93 m_Rb_rock.angularVelocity = Vector3.zero; 94 95 rockB.transform.localPosition = initialPositionB; 96 rockB.transform.rotation = Quaternion.Euler(0f, 0f, 0f); 97 m_Rb_rockB.velocity = Vector3.zero; 98 m_Rb_rockB.angularVelocity = Vector3.zero; 99 100 // 残り時間を初期値に設定 101 timeLeft = EpisodeTime; 102 103 // エピソード開始時に行動決定およびアクションを要求 104 RequestDecision(); 105 } 106 107 public override void CollectObservations(VectorSensor sensor) 108 { 109 // 念のため申し上げますと、今回の状況設定ではエピソード開始時の 110 // 岩の姿勢は常に一定ですので、それをエピソード開始時の1回だけ 111 // 観測したところで変化はなく、行動決定の役には立たなそうな気がします 112 sensor.AddObservation(rock.transform.localPosition); 113 sensor.AddObservation(rock.transform.localEulerAngles); 114 } 115 116 public override void OnActionReceived(ActionBuffers actionBuffers) 117 { 118 // エピソード開始時の回転一発で目標姿勢に向けるのであれば、 119 // 離散アクションより連続アクションの方が適しているんじゃないかと思い 120 // ContinuousActionsを使うよう変更しました 121 var rotation = Quaternion.identity; 122 for (var i = 0; i < 4; i++) 123 { 124 rotation[i] = actionBuffers.ContinuousActions[i]; 125 } 126 rotation.Normalize(); 127 rock.transform.localRotation = rotation; 128 129 // 行動決定はエピソード開始時の1回しか行わないので 130 // OnActionReceived内で結果を評価するわけにはいかないため 131 // ここではエピソード開始フラグを立てるだけにしました 132 episodeBegun = true; 133 } 134 135 // 結果を評価し報酬を与えるのはFixedUpdate内で行いました 136 void FixedUpdate() 137 { 138 if (!episodeBegun) 139 { 140 return; 141 } 142 143 // rockがおおむね静止している間、残り時間をカウントダウンしていき... 144 if (m_Rb_rock.velocity.sqrMagnitude < 0.01f) 145 { 146 AddReward(1f / EpisodeTime); 147 timeLeft -= Time.deltaTime; 148 } 149 150 // EpisodeTime秒持ちこたえたら、ペナルティなしで 151 // エピソードを終えることにしました 152 if (timeLeft <= 0.0f) 153 { 154 EndEpisode(); 155 } 156 157 // ご質問者さんの条件に加えて、何らかの理由で岩が吹っ飛んで行方不明に 158 // なった場合に備え、rockとrockBが限界距離を超えて遠ざかった場合にも 159 // エピソードを中止するようにしました 160 // また、前述のようにCollisionDetectorが衝突を検出した場合も 161 // CollisionDetectorによってエピソードが中断されることになります 162 if (rock.transform.position.y < 0f || (rock.transform.localPosition - rockB.transform.localPosition).sqrMagnitude > distanceThreshold) 163 { 164 StopEpisode(); 165 } 166 } 167}

なおコード中のコメントでも申し上げましたが、行動を離散型から連続型に変更(XYZ軸それぞれについて200°/秒で回転するかしないか...の代わりに、目標姿勢を表すクォータニオンの4成分を出力させる)しましたので、エージェントのインスペクター上でContinuous Actionsを4、Discrete Branchesを0に設定しています。

図1

50万回の訓練を行ったところ、下図のように次第に成績が向上し...

図2

確実に成功とまではいきませんでしたが、高確率で安定な姿勢で落とせるようになりました。

図3

念のため申し上げますと、今回の実験ではrockの初期姿勢しか判断材料に加えていませんので(その初期姿勢にしても、コード中のコメントで申し上げましたように役に立っているとは思えず、実質判断材料なしの手探り)、rockrockBの形を変えたり、あるいはrockBの位置をずらしただけでも成功率が落ちそうな気がします。
いろいろな岩に対応させるには、何らかの手段で岩の形をエージェントに教えてやる必要があるでしょう。たとえば岩の周りからさまざまな角度でカメラセンサーで撮影する...とかでしょうかね?

高い塔を目指した結果

C#

1using Unity.MLAgents; 2using Unity.MLAgents.Actuators; 3using Unity.MLAgents.Sensors; 4using UnityEngine; 5 6public class RockAgent : Agent 7{ 8 static readonly string RockTag = "Rock"; 9 static readonly float EpisodeTime = 10.0f; 10 11 public GameObject rock; 12 public GameObject rockB; 13 public GameObject ground; 14 15 float distanceThreshold; 16 bool episodeBegun; 17 Vector3 initialPosition; 18 Vector3 initialPositionB; 19 Rigidbody m_Rb_rock; 20 Rigidbody m_Rb_rockB; 21 float timeLeft; 22 23 void StopEpisode() 24 { 25 // 失敗時のペナルティを増やしました 26 episodeBegun = false; 27 SetReward(-4.0f); 28 EndEpisode(); 29 } 30 31 [DisallowMultipleComponent] 32 [RequireComponent(typeof(Collider))] 33 private class CollisionDetector : MonoBehaviour 34 { 35 public RockAgent Agent { get; set; } 36 37 private void OnCollisionEnter(Collision collision) 38 { 39 if (collision.gameObject.CompareTag(RockTag)) 40 { 41 Agent.StopEpisode(); 42 } 43 } 44 } 45 46 public override void Initialize() 47 { 48 m_Rb_rock = rock.GetComponent<Rigidbody>(); 49 m_Rb_rockB = rockB.GetComponent<Rigidbody>(); 50 var detector = ground.AddComponent<CollisionDetector>(); 51 detector.Agent = this; 52 rock.tag = RockTag; 53 initialPosition = rock.transform.localPosition; 54 initialPositionB = rockB.transform.localPosition; 55 distanceThreshold = ((initialPosition - initialPositionB) * 4.0f).sqrMagnitude; 56 } 57 58 public override void OnEpisodeBegin() 59 { 60 rock.transform.localPosition = initialPosition; 61 rock.transform.rotation = Quaternion.Euler(0f, 0f, 0f); 62 m_Rb_rock.velocity = Vector3.zero; 63 m_Rb_rock.angularVelocity = Vector3.zero; 64 rockB.transform.localPosition = initialPositionB; 65 rockB.transform.rotation = Quaternion.Euler(0f, 0f, 0f); 66 m_Rb_rockB.velocity = Vector3.zero; 67 m_Rb_rockB.angularVelocity = Vector3.zero; 68 timeLeft = EpisodeTime; 69 RequestDecision(); 70 } 71 72 public override void CollectObservations(VectorSensor sensor) 73 { 74 sensor.AddObservation(rock.transform.localPosition); 75 sensor.AddObservation(rock.transform.localEulerAngles); 76 } 77 78 public override void OnActionReceived(ActionBuffers actionBuffers) 79 { 80 // 慣性モーメントが最も小さい方角が長軸だと仮定し、その軸を縦に置いた姿勢から 81 // 一定角度(さしあたり15°)以内の範囲で回転量を決めさせることにしました 82 var actions = actionBuffers.ContinuousActions; 83 var inertiaTensor = m_Rb_rock.inertiaTensor; 84 var axis = Vector3.zero; 85 if (inertiaTensor.x < inertiaTensor.y) 86 { 87 if (inertiaTensor.x < inertiaTensor.z) 88 { 89 axis.x = 1.0f; 90 } 91 else 92 { 93 axis.z = 1.0f; 94 } 95 } 96 else 97 { 98 if (inertiaTensor.y < inertiaTensor.z) 99 { 100 axis.y = 1.0f; 101 } 102 else 103 { 104 axis.z = 1.0f; 105 } 106 } 107 var rotation = Quaternion.FromToRotation( 108 m_Rb_rock.inertiaTensorRotation * axis, 109 actions[3] > 0.0f ? Vector3.up : Vector3.down); 110 rotation = Quaternion.AngleAxis(actions[3], Vector3.up) * rotation; 111 var targetDirection = new Vector3(actions[0], actions[1], actions[2]).normalized; 112 if (targetDirection.sqrMagnitude > 0.0f) 113 { 114 rotation = Quaternion.RotateTowards( 115 Quaternion.identity, 116 Quaternion.FromToRotation(Vector3.up, targetDirection), 117 15.0f) * rotation; 118 } 119 rock.transform.localRotation = rotation; 120 121 // 回転だけできれいに乗る位置を決めさせるのは酷なように思い、 122 // 位置を少しずらすことも許すようにしました 123 // ただし、ずれに応じていくらか減点しています 124 var shift = Vector3.ClampMagnitude( 125 new Vector3(actions[4], 0.0f, actions[5]), 126 0.0625f); 127 rock.transform.localPosition += shift; 128 AddReward(-shift.sqrMagnitude * 16.0f); 129 130 episodeBegun = true; 131 } 132 133 void FixedUpdate() 134 { 135 if (!episodeBegun) 136 { 137 return; 138 } 139 140 if (m_Rb_rock.velocity.sqrMagnitude < 0.01f) 141 { 142 // 静止時の獲得報酬が多すぎたように感じ、引き下げました 143 AddReward((4.0f * Time.deltaTime) / EpisodeTime); 144 timeLeft -= Time.deltaTime; 145 } 146 147 if (timeLeft <= 0.0f) 148 { 149 // 適当な高さ(さしあたりinitialPosition.yの2倍)から適当なサイズのBoxCastを行い... 150 var origin = ground.transform.position; 151 if (Physics.BoxCast( 152 new Vector3(origin.x, initialPosition.y * 2.0f, origin.z), 153 new Vector3(1.0f, 1.0f, 0.01f), 154 Vector3.down, 155 out var hitInfo)) 156 { 157 // 塔の高さを測定して、高さに応じたボーナス点を与えることにしました 158 // このボーナス点の式は勘で決めたもので、根拠があるわけではありません 159 AddReward(Mathf.Pow(Mathf.Clamp01((hitInfo.point.y - origin.y) / initialPosition.y), 8.0f) * 32.0f); 160 } 161 162 EndEpisode(); 163 } 164 165 if ((rock.transform.position.y < 0f) || 166 ((rock.transform.localPosition - rockB.transform.localPosition).sqrMagnitude > 167 distanceThreshold)) 168 { 169 StopEpisode(); 170 } 171 } 172}

図4

図5

投稿2022/06/05 21:47

編集2022/06/19 09:14
Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

545498_ho

2022/06/06 05:33

再現までしてくださってありがとうございます。 一度自分でもやってみます。
545498_ho

2022/06/17 09:56

石がなるべく高くなるような傾きで乗ってほしい場合、どのように報酬を与えればよいでしょうか?
Bongo

2022/06/19 09:15

返信が遅くなりすみませんでした。試してみたところですと、当初は出来上がった塔の高さを測定し報酬に加味すればいいかと思ったのですが、もしかするとそれだけでは大して積み方が変わらないかもしれません。おそらく、高いけれど不安定な積み方は要求される位置・回転がシビアで、学習の過程で正しい向きを発見できる確率が低すぎるんじゃないかと感じました。 自由な回転を許した上で高さが最大の塔を作れるような報酬の与え方ができればいいのでしょうが、あいにくいまいちいい案が思いつかず、ちょっと不本意ですが慣性モーメントが小さい軸を垂直に持ってきた上で、そこから小さな角度以内に回転を制限してやることで高い塔の発見確率を上げてやることにしました。また、回転だけでは条件が厳しすぎるかと思い、落とす位置を少しずらすことも許すようにしてみました。なおこの変更に伴い、Behavior ParametersのContinuous Actionsを6に増やしました。 回答に変更したエージェントのコードと結果の図を追記しましたが、一応は縦に積めそうな位置・回転を見つけられたようです。ですが当初の回答で提示しました横置きの結果と比べるとだいぶ不安定になってしまいました。 あれだけお膳立てをすればもっと成功率が上がるんじゃないかと期待してはいたのですが、100万ステップぐらいで成績は上げ止まり、現状ではあれ以上成功率は改善しないような気がします。もっとゲームのルールを甘くしてやるべきかもしれませんね。未確認ですが、上からポトッと落とすのではなく、静かに乗っけてやることを許せば成功率が上がるかもしれません。
545498_ho

2022/06/21 08:08

お忙しい中、丁寧にご回答いただき誠にありがとうございます。 また疑問に思う部分が出てきたら宜しくお願い致します。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問