エアホッケーを目指したロボットの実験のの忘備録です。
—–
ボールを向こう側に押し出す動作を学習させた。
報酬は、赤いディスクが落ちた時の縦方向の速度(向こう側が正)とした。結構激しい動きだが、ディスクは向こう側に押し出されている。
前回は、稼働部分をHinge で実装したが、
今回は、ArticulationBody という、Unity2010.1からの新機能で実装した。
そのおかげで、関節部分がぶれずにしっかりしている。
ArticulationBody の使い方は、npaka さんの「UnityのArticulationBodyの使い方」がとても参考になった。
ゲームオブジェクト
アームは、アンカー部分となるJoint0、そこにRevolute でつながるJoint1、さらにそこにつながるJoint2からなる。
Joint2は、Endeffectorとなずけた緑の円盤状のパッド(Endeffector)も含む。
これらは、階層構造で定義しており、Joint0 > Joint1 > Joint2 の関係がある。
さらにJoint0は、Worldというゲームオブジェクトの下の階層となっている。
Joint0のパラメータは以下の通り。
Articulation Body をAddして、Immovableをチェック。Regidbodyはいらない。
Joint0をエージェントとしたので、Behavior ParametersとDecision RequesterをAddしている。Decision RequesterのDecision Period はきめ細かい動きにしたくて、最終的にDecisionは2まで下げた。
Joint1のパラメータは以下。
Articulation Body は、Hinge のような可動部を表すRevolute にして、方向と位置を設定。
Joint2のパラメータは以下。
今気づいたが、Articulation Body のDamping がJoint1では100で、Joint2では1000だった。これは意図していなかったが、これでも学習できた。
Joint2の先にパッドがついている。これをEndeffectorと名付けた。ディスクをはじくために、RigidbodyとMesh ColliderをAddしている。
赤のデスクがTarget。これにも、RigidbodyとMesh Collider をAddしている。
以上がWorldの中身で、最終的にはこのWorldをプレファブ化して、16個並べた。
プログラム
並列に並べて実行するために、ローカル座標を使って制御や報酬を定義するようにした。
using System.Collections.Generic; using UnityEngine; using Unity.MLAgents; using Unity.MLAgents.Sensors; using Unity.MLAgents.Actuators; public class Controller : Agent { public ArticulationBody Joint0, Joint1, Joint2; public Rigidbody RB_Target, RB_Endeffector; public GameObject GO_World; public float speed; private Vector3 init_pos1, init_pos2; private Quaternion init_rot1, init_rot2, init_rott; private float angle1, angle2; public override void Initialize() { init_pos1 = Joint1.transform.localPosition; init_rot1 = Joint1.transform.rotation; init_pos2 = Joint2.transform.localPosition; init_rot2 = Joint2.transform.rotation; init_rott = RB_Target.transform.rotation; angle1 = 0; angle2 = 0; } public override void OnEpisodeBegin() { Joint1.transform.localPosition = init_pos1; Joint1.velocity = Vector3.zero; Joint1.angularVelocity = Vector3.zero; Joint2.transform.localPosition = init_pos2; Joint2.velocity = Vector3.zero; Joint2.angularVelocity = Vector3.zero; RB_Target.transform.rotation = init_rott; angle1 = 0; angle2 = 0; // RB_Endeffector.velocity = Vector3.zero; RB_Target.velocity = Vector3.zero; RB_Target.transform.localPosition = new Vector3( Random.value * 2 - 1, 0.5f, Random.value * 2.4f - 2.7f); } public override void CollectObservations(VectorSensor sensor) { Vector3 joint_pos = GO_World.transform.InverseTransformPoint(Joint2.transform.position); Vector3 joint_rot = GO_World.transform.InverseTransformDirection(Joint2.transform.eulerAngles); Vector3 target_pos = GO_World.transform.InverseTransformPoint(RB_Target.transform.position); sensor.AddObservation(joint_rot.y / 360); sensor.AddObservation(joint_pos.x); sensor.AddObservation(joint_pos.z); sensor.AddObservation(target_pos.x); sensor.AddObservation(target_pos.z); } public override void OnActionReceived(ActionBuffers actionBuffers) { float a1 = actionBuffers.ContinuousActions[0]; float a2 = actionBuffers.ContinuousActions[1]; // 行動を角速度とする場合 speed = 10くらいで // angle1 += speed * a1; //angle2 += speed * a2; // 行動を角速度とする場合 speed = 180くらいで angle1 = speed * a1; angle2 = speed * a2; float a1_min = -90; float a1_max = 90; float a2_min = -160; float a2_max = 160; if(angle1 < a1_min){ angle1 = a1_min; } if(angle1 > a1_max){ angle1 = a1_max; } if(angle2 < a2_min){ angle2 = a2_min; } if(angle2 > a2_max){ angle2 = a2_max; } var xDrive1 = Joint1.xDrive; xDrive1.target = angle1; Joint1.xDrive = xDrive1; var xDrive2 = Joint2.xDrive; xDrive2.target = angle2; Joint2.xDrive = xDrive2; // リーチングだったら以下の距離を使う // ローカル座標の計算のためInverseTransformPointを使用 float distanceToTarget = Vector3.Distance( GO_World.transform.InverseTransformPoint(RB_Target.transform.position), GO_World.transform.InverseTransformPoint(RB_Endeffector.transform.position)); Vector3 target_v = RB_Target.velocity; AddReward(target_v.z); if (RB_Target.transform.localPosition.y < 0) { EndEpisode(); } } public override void Heuristic(in ActionBuffers actionsOut) { float a1, a2; a1 = 0; a2 = 0; var continuousActionsOut = actionsOut.ContinuousActions; if (Input.GetKey(KeyCode.Alpha1)){ a1 = -0.5f; } if (Input.GetKey(KeyCode.Alpha2)){ a1 = 0.5f; } if (Input.GetKey(KeyCode.Alpha3)){ a2 = -0.5f; } if (Input.GetKey(KeyCode.Alpha4)){ a2 = 0.5f; } continuousActionsOut[0] = a1; continuousActionsOut[1] = a2; } }
プログラムは、Joint0にAdd Component(多分、何にAddしても大丈夫そう)。
プログラムのパラメータは以下の通り。
Max Step: 200 Joint0: Joint0 Joint1: Joint1 Joint2: Joint2 RB_Target: Target RB_Endeffector: Endeffector GO_World: World Speed: 180
Config.yaml
Config.yaml はいつもと同じ。
behaviors: MyBehavior: 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 beta_schedule: constant epsilon_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
実行コマンド
–force は上書きオプション。[name]はシミュレーションのID。
[code]
(mlagents) …\Assets\ML-Agents> mlagents-learn .\config\config.yaml –run-id [name] –force
[/code]
途中から再開するときには –resume オプション。
[code]
(mlagents) …\Assets\ML-Agents> mlagents-learn .\config\config.yaml –run-id [name] –resume
[/code]
グラフを見るためのコマンド
[code]
(mlagents) …\Assets\ML-Agents> tensorboard –logdir results –port 6006
[/code]
グラフがうまく書けていない場合には、ctrl + c で止めてもう一度実行すると見えたりする。
いろいろな条件で試した。計算はmax_steps = 500000でも20分くらいだったと思う。
コメントを残す