エアホッケーを目指したロボットの実験のの忘備録です。

—–

ボールを向こう側に押し出す動作を学習させた。

報酬は、赤いディスクが落ちた時の縦方向の速度(向こう側が正)とした。結構激しい動きだが、ディスクは向こう側に押し出されている。

前回は、稼働部分を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分くらいだったと思う。