エアホッケーを目指したロボットの実験のの忘備録です。
—–
ボールを向こう側に押し出す動作を学習させた。
報酬は、赤いディスクが落ちた時の縦方向の速度(向こう側が正)とした。結構激しい動きだが、ディスクは向こう側に押し出されている。
前回は、稼働部分を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分くらいだったと思う。









コメントを残す