エアホッケーを目指したロボットの実験のの忘備録です。
—–
ボールを向こう側に押し出す動作を学習させた。
報酬は、赤いディスクが落ちた時の縦方向の速度(向こう側が正)とした。結構激しい動きだが、ディスクは向こう側に押し出されている。
前回は、稼働部分を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個並べた。
プログラム
並列に並べて実行するために、ローカル座標を使って制御や報酬を定義するようにした。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
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しても大丈夫そう)。
プログラムのパラメータは以下の通り。
1 2 3 4 5 6 7 8 |
Max Step: 200 Joint0: Joint0 Joint1: Joint1 Joint2: Joint2 RB_Target: Target RB_Endeffector: Endeffector GO_World: World Speed: 180 |
Config.yaml
Config.yaml はいつもと同じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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。
途中から再開するときには –resume オプション。
グラフを見るためのコマンド
グラフがうまく書けていない場合には、ctrl + c で止めてもう一度実行すると見えたりする。
いろいろな条件で試した。計算はmax_steps = 500000でも20分くらいだったと思う。
コメントを残す