tanaka's Programming Memo

プログラミングについてのメモ。

Leap Motionでマウス的な操作をする LeapMotionPointer

Leap Motionを使って、マウスと同じようにスクリーン上の場所を指定する操作を実装しました。以下は、開発中のVoxelorer Birdに組み込んだ作例です。

以下のリポジトリーで公開しています。

github.com

使い方を紹介します。

f:id:am1tanaka:20190217005740g:plain
動作例

目次

前提

動作確認をしたのは以下の環境です。

  • Windows10
  • Unity2017.4.20と2018.3.2
  • Leap Motion Orion 4.0.0
  • Unity Core Assets 4.4.0

WebGLについて

Unity2017.4ではビルドできませんでした。Unity2018.1にして、Unsafe Codeを許可するとビルドはできましたが、実行時にエラーが出て動きませんでした。WebGLでの動かし方は分かっていません。

環境設定

Leap MotionをUnityで使えるようにする手順はこちらの公式ページで説明されています。すでに環境を構築済みでしたら、飛ばして構いません。

  • 公式ページを開きます
  • DOWNLOAD ORION BETAのボタンをクリックして、Leap Motion Orionの最新版をダウンロードしてインストールします
  • 公式のこちらを開きます
  • DOWNLOAD UNITY CORE ASSETS X.X.Xのボタンをクリックして、Unity Core Assetsをダウンロードします

以上で下準備完了です。

デモを動かす

デモの実行方法です。

Leap Motion用のアセットがないので、以下のようなエラーが出ます。次の手順で消えますので作業を進めてください。

f:id:am1tanaka:20190217010038p:plain
エラーが出ても構わず進めて良い

  • 上の手順でダウンロードしたUnity Core Assetsをプロジェクトにインポートします

f:id:am1tanaka:20190217010102p:plain
インポート

これでエラーが消えます。

  • Projectウィンドウから、LeapMotionPointer > Demoフォルダーを開いて、Demoシーンをダブルクリックして開きます

以上でデモ用のシーンが開きます。Playするとデモが動きます。

f:id:am1tanaka:20190217010115p:plain
デモの画面

操作方法

手を水平に開いておくのが基本姿勢です。以下のように、Leap Motionの上に手を開いて、水平にかざします。

f:id:am1tanaka:20190216211447j:plain
基本姿勢

手のひらを水平にしたまま、上下左右に手を動かして画面にカーソルが表示される場所を探してください。Leap Motionから少し奥で、30cmぐらい上の位置ぐらいで表示されると思います。

左手でも操作できます。

左右移動

手を左右に動かします。

f:id:am1tanaka:20190217010155g:plain
左右操作

上下移動

手を上下、あるいは前後に動かします。

f:id:am1tanaka:20190217010233g:plain
上下操作

上下、前後のどちらも、カーソルは上下移動と見なします。

クリック

クリックは、人差し指を下げます。

f:id:am1tanaka:20190216211523j:plain
クリック

人差し指薬指の高さの違いでクリック判定をしています。中指は見ていないので、曲げても曲げなくてもどちらでも楽な方で。

手のひらが人差し指側に傾いていると誤クリックが発生しやすくなり、操作が安定しません。

f:id:am1tanaka:20190216211546j:plain
誤クリックが増える

小指側に傾けておく方が安定した操作ができます。

f:id:am1tanaka:20190216211538j:plain
小指側に傾けると安定する

丸いのをドラッグしたり、左上のボタンをクリックしたりしてみてください。

f:id:am1tanaka:20190217005740g:plain
動作例

手のひらを小指側に傾けておけば、適当に操作してもそれっぽく動きます。

手の認識がうまくいっていないと動作が不安定になります(結構頻繁に起きます)。反対側に動いたり、クリックがおかしかったり、震えたりする場合は、手をLeap Motionの視野から一旦外してから、手を認識しなおしてください。

プロジェクトに組み込む

組み込みたいプロジェクトをUnity2017.4以降で開いてから、以下の作業をします。

必要なアセットをインポート

  • ダウンロードしてあるUnity Core Assets(Leap_Motion_Core_Assets_ x.x.x.unitypackage)をインポートします
  • LeapMotionPointerリポジトリーのReleasesを開いて、最新版のLeapMotionPointerX.X.X.unitypackageをダウンロードして、プロジェクトにインポートします
  • LeapMotionを使いたいシーンを開きます
  • Projectウィンドウで、LeapMotionPointer > Prefabsフォルダーを開きます
  • LeapMotionPointerプレハブをドラッグして、Hierarchyウィンドウにドロップします

これで準備完了です。ついでに、動作確認のためにデモ用のCanvasを配置しておくと楽です。

  • ProjectウィンドウのLeapMotionPointer > Demo > Prefabsフォルダーを開いて、DemoCanvasプレハブをHierarchyウィンドウにドロップします
  • EventSystemがシーンにない場合は、HierarchyウィンドウのCreateボタンをクリックして、UI > Event Systemを選択して追加します

f:id:am1tanaka:20190217010456p:plain
Hierarchyの様子

これでLeapMotionを使ってカーソルを操作できます。カーソルは、DemoCanavsの子供のCursorオブジェクトです。これの画像を差し替えればオリジナルのものに差し替えることができます。

f:id:am1tanaka:20190217010526p:plain
DemoCanvas

スクリプトで情報を得る

情報は、LeapMotionManagerExクラスのstaticプロパティーで得られます。

利用したいスクリプトの冒頭に、以下のusingを追加します。

// :
using AM1.LeapMotionPointer;

bool LeapMotionManagerEx.isEnable

Leap Motionで手を確認している時にtrueになります。

Vector3 LeapMotionManagerEx.screenPoint

Leap Motionが指している画面座標を返します。

// :
Debug.Log(LeapMotionManagerEx.screenPoint);

補足

デフォルトの設定では、Camera.mainのスクリーン座標を返します。Main Cameraタグが未設定だった場合など、Camera.mainが取得できない状態の時はscreenPointは無効な値を返します。

カメラを指定するには、シーンに配置したLeapMotionPointerオブジェクトのTarget Cameraプロパティーに目的のカメラを設定します。

f:id:am1tanaka:20190217010543p:plain
カメラを設定する場所

Vector3 LeapMotionManagerEx.viewportPosition

Leap Motionが指している場所をビューポート座標で返します。

bool LeapMotionManagerEx.isPressDown

クリックを開始した時にtrueになります。

// :
if (LeapMotionManagerEx.isPressDown) {
    Debug.Log("クリック!");
}

bool LeapMotionManagerEx.isPress

クリック状態の時、ずっとtrueになります。ドラッグを判定したい場合などに利用します。

// :
if (LeapMotionManagerEx.isPress) {
    Debug.Log("押し続けている");
}

bool LeapMotionManagerEx.isPressUp

クリックが解除された時にtrueになります。

// :
if (LeapMotionManagerEx.isPressUp) {
    Debug.Log("クリック終了");
}

UIのボタン

UIのボタンは、クリック時に自動的に押すようにしてあります。何もしなくても反応します。

パラメーター

LeapMotionPointerの調整用パラメーターは以下の通りです。

f:id:am1tanaka:20190217012026p:plain
LeapMotionPointerのインスペクター

  • Leap Provider
  • Model Pool
    • 手のモデルを表示したい時に利用します。本プロジェクトでは設定不要です
  • Target Camera
    • LeapMotionManagerEx.screenPointを計算する時に使うカメラを指定します。未設定の時は、Camera.mainの値を使います。Camera.mainnullの時は、LeapMotionManagerEx.screenPointの値が設定されません
  • Move Rate
    • 手の動きと、カーソルの動きを対応させる係数です。この値を大きくすると、手の動きに対して、カーソルの動きが速くなります
  • Visible Under
    • 手の低さの下限値です。この値を小さくすると、低い位置で手を認識するようになります。小さくしすぎると指の動きが認識できなくなるので、0.15程度が下限です
  • Click Threshold
    • クリックを判別する人差し指と薬指の高さの差です。この値を大きくすると、クリックの誤動作は減りますが、クリックの反応が鈍くなります
  • Bank Limit
    • 手首の傾きがこの値よりも大きくなったら、誤動作を避けるためにクリック判定を無視します。0は傾き無し。0.5が90度です
  • Viewport Click Limit
    • 画面端でクリックを無視する範囲です。特に不要な場合は0のままで構いません
  • Flat Rate
    • 手の震えを止めるための平均係数です。この値を小さくすると反応はよくなりますが、Leap Motionの誤差による震えが出やすくなります

特に調整する必要はないと思います。手を認識する場所を下げたい時に、Visible Underを設定するぐらいだと思います。

今後について

現在、UIのボタンは無理やりButtonコンポーネントを取得して、クリック時のイベントを実行するようにしています。レイヤーの指定などができず、他の要素のクリックもできないので、将来的にはEvent Systemに対応させることを考えています。

人差し指と薬指の高さ判定に手首の回転を考慮していませんが、試しに実装したら動作が不安定になったので削除した経緯があります。よい方法があればご教示いただければ幸いです。

プルリク歓迎です。

github.com

ライセンス

MITライセンス

参考URL

www.leapmotion.com

UnityのML-Agentsで、新しい学習環境を作成する(0.6.0a版)

0.7.0だと色々変更になったものと思います。たっつー様が0.7.0の導入方法を以下で公開して下さっていることをお知らせくださいました。

www.fast-system.jp


Unityで新規プロジェクトを作って、ML-Agentsの0.6.0aを組み込む手順です。

f:id:am1tanaka:20190117011444g:plain
こんな感じ

以下のML-Agentsリポジトリーのドキュメントを元に進めていきます。

github.com

目次

動作環境

以下で確認しました。

  • Windows10
  • Unity2018.2.10
  • ML-Agents Beta 0.6.0a

ML-Agentsのリポジトリーをクローンしていなかったり環境構築ができていない場合は、以下で済ませてください。

am1tanaka.hatenablog.com

0.5.0で環境を作成済みの場合

PythonとAnaconda、TensorFlowは、0.5.0と同じバージョンなのでそのまま使えます。Pythonの実行環境に0.6.0用のツールをインストールする必要があるので、新しく実行環境を作ってインストールします(0.5.0を全く使わなくなるのであれば、0.5.0用のものに上書きインストールしても大丈夫そうです)。

  • こちらML-Agentsツールキットリポジトリーのダウンロードに従って、ML-Agentsのリポジトリーをクローンするか、ダウンロードして、ドキュメントフォルダーなど使いやすいところに展開してください
  • スタートメニューなどから、Anaconda Promptを起動します

f:id:am1tanaka:20190117132534p:plain
Anaconda Promptを起動

  • 以下を実行して0.6.0用のPythonの実行環境を作成します。質問が表示されたらyキーでインストールを進めます
conda create -n ml-agents060 python=3.6
  • 完了したら、以下を実行して実行環境を開始します
activate ml-agents060
  • cdコマンドで、クローンやダウンロードした0.6.0のML-Agentsフォルダーを開きます
  • cd ml-agentsml-agentsフォルダーを開きます
  • 以下のコマンドを実行します
pip install -e .
pip install pypiwin32

以上で、0.6.0用のファイルがインストールされます(2行目のpypiwin32のインストールは、2019/1/17時点で発生するエラーを回避するためのものです。そのうち不要になるかも。こちら参照)。

Anaconda Promptはあとで利用するので開きっぱなしにしておいても構いません。

このチュートリアルで作るもの

ランダムな場所に登場するキューブを取るようにボールを操作するAIを学習して作ります。また、ボールが地面から落下しないようにもします。

  1. AIで動かすオブジェクトであるエージェント(Agent)が活動する環境(environment)をUnity上に作ります。環境は、いくつかのオブジェクトによるシンプルな物理シミュレーションから、ゲームやエコシステム全体まで含みます
  2. アカデミー(Academy)サブクラスを実装してゲームオブジェクトにアタッチして、環境を組み込んだUnityのシーン内に配置します。アカデミークラスには、AIエージェントとは別にシーンを更新するいくつかのオプションのメソッドを実装できます。例えば、環境内に、AIエージェントやその他のオブジェクトを追加、移動、削除したりできます
  3. 1つ以上のAIのブレイン(Brain=脳)を、AssetsメニューやProjectウィンドウのCreateボタンから、Create > ML-Agents > Brainを選択して作ることができます。名前は自由につけることができます
  4. エージェントサブクラスを実装します。エージェントサブクラスは、環境を観察するためのコードを定義して、行動を割り当てて、強化学習のための報酬(Reward=リワード)を計算します。また、学習が終了したり、タスクが失敗した時に、エージェントをリセットするメソッドを実装することができます

  5. AIで制御したいゲームオブジェクトに、作成したエージェントサブクラスをアタッチすることで、そのオブジェクトはAIで行動するようになります。エージェントには必ずブレインアセットを割り当てる必要があります

  6. AIを学習(Training)させる時は、アカデミーオブジェクトのBroadcastHub欄のControlチェックボックスにチェックを入れます
  7. 学習してできたモデルデータを使ってUnityでAIを動かすには、アカデミーオブジェクトのControlチェックボックスのチェックを外して、モデルデータをブレインに設定します。学習についてはこちら

Note

  • Unityの操作になじみがなく、このマニュアルの操作が理解できない場合は、Unityの公式マニュアルのLearning the interfaceをおすすめします。

Unityプロジェクトの作成

まずは、新規でUnityプロジェクトを作成して、ML-Agentsを組み込みます。

  • Unity2017.4以降を起動して、RollerBallなどの名前で新しいプロジェクトを作成します
  • Editメニューから、Project Settings > Playerを選択します

以下を、利用したいプラットフォームごとに実施します

  • Other Settingsを開きます
  • Scripting Runtime VersionExperimental、あるいは.NET 4.6 Equivalent、あるいは.NET 4.x Equivalentにします(2018.3ではデフォルトで4.xになっているのでこの手順は不要)
  • Restartするかのウィンドウが表示されたら、Restartします

ML-Agentsのデータをプロジェクトに読み込みます。

  • エクスプローラーで、ダウンロード(あるいはクローン)して展開したml-agentsフォルダーを開きます
  • UnitySDK/Assetsフォルダーを開きます
  • フォルダー内にある全てのファイルとフォルダーを選択したら、ドラッグしてUnityのProjectウィンドウにドロップしてインポートします

f:id:am1tanaka:20190115235829p:plain
Import ML-Agent System

Unityのバージョンによって表示は変わりますが、おおよそ以下のようになります。

f:id:am1tanaka:20190115235932p:plain
Imported Packages

環境(Environment)を作る

ML-Agentsの環境用のシーンとして、エージェントが乗っかる床となるPlane、エージェントがゴールや目標として探索するCube、エージェントを表すSphereを作成します。

床のPlaneを作る

  • HierarchyウィンドウのCreateをクリックして、3D Object > Planeを選択します
  • 作成したPlaneの名前をFloorにします
  • 作成したFloorをクリックして選択します
  • InspectorウィンドウのTransform欄を以下の通り設定します
    • Position = 0, 0, 0
    • Rotation = 0, 0, 0
    • Scale = 1, 1, 1
  • InspectorウィンドウのMesh Renderer欄にあるMaterialsの左の三角をクリックして開いたら、Element 0の右の〇をクリックして、ウィンドウからLightGridFloorSquareマテリアル(別のものでも構いません)を選択して割り当てます

f:id:am1tanaka:20190116000559p:plain
Set Material

以上で床は完成です。

取るためのキューブを作る

  • HierarchyウィンドウのCreateをクリックして、3D Object > Cubeを選択します
  • 作成したCubeの名前をTargetにします
  • 作成したTargetをクリックして選択します
  • InspectorウィンドウのTransform欄を以下の通り設定します
    • Position = 3, 0.5, 3
    • Rotation = 0, 0, 0
    • Scale = 1, 1, 1
  • InspectorウィンドウのMesh Renderer欄にあるMaterialsの左の三角をクリックして開いたら、Element 0の右の〇をクリックして、ウィンドウからBlockマテリアルを選択して割り当てます

エージェント(Agent)とするSphereを作る

  • HierarchyウィンドウのCreateをクリックして、3D Object > Sphereを選択します
  • 作成したSphereの名前をRollerAgentにします
  • 作成したRollerAgentをクリックして選択します
  • InspectorウィンドウのTransform欄を以下の通り設定します
    • Position = 0, 0.5, 0
    • Rotation = 0, 0, 0
    • Scale = 1, 1, 1
  • InspectorウィンドウのMesh Renderer欄にあるMaterialsの左の三角をクリックして開いたら、Element 0の右の〇をクリックして、ウィンドウからCheckerSquareマテリアルを選択して割り当てます
  • Add Componentをクリックします
  • PhysicsからRigidbodyを選択してアタッチします

後程、このオブジェクトにエージェントサブクラスをアタッチします。

アカデミー(Academy)を持たせるための空のゲームオブジェクトを作る

  • HierarchyウィンドウのCreateをクリックして、Create Emptyを選択します
  • 作成したGameObjectの名前をAcademyにします

以上でシーンが完成です。

f:id:am1tanaka:20190116001537p:plain
Created Hierarchy

Main Cameraの座標や角度を調整して、Gameウィンドウで見やすくなるようにしてください。

f:id:am1tanaka:20190116001428p:plain
Game Window Sample

アカデミーの実装

アカデミーオブジェクトは、シーン内のML-Agentsを動かして、観測-意思決定-行動というシミュレーションループを制御します。ML-Agentのシーンごとに1つのアカデミーのインスタンスが必要です。アカデミークラスのベースクラスはabstractなので、メソッドを実装する必要がなくてもサブクラスを作成する必要があります。

先に作成したアカデミーオブジェクトに新しいスクリプトを追加します。

  • HierarchyウィンドウからAcademyオブジェクトをクリックして選択します
  • Inspectorウィンドウで、Add Componentをクリックします
  • New Scriptを選択して、RollerAcademyという名前で作成します
  • 作成したRollerAcademyスクリプトをダブルクリックして、エディターで開きます
  • MLAgentsの機能を使うために、using MLAgents;を定義します
  • クラスの継承元をMonoBehaviourからAcademyに変更します
  • Start()Update()メソッドを削除します

基本的な機能はベースクラスのAcademyが持っているので、実装は不要です。以下の通りにしたら、上書き保存をしてUnityに切り替えます。

using MLAgents;

public class RollerAcademy : Academy { }

デフォルトの設定で問題なく動作しますのでそのままの設定で使います。Inspectorウィンドウで変更する項目はありません。

f:id:am1tanaka:20190116103613p:plain
Default MLAgents

ブレイン(Brain)を追加する

ブレインアセットは、行動を決める処理を担当します。エージェントは自らが観測した情報をブレインに送ります。ブレインは送られてきた観測情報を元に行動を決定してエージェントに伝えます。エージェントは、ブレインから受け取ったデータに従って行動します。作成するブレインアセットの種類によって、そのブレインの行動の決め方を選択することができます(Learning=AI学習, Heuristic=アルゴリズムで実装, Player=人間が操作)。

ブレインアセットを作成します(0.5.0からこの部分が完全に変わりました!)

  • ProjectウィンドウのCreateボタンをクリックして、ML-Agentsから、作成したいBrainの種類のアセットを選択します。ここでは、Learning BrainPlayer Brainを作成します

f:id:am1tanaka:20190116104825p:plain
Create Asset

f:id:am1tanaka:20190116104845p:plain
Create Brains

  • 作成したら、それぞれの名前をRollerBallBrainと、RollerBallPlayerにします

f:id:am1tanaka:20190116105019p:plain
作成した2つのBrain

設定は実際に動かす時に行うので、一先ずこのままで先に進めます。

エージェントの実装

エージェントを制御するスクリプトを作成します。

  • Hierarchyウィンドウから、RollerAgentオブジェクトをクリックして選択します
  • Inspectorウィンドウで、Add Componentをクリックします
  • New Scriptを選択して、RollerAgentという名前で作成します
  • 作成したRollerAgentスクリプトをダブルクリックして、エディターで開きます
  • Academyの時と同じように、usingを設定して、ベースクラスをMonoBehaviourからAgentに変更します

以下のようなコードになります。

using MLAgents;

public class RollerAgent : Agent { }

上書き保存をして、Unityに戻ります。

ここまでの手順は、あらゆるUnityのプロジェクトにML-Agentsを加える基本的な手順です。次に、強化学習を使ってSphereのエージェントがキューブを取るように動かすためのロジックを加えていきます。

今回のシンプルなシナリオでは、アカデミーオブジェクトに環境の変更は実装しませんが、シミュレーション前やシミュレーション中に、床の大きさを変えたり、エージェントやその他のオブジェクトを追加したり削除したりしたい場合は、アカデミーにメソッドを自由に追加して実装することができます。ここでは、エージェントが成功したり失敗した時に、エージェントや目標物をリセットすることだけを行います。

エージェントの初期化とリセット

今回作成するエージェントは、Targetに触れたら自分自身でそのことを確認して、Targetをランダムな座標に配置し直すリセット関数を呼び出します。また、地面から落下したらリセット関数によって地面に戻します。

Targetオブジェクトに辿り着くには、エージェントはTargetTransformへの参照を知っている必要があります(Transformには、3D空間における座標、回転、拡大率が記録されています)。そのために、RollerAgentクラスにpublicTransformを定義します。これにより、InspectorウィンドウでTargetTransformを渡せるようになります。

エージェントの速度をリセットしたり、エージェントを動かすための力を与えるために、Rigidbodyコンポーネントの参照が必要です。Rigidbodyは、Unityで物理シミュレーションをするための基本的なエレメントです。スクリプトのゲームオブジェクトにアタッチされているRigidbodyへの参照は、Start()メソッド内で、GameObject.GetComponent<T>()を使って取得します。

RollerAgent.csは以下のようになります。

using System.Collections.Generic;
using UnityEngine;
using MLAgents;

public class RollerAgent : Agent {
    Rigidbody rBody;
    private void Start()
    {
        rBody = GetComponent<Rigidbody>();
    }

    public Transform Target;
    public override void AgentReset()
    {
        if (this.transform.position.y < 0)
        {
            // エージェントが落ちた
            this.rBody.angularVelocity = Vector3.zero;
            this.rBody.velocity = Vector3.zero;
            this.transform.position = new Vector3(0, 0.5f, 0);
        }

        // ターゲットを新しい場所へ移動させる
        Target.position = new Vector3(
        Random.value*8-4,
            0.5f,
            Random.value*8-4
        );
    }
}

次は、Agent.CollectObservations()関数(情報を収集する関数)を実装します。

環境を観測する(Observing the Environment)

エージェントは、収集した環境の情報をブレインに送ります。ブレインは送られてきた情報を使って行動を決めます。エージェントを学習する時、あるいは、学習済みのモデルを利用する時に、観測(Observation)したデータをfeature vector(観測結果を表すベクトルデータ)としてニューラルネットワークに与えます。エージェントの学習を成功させるためには、正しい情報を与えなくてはなりません。どの情報を与えるのが正しいかを決めるよい指標は、問題を解決するために何が必要かを分析して考えることです。

今回の場合、エージェントは以下の情報を集めます。

  • ターゲットの位置
// :
AddVectorObs(Target.position);
  • エージェント自身の位置
// :
AddVectorObs(this.transform.position);
  • エージェントの速度。速度があれば、Targetを行き過ぎたり、床から転落したりするのを防ぐことを学習する助けになります
// :
AddVectorObs(rBody.velocity.x);
AddVectorObs(rBody.velocity.z);

以上、8次元のベクトル(TargetのX, Y, Z、AgentのX, Y, Z, Agentの速度X, Z)で、観測したデータをブレインに渡します。まとめると、以下のようになります。RollerAgent.csスクリプトRollerAgentクラス内に、以下のメソッドを追加します。

// 32:
    public override void CollectObservations()
    {
        // TargetとAgentの位置
        AddVectorObs(Target.position);
        AddVectorObs(this.transform.position);

        // Agentの速度
        AddVectorObs(rBody.velocity.x);
        AddVectorObs(rBody.velocity.z);
    }

エージェント用のコードの仕上げは、ブレインが選択した行動を受け取って、報酬を与えるAgent.AgentAction()メソッドの作成です。

Actions

ブレインが決めた行動は、AgentAction()関数に配列で渡されます。この配列の要素数は、エージェントのブレインのVector Actionの設定のSpace TypeSpace Sizeによって決まります。今回作成しているRollerAgentは、行動データを加える力として利用します。力はfloat値(連続的な値)として使いたいので、Space TypeContinuous(連続的)を選択します。行動に必要なデータの個数を表すAction Sizeには2を設定します。1つ目のエレメントaction[0]X方向に加える力、2つ目のaction[1]Z方向に加える力とします(もし、Y方向の移動もあった場合は、Action Size3にして、Y方向の力の分を増やします)。ブレインは、これらの値がどのようなものかは把握しません。学習は、観測した入力に対して返す値を調整した結果、どのような報酬が得られるかということだけに注目して行われます。

(補足) Space TypeDiscreteにすると、ブレインからは整数値が行動として返されるようになります。ボードゲームのマス目や、「止まる」「歩く」「パンチする」「食べる」など整数で表せる行動をさせたい場合は、Discreteにします。

RollerAgentaction[]配列の値をRigidbodyコンポーネントであるrBodyAddForce()で反映させます。以下、コード例です。実際に追加するコードは後述します。

// :
Vector3 controlSignal = Vector3.zero;
controlSignal.x = action[0];
controlSignal.z = action[1];
rBody.AddForce(controlSignal * speed);

報酬(Rewards=リワード)

強化学習(Reinforcement learning)では報酬の設定が必要です。報酬は、AgentAction()関数内で割り当てます(他の場所では無効になるので注意!!)。学習アルゴリズムは、シミュレーションのステップごとにエージェントに報酬を与えて、エージェントにとって最適な行動が何かを学習するのに利用します。エージェントが目的を達成したら(今回ならTargetオブジェクトに辿り着いたら)報酬を与えて、望まない行動をしたら(今回なら床から転落したら)報酬を取り上げます。今回はTargetオブジェクトに辿り着いたら1の報酬を与えます。

RollerAgentは、Targetに辿り着いたかを検出するために距離を計算します。算出した距離が到達したと判断する値以下になっていたら、Agent.SetReward()メソッドを呼び出してエージェントに1の報酬を与えて、完了したことを報告するためにDone()を呼び出します。

(補足) 報酬を与えるメソッドが、AddRewardからSetRewardに改名されました。

// :
float distanceToTarget = Vector3.Distance(this.transform.position,
                                          Target.position);
// ターゲットに辿り着いたか
if (distanceToTarget < 1.42f)
{
    SetReward(1.0f);
    Done();
}

メモ: Done()を呼び出してエージェントが完了したことを知らせると、リセットするまで動作が停止します。Agent.ResetOnDoneプロパティーをインスペクター上でTrueに設定しておくことで、完了したらすぐに自動的にエージェントをリセットすることもできますし、アカデミーがMaxStepに到達して、環境をリセットするのを待つこともできます。今回はResetOnDoneプロパティーTrueに設定してリセットします。MaxStep0のままにして、アカデミーが環境をリセットしないようにします。

最後に、プラットフォームから転落した時にDoneを呼び出して、次のステップで自分自身をリセットさせます。

// :
// プラットフォームからの転落
if (this.transform.position.y < 0)
{
    Done();
}

(補足) 0.5.0では床から転落した時に報酬を-1していましたが、その処理がなくなっています。あえて報酬を下げなくても、報酬が得られないだけで学習には十分なようです。

AgentAction()

行動と報酬の考え方は以上の通りです。AgentAction()関数の完成形は以下のようになります。RollerAgent.csRollerAgentクラス内に加えてください。

// 43:
    public float speed = 10;
    public override void AgentAction(float[] vectorAction, string textAction)
    {
        // 動作の設定。sizeが2なのでvectorActionは2つ
        Vector3 controlSignal = Vector3.zero;
        controlSignal.x = vectorAction[0];
        controlSignal.z = vectorAction[1];
        rBody.AddForce(controlSignal * speed);

        // Targetとの距離
        float distanceToTarget = Vector3.Distance(
            this.transform.position,
            Target.position);

        // Targetに接触したか
        if (distanceToTarget < 1.42f)
        {
            SetReward(1);
            Done();
        }

        // プラットフォームから転落
        if (this.transform.position.y < 0)
        {
            Done();
        }
    }

speedプロパティーを関数の前にpublicで定義することで、Inspectorウィンドウで速さを調整できるようにしてあります。

仕上げのエディター設定

全てのゲームオブジェクトとML-Agentコンポーネントの設定が完了したので、データを接続していきます。

  • HierarchyウィンドウのAcademyオブジェクトをクリックして選択します
  • ProjectウィンドウのBroadcast Hubの下のAdd Brain to Broadcast Hubボタンをクリックします

f:id:am1tanaka:20190116131430p:plain
1つ目のBrain欄を追加

  • もう一つ設定したいので、Add Newボタンをクリックして増やします

f:id:am1tanaka:20190116153255p:plain
Add Newで2つ目のBrain欄を追加

  • ProjectウィンドウからRollerBallBrainをドラッグして、InspectorウィンドウのBrains欄にドロップします。RollerBallPlayerも同様に2つ目のBrains欄にドラッグ&ドロップします

f:id:am1tanaka:20190116153857p:plain
Brainアセットを設定

  • HierarchyウィンドウからRollerAgentオブジェクトをクリックして選択します
  • ProjectウィンドウからRollerBallPlayerをドラッグして、InspecorウィンドウのBrain欄にドロップします
  • InspectorウィンドウのDecision Frequency10にします(ブレインは10ステップごとに意思決定をして、行動は毎フレーム実行します)
  • HierarcyウィンドウからTargetオブジェクトをドラッグして、InspectorビューのTarget欄にドロップします

f:id:am1tanaka:20190116154114p:plain
RollerAgentの設定

最後に、Brainアセットの設定をします。

  • ProjectウィンドウからRollerBallBrainアセットをクリックして選択したら、Inspectorウィンドウで以下を設定します。
    • Vector ObservationSpace Size = 8
      • ブレインに送る観測データベクトルの次元数(要素数)
  • Vector ActionSpace Type = Continuous
    • 行動結果を表すベクトルを、整数(Discrete=不連続)で返すか、小数(Continuous=連続)で返すか
  • Vector ActionSpace Size = 2
    • 行動結果として返すベクトルの次元数(要素数)

ProjectウィンドウからRollerBallPlayerアセットをクリックして選択して、以上と同じく設定します。これで環境を試すための準備が整いました。

f:id:am1tanaka:20190116154929p:plain
Brainアセットの設定

環境を手動で試す

実際に学習を開始する前に、手動で環境を試すのがおすすめです。RollerAgentBrainRollerBrainPlayerを設定したので、キー入力でAgentオブジェクトを操作することができます。実行する前に、前後左右の4方向に移動するためのキーを4つ割り当てます。

  • ProjectウィンドウからRollerBallPlayerアセットをクリックして選びます
  • InspectorウィンドウのKey Continuous Player Actions欄を開きます(この設定は、PlayerBrainのみ表示されます)
  • Size4にします
  • 以下の通り、設定します
Element Key Index Value
Element 0 D 0 1
Element 1 A 0 -1
Element 2 W 1 1
Element 3 S 1 -1

Indexの値は、AgentAction()関数に渡す配列のインデックスを表します。Valueの値は、Keyが押された時にvectorAction[Index]に渡される値です。

Playすると、[W][A][S][D]キーで、Agentを操作することができます。エラーが発生しないことと、Targetに触ったり床から落ちたらエージェントがリセットされることを確認してください。ML-Agents SDKには、ゲームウィンドウにエージェントの状態を簡単に表示させることができるMonitorクラスが用意されています。より詳細な状態が見たい場合は利用するとよいでしょう。

(ここではやりませんが)作成した環境とPython APIが想定通りに動くかどうかを、Jupyter notebookを使って試すことができます。確認のためには、notebooks/getting-started.ipynbを利用します。ノートブックのenv_nameに、この環境をビルドして生成した実行ファイル名を設定します。

環境の学習

学習を開始するには、AgentのBrainをRollerBallBrainに変更して、AcademyオブジェクトのControlプロパティーにチェックを入れます。

  • HierarchyウィンドウでRollerAgentをクリックして選択します
  • ProjectウィンドウからRollerBallBrainをドラッグして、InspectorウィンドウのBrain欄にドロップします

f:id:am1tanaka:20190116160552p:plain
Brainを入れ替え

  • HierarchyウィンドウでAcademyオブジェクトをクリックして選択します
  • InspectorウィンドウのBroadcast Hub欄に設定したRollerBallBrainの右のチェックボックスにチェックを入れます

f:id:am1tanaka:20190116160825p:plain
RollerBallBrainを有効化

これで、AIのブレインを利用するようになります。ここからの設定は、Training ML-Agentsと同様です。

mlagents-learnプログラムに渡すconfigurationファイル内の学習のためのパラメーターを設定します。Clone、あるいはダウンロードしたml-agentsフォルダー内のconfig/trainer_config.yamlファイルにデフォルト値が設定されています。

f:id:am1tanaka:20190116222250p:plain
学習設定

今回は、この設定ファイルのdefault設定を利用します。そのままの設定で学習させる場合、30万ステップ程度学習させる必要があります。しかし、以下の設定をすることで、2万ステップ程度で学習を完了させることができるようになります。

batch_size: 10
buffer_size: 100

今回のサンプルは、入力も出力も数が少ないシンプルな学習なので、小さなバッチ(batch)やバッファー(buffer)サイズにして、学習を高速化することができます。ただし、環境が複雑になったり、報酬や観測方法を変更したら、このパラメーターは調整が必要になるかも知れません。

Note: trainer_config.yamlに加えて、AgentDecisionFrequencyパラメーターも、学習時間や成功するかどうかに大きく影響します。大きな値を設定すると、学習アルゴリズムが行動を判断する回数を減らすことができるので、今回のようなシンプルな環境では学習のスピードアップにつながります。

エディターで学習を開始するには、UnityでPlayする前に、ターミナルやコマンドプロンプトからPythonコマンドを実行します。

  • Anaconda Promptを起動していなかければ、スタートメニューなどから起動します
  • 以下を実行して、ML-Agents用の実行環境を開始します
activate ml-agents060
  • クローンやダウンロードしたml-agentsフォルダーにcdコマンドで移動します
  • 初めて学習する時は、設定を調整するために、configフォルダー内のtrainer_config.yamlを何らかのエディターで開きます
  • defaultの設定の以下の部分を書き換えます

f:id:am1tanaka:20190116233317p:plain
config.yaml

  • 名前を付けて保存を選択して、config.yamlの名前で保存します
  • Anaconda Promptで以下を実行します
mlagents-learn config/config.yaml --run-id=RollerBall-1 --train

しばらく待つと以下のような画面が表示されて、最後にUnityをPlayするようにメッセージが出るので、UnityをPlayしてください。

f:id:am1tanaka:20190117002030p:plain
レーニング開始

Unity上で学習が始まります。2万ステップの学習が完了したら、Anaconda Promptが以下のように表示されて学習が完了します。

f:id:am1tanaka:20190117002241p:plain
レーニング完了

学習の様子は、TensorBoardを使ってグラフで確認できます。

Anaconda Promptで、以下を実行します。

tensorboard --logdir=summaries

以下のようなメッセージが表示されます。

f:id:am1tanaka:20190117002833p:plain
ログの確認

  • Webブラウザーで、メッセージの最後の部分に書かれているhttp://で始まるURLを開いてください
  • 以下のようにグラフが確認できます

f:id:am1tanaka:20190117003106p:plain
TensorBoard

Environment欄のCumulative Rewardと、Policy欄のValue Estimateのグラフで、エージェントが目的をどれぐらい達成できたかを把握できます。今回の例では、エージェントが得られる最大の報酬は1.0です。エージェントが目的を達成するほど、グラフは1.0に近づきます。

Note: TensorBoardを利用する場合、mlagents-learnコマンドで学習を実行するたびに、run-idの部分を増やすなどして変更した方がよいかも知れません。同じrun-idで学習させると、過去の学習結果のグラフも重ねて表示されるので、傾向の把握が難しくなる可能性があります。

f:id:am1tanaka:20190117153604p:plain
同じrun-idで学習させた時のグラフ

シーンのまとめ

Unity環境で、ML-Agentsを利用する時にシーンをどのように構成するかをまとめます。

Unity ML-Agentを利用する場合、一つのアカデミーと、一つ以上のエージェントゲームオブジェクトが必要です。また、利用したい種類のブレインアセットを作成して、エージェントアカデミーに設定する必要があります。

約束事です。

  • 一つのシーンには、アカデミーゲームオブジェクトを一つだけ配置します
  • 学習に利用できるブレインは、アカデミーBroardcast Hubリストに追加したものだけです

参照したドキュメントは以上です。以下はこのブログでの追記です。

学習したモデルを試す

学習が完了したモデルを使って、実際にRollerAgentを動かしてみます。再生するには、TensorflowSharpプラグインをプロジェクトにインポートする必要があります。0.5.0の時と同じプラグインが利用できるので、ダウンロード済みのTFSharpPlugin.unitypackageがあればそれを利用できます。

f:id:am1tanaka:20190117005821p:plain
TFSharpパッケージのダウンロード

  • ダウンロードしたTFSharpPlugin.unitypackageをドラッグして、Projectウィンドウにドロップして、インポートします
    • PC上で試すだけなら、AndroidiOSプラグインは不要なので、チェックを外した方が容量やインポート時間を節約できます。

f:id:am1tanaka:20181114001257p:plain

  • Editメニューから、Project Settings > Playerを開きます
  • Other SettingsScripting Define Symbolsに、ENABLE_TENSORFLOWと入力します
  • Allow 'unsafe' Code欄にチェックを入れます

以上、ビルドしたい全てのタブでおこないます。

f:id:am1tanaka:20190117010625p:plain
Tensorflowを有効にする

学習させたモデルデータをブレインに設定します。

  • エクスプローラーなどで、ML-Agentsをクローンやダウンロードしたフォルダーを開きます
  • フォルダー内のmodels > RollerBall-1を開きます
  • RollerBallBrain.bytesをドラッグして、UnityのProjectウィンドウにドロップしてインポートします

f:id:am1tanaka:20190117005024p:plain
モデルファイルをインポート

  • Projectウィンドウから、RollerBallBrainアセットをクリックして選択します
  • Projectウィンドウから、インポートしたRollerBallBrainをドラッグして、InspectorウィンドウのModel欄にドロップします

f:id:am1tanaka:20190117005243p:plain
モデルデータを設定

  • HierarchyウィンドウからAcademyオブジェクトをクリックして選択します
  • InspectorウィンドウのRollerBallBrain欄のControlのチェックを外します

以上で学習したモデルデータを使ってRollerAgentが動くようになります。

f:id:am1tanaka:20190117011444g:plain
完成!!

おまけ

default以外の設定を使う

ドキュメントではconfig.yamldefault設定を書き換えましたが、ブレインアセットの名前をキーにして、ブレインごとの設定を作ることができます。

f:id:am1tanaka:20190117155615p:plain
ブレイン用の設定を作る

上記のようにすることで、defaultを書き換えなくても、RollerBallBrainを学習させるときに利用したい設定をすることができます。

ログの見方

コンソール画面に謎の文字が表示されますが、ここから学習がどのように進んでいるかを読み取ることができます。このログは、設定した回数ごとの集計になっています。デフォルトでは千回に1回、集計されて以下のように表示されます。

f:id:am1tanaka:20190117155837p:plain
ログ

重要なのはStep以降の部分です。

Step

何ステップ学習したかを表示します。今回は2万ステップ設定したので、Step: 20000.で終了しています。

Mean Reward

獲得した報酬の平均値です。最初の千回の平均は0.212で、かなり失敗していることが分かります。学習を進めることでどんどん改善して、1万4千ステップには1.000を達成しています。ほぼ全部ターゲットを取ったということです。

Std of Reward

報酬の標準偏差です。標準偏差とは、データのバラつきを表す値です。全てのリワードが同じ値ならこの値は0になり、バラけているほど大きな値になります。

AIがめちゃくちゃに動いている最初の方は、得られる報酬がバラバラなので大きな値になりがちです。頭が良くなるにつれて行動が似てくるので報酬のバラつきが減り、この値は小さくなっていくのが一般的です。

最初からこの値が小さい場合、めちゃくちゃに動いても報酬に変化がないことになります。この場合、学習のしようがないので報酬の与え方がよくないか、エージェントの設定に失敗している可能性があります。

学習の再開

同じrun-idで学習を開始しても、前に学習した内容は破棄されて学習はやり直しになります。今回は2万ステップで学習は十分でしたが、試した結果、もう少し学習を継続させたい場合があります。その時は、config.yamlを開いてmax_stepsの値を増やして上書き保存をしてから、Anaconda Promptで以下を実行すれば、先ほどのデータの続きから学習を再開できます。

mlagents-learn config/trainer_config.yaml --run-id=RollerBall --train --load

注意!!: すでに学習ステップ数がmax_stepsに達していたら、学習を再開させてもすぐに終わってしまいます。学習回数を増やす場合は、config.yamlmax_stepsを増やす必要があります。

学習効果を高める(おまけ)

0.5.0と共通の知見としては、以下が挙げられます。

バッチとバッファの設定を適切にする

default値のまま学習すると、2万ステップでは学習が全く足りませんでした。どのぐらいの値がよいかの指標があるのかないのかも含めて調査したい項目です。

ベクトルの観測データはベクトルのまま渡す

観測データ(CollectObservations()メソッド内でAddVectorObs()メソッドで渡すデータ)がベクトルの場合、使ってないデータがあってもベクトルのまま渡した方がよさそうでした。

今回のサンプルでは、RollerAgentXZの速度を個別のAddVectorObs()メソッドで渡していますが、これによって学習効果が下がってしまうようです。

RollerBallBrainアセットのSpace Size9にして、RollerAgent.csスクリプトCollectObservations()メソッドを以下のように書き換えて、学習しなおすと効果が分かります。

// 36:
    public override void CollectObservations()
    {
        // TargetとAgentの位置
        AddVectorObs(Target.position);
        AddVectorObs(this.transform.position);

        // Agentの速度
        AddVectorObs(rBody.velocity);
    }

以下、学習結果です。青がサンプルのまま。オレンジがRollerAgentの速度をベクトルで与えた時のものです。

f:id:am1tanaka:20190118113458p:plain
観測データをベクトルにするかどうかの違い

速度をベクトルで与えたところ、1万ステップの辺りでMean Reward1.000になることもありました。ベクトルデータはベクトルのまま渡した方がよさそうです。

まとめ

Unityの新規プロジェクトに学習用の環境を構築して、Pythonと連携した強化学習の実行、そして学習結果のモデルデータを利用してエージェントを動かすことができました。

0.5.0との目立った違いとしては以下が挙げられます。

  • ブレインがアセットになって、設定が楽になった
  • 学習の切り替えを、AcademyのControlチェックでできるのも楽
  • 報酬を設定するメソッドがSetRewardに変更になった

あとはおおよそそのままいけました。慣れれば導入はかなり簡単だと思います。楽しいAIライフを!

参考URL

Unity2018.3.1にしたらRigidBodyのkinematicでなんかエラーが出た

こんなエラーが出ました。

[Physics.PhysX] RigidBody::setRigidBodyFlag: kinematic bodies with CCD enabled are not supported! CCD will be ignored.

CCDってなんだ。これだ・・・Unity - Manual: Continuous collision detection (CCD)

高速時にオブジェクトを抜けなくするための設定です。isKinematicを有効にした場合、Rigidbodyによる移動ではなくなるので、CCDが効かなくなるということで、2018.3ではエラーになってしまうようです。

エラー表示にはなってますが、Playできるので本来は警告で表示するはずだったのを、エラーログで出しちゃってるっぽいです。ただ、効果がないなら直した方がいいのでエラー表示でもいいかも。

目次

原因

以下のような設定をしていると、エラーになります。

f:id:am1tanaka:20190115191150p:plain
エラー例

Is Kinematicを有効にする場合は、Collision DeteictionDescreteにします。

以下ならOK。

f:id:am1tanaka:20190115191530p:plain
正しい設定

問題のゲームオブジェクトを探す

Hierarchyウィンドウの検索バーにRigidbodyと入力すれば、Rigidbodyを持っているゲームオブジェクトが見つかります。表示されたオブジェクトをチェックして、問題ないか確認します。

f:id:am1tanaka:20190115195145p:plain
オブジェクトの見つけ方

問題のある個所が見つからない時

Hierarchyにあるオブジェクトが全て問題ないのに、Playの終了時にエラーが発生する場合があります。その場合はプレハブを確認してみてください。登場しているオブジェクトに問題がなくても、プレハブにisKinematicが有効で、Discrete以外の衝突検出モードを設定しているとエラーが出ました。

スクリプトでの切り替え方

スクリプトで切り替えたい場合は以下のような感じです。

Rigidbody rb = GetComponent<Rigidbody>();
rb.collisionDetectionMode = CollisionDetectionMode.Discrete;
rb.isKinematic = true;

参考URL

ボクセルキャラを粘土で作る!

この記事はVoxel Advent Calendar 2018 20日目のものです。ちょっと趣向を変えて、リアルにボクセルのキャラクターを作る記事です。

昨日は、はやなささんの自分のボクセル創作を振り返った話 - hayanasa’s Flower Factoryでした。明日はMagicaVoxelからUnityにキャラクターを読み込むときにめちゃくちゃお世話になっているブログを書いてくださったdaishihmrさんです!

目次

ボクセルキャラを作る!

現在、ボクセルで描いたキャラクターを主人公にしたパズルゲームVoxelorerBird(ボクセローラーバード)を開発してます。主人公はこんなキャラ。

f:id:am1tanaka:20181218220432p:plain
ハク

なんかグッズも欲しいなぁと思って粘土で作ることにしました。

f:id:am1tanaka:20181215121442j:plain
ドン!

とりあえずこんな感じ。仕上がりはちょっと微妙ですが、一応それっぽい感じにはできそうです。ということで、材料や作り方をご紹介します!

材料

キューブ型

レジンや粘土用のキューブのシリコン型です。アートギャラリーフローレのソフトモールド キューブ型を使ってます。これに粘土を詰めて、キューブを作ります。

f:id:am1tanaka:20181215120041j:plain
シリコン型

いくつかのサイズがありますが、2番目に小さい3.5mmのものがちょうどよい感じです。同時に8つ作れます。

粘土

粘土は何種類か試してみて、Heartyが個人的には一番使いやすかったです。

f:id:am1tanaka:20181215120108j:plain
Hearty

マットな感じになるので、今回のような動物キャラには向いていると思います。

もう1種類よかったのがGraceです。Heartyに比べるとちょっとお高いですが、ピカピカな硬質な仕上がりになります。目とクチバシはGraceの黒で作りました。

f:id:am1tanaka:20181215120129j:plain
Grace

アクリル角棒 3mm

粘土を詰めるために東急ハンズで買いました。

f:id:am1tanaka:20181215120207j:plain
アクリル角棒3mmx3mm

最初は爪楊枝の裏側で詰めていましたが、角まで押し込むのに時間がかかりました。角棒だと面積があるので押し込みやすいです。東急ハンズで100円ちょっとだったと思いました。

木工用ボンドと筆

キューブを接着するためのセットです。木工用ボンドは普通の速乾タイプ。筆は100円ショップで買いました。

f:id:am1tanaka:20181215121221j:plain
木工用ボンドと筆

木工用ボンドを水で薄めて使うのですが、受け皿はインスタントコーヒーの蓋を使ってます。まあ、何でもいいです。

その他

あとは、爪楊枝、サランラップ、粘土を保存するためのビニール袋といった日用品で全てです。

今回のキャラ用の材料費

シリコン型は4つ買いました。白と黒のキューブをそれぞれ同時に16個ずつ、合計32個作れる体制です。モノトーンのキャラなので、粘土はHeartyの白と黒、目とクチバシ用のGraceの黒が1袋ずつです。一番使ってる白でもまだ半分も減ってません。当面は1袋で十分です。

あとは一つずつということで、スターターキットは4千円ちょっとでした。一度揃えれば、あとは粘土とか消耗品は安いので結構この後はお安そうです。

値段
シリコン型4つ 2,580円
Hearty白と黒 940円
Grace黒 164円
木工用ボンド 185円
108円
アクリル棒 150円ぐらいだったかな?
合計 4,127円(ぐらい)

キューブの作り方

粘土のキューブの作り方です。

こねる

まずは粘土を10秒~20秒ほどこねて、柔らかさを均等にします。最初は水分が多くて、手にくっつくような状態かも知れません。それだと乾くのが遅くなるので、平たくして5分ぐらい乾かしてから使った方がよいかも知れません。

粘土がべたべたしなくなって滑らかになったら、シリコン型に詰めていきます。

シリコン型に詰める

キューブの半分ぐらい埋まる程度の薄さで、シリコン型に押し付けるようにして粘土を延ばします。あとは、アクリル棒でしっかりと角まで粘土がいきわたるように押していきます。

f:id:am1tanaka:20181215120316j:plain
3.5mmのCubeに粘土を詰める

裏側から見ると、詰まり具合が見えますので、角までうまく粘土が行き渡っていたら、さらに粘土を足して、キューブからはみ出して構わないので、アクリル棒で押し込みます。

余分な粘土を削り取る

粘土がキューブにしっかりと詰められたら、爪楊枝の腹の部分を使って、余分な粘土を取り除きます。

f:id:am1tanaka:20181215120455j:plain
ならす

大雑把にやると粘土がめくれたりするので、爪楊枝をよい塩梅で転がしつつ押し広げる感じで平らにします。粘土は乾燥すると少し縮むので、へこむよりは、少し盛り上がっているぐらいの方がよいと思います。シリコン型にはみ出した分は、粘土のかたまりを押し付けるとくっついて取りやすいです。

この作業に一番時間がかかります。道具を工夫すればもっと奇麗で手早くできるかも知れません。まだ研究中です。

2.5mmキューブ

ついでなので、2.5mmの一番小さいキューブも作ってはいます。このサイズだと爪楊枝の裏側で押し込めます。

f:id:am1tanaka:20181215120718j:plain
2.5mmは爪楊枝で

表面をならす

詰め終わったら、ビニールなど表面がなめらかなものを敷いて、上から爪楊枝でならして、表面のでこぼこを減らします。

f:id:am1tanaka:20181215121007j:plain
表面をならす

効果はあるようなないような、まあ、おまじないみたいなものでやってます。

完了!

ということで、一丁あがりです。

f:id:am1tanaka:20181215121135j:plain
詰め終わり

自分の場合、4セット揃えるのに30分ぐらいかかります。時給換算するとこれはどうだろう...という感じですが、息抜きの手の運動ということで。

粘土をしまう

粘土はとても1回では使いきれません。残りは、こんな感じでラップで口を閉じた上で、ジップロックに入れて空気を抜いて、冷蔵庫で保管しています。

f:id:am1tanaka:20181215120946j:plain
仕舞い方

夏から4ヶ月ぐらい経ってますが、ちゃんと空気を抜いておけばまだ使えています。

乾燥

最初は、詰めたらすぐに取り外して、どんどん量産できるものと思っていました。実際やってみると、完全に乾かないと形が崩れてしまって取り出せませんでした。

真夏で半日冬だと1日はかかります。のんびり待ちます^^; 冷蔵庫だと乾燥しているので早く乾くという情報もありますが、うちの冷蔵庫は乾燥しない感じの冷蔵庫らしく全然乾きませんでした...。一度、試してみるとよいと思います。うまくいくと結構早く乾燥するかも知れません。

乾いたらシリコン型を裏返して、裏からデコピンをすればポロポロと外れます。完全に乾いていれば形は崩れませんので、粘土の周りのシリコンを少し押して、キューブが少し出たところを爪に引っかけて取り出しても大丈夫です。ということで、こんな感じでできあがります!

f:id:am1tanaka:20181215121318j:plain
出来上がり

シリコン型を4つ持っていても、1日で作れるキューブの数は32個。夏なら頑張って64個。キャラクターを作るのはなかなかの長期戦です^^;

接着

キューブができたら、木工用ボンドで接着します。木工用ボンドをそのまま使うと、粘土をくっつける時にはみ出して、そこが変色してしまったりします。そこで、水で薄めることにしました。割合は適当で1対1ぐらいでよいと思います。何らかの器に木工用ボンドを少し出して、同じぐらいの量の水も一緒に垂らして、筆で薄めながら粘土の接着面に薄く塗ります。

組み立て方は、表面の層から1列をまずくっつけて、繋げた列同士をまたくっつけて面を作るという感じでやりました。例えば、まずは以下の白、黒、黒、黒、白の5つのキューブをくっつけて1列作ります。

f:id:am1tanaka:20181219203039j:plain
最初の1列

接着は結構適当で大丈夫で、ボンドを塗ってはくっつけ、ボンドを塗ってはくっつけしてまず5つ繋げてしまってから、平らな面に押し付けながら、端から爪楊枝の後ろでぎゅうぎゅう押して10秒ぐらい待つと離れなくなります。そんな感じで、次は白を7つ、その次は白、Graceの黒、白3つ、Graceの黒、白で1列、という感じで顔のパーツを1列ずつ作っていきます。

30分ほどすれば、少し力が加わったぐらいだと大丈夫になるので、作った列同士をボンドでくっつけていきます。あまり強く力を加えるとまだこの段階では形が崩れるので、爪楊枝の腹の部分や、アクリル角棒などをうまく使って力を加えてくっつけました。1日放っておけば、かなりがっちりと乾きますので、列だけ作っておいて、あとは次の日とする方が確実かも知れません。

1列できたら、もとのボクセルモデルを見ながら1列奥を同じようにくっつけていきました。裏から見るとこんな感じです。

f:id:am1tanaka:20181219203807j:plain

キューブ自体がそれほどちゃんとした立方体ではないので、力技っぷりがあふれる裏側ですが、表はなんとか形になってます^^;

完成

改めて現在できてるやつ。

f:id:am1tanaka:20181215121442j:plain
ななめ

f:id:am1tanaka:20181215121424j:plain
見上げ

f:id:am1tanaka:20181215121451j:plain
見下ろし

デジゲー博に連れて行きました。

f:id:am1tanaka:20181219205234j:plain
デジゲー博のブース

f:id:am1tanaka:20181219205254j:plain
ココ

まとめ

クオリティは微妙な感じですが、立体物が作れるのは面白いです。また、立体にせずに平面でくっつけて、ニスで仕上げてキーホルダーにするとかもできるかも知れません。

粘土は、基本的な色は揃っていて発色もいいです。粘土を混ぜて色を作ることもできますし、白の粘土に絵具で着色することもできます。そのあたりは自由度が高いと思います。粘土に磁石を練りこんで、マグネットにできると面白いなとか思ってます。

シリコン型はレジンにも使えるので、レジンのキューブを作ることもできます。そうすればもっと奇麗なものが作れるかも知れません。粘土でも、器用で仕事が丁寧な人ならもっと奇麗にできそう。。。

ちょっと熱が冷めて最近あまり作業を進めてませんが、またぼちぼち組み立てたりキューブを作っていこうと思います^^

明日は、daishihmrさんです!

ML-Agents Beta 0.6.0のリリースノート

Unityで手軽に強化学習などのAIを使うことができるML-Agentsの0.6.0が出てました。

github.com

Releaseページ

とりあえずリリースページにあった内容をざっくりと。

重要

BrainがMonoBehaviourからScriptableObjectに変更されました。これにより、Brainをプレハブに設定して、同じBrainをマルチシーンで使えるようになりました。詳しくはml-agents/Migrating.md at 0.6.0 · Unity-Technologies/ml-agents · GitHubを参照してください。

  • InternalExternalBrain Typeが、LearningBrainアセットによって置き換えられました
  • Heuristic Brainタイプが、HueristicBrainアセットに置き換えられました
  • Player Brainタイプが、PlayerBrainアセットに置き換えられました
  • Brainは、AcademyコンポーネントBroadcast Hubを通して、Pythonのトレーニングプロセスから参照されるようになりました

新機能

  • [Unity] Demonstration Recorder. Unityエディターから、Agentの行動や観測内容を記録することができるようになりました。記録したデータを使って、Agentを後でトレーニングすることができます。記録したトレーニングデータを使うことで、複数のトレーニングセッションでトレーニングすることができるようになりました
  • [Communication] Windowsマシン上のprotobuf-definitions内のprotobufオブジェクトを生成するためのmake_for_win.batが追加されました
  • LearningBrainに、モデルとBrainのパラメーターが一致していなかったときに警告するデバッグメッセージを追加しました

変更点

  • 学習済みモデル(trained model)からGraph Scopeが削除されました。同じセッション内で複数のBrainがトレーニングされる際、複数のGraph Scopeを1つのGraphにまとめるのではなく、Brainごとに1つのGraphが生成されるようになりました

既知の不具合

  • Windowsでは、[Ctrl]+[C}キーでトレーニングを終了した時にモデルが保存されません

まとめ

Brainタイプが変更になったので、0.5.0のプロジェクトを利用する場合は、Agentの再設定が必要なようです。ScriptableObjectにBrainの設定をするようになるので、再利用が楽になりそうです。年末年始にエアホッケーのトレーニングに手をつけようと思ってましたが、0.6.0でやってみようと思います。

参考URL

Photon BoltでPhysicsのボールは動くのか

2019/7/28現在、この記事の内容が古くなっていたので以下にPhoton Bolt 1.2.9対応に書き直しました。以下のリンク先の記事をご覧ください。

am1tanaka.hatenablog.com


続きを読む

UnityのML-Agentsで、新しい学習環境を作成する

このブログはML-Agents 0.5.0aのものです。ML-Agents 0.6.0a用のものをこちらにアップしました。


ML-Agentsの動作環境は整えられました。次は、新しいプロジェクトにML-Agentsを組み込む手順を確認します。以下のドキュメントを読んでいきます。

github.com

以下は、元記事から変更や補足したところです。

  • ブレインの名前の変更
  • ターゲットに近づくとリワードがもらえる機能のコードの追加
  • 学習手順と再生手順とまとめを追加

目次

動作環境

以下で確認しました。

  • Windows10
  • Unity2018.2.10
  • ML-Agents Beta 0.5.0a

ML-Agentsのリポジトリーをクローンしていなかったり、環境構築ができていない場合は、先に以下の記事を完了させてください。

am1tanaka.hatenablog.com

このチュートリアルで作るもの

ランダムな場所に登場するキューブを取るようにボールを制御するAIをトレーニングします。また、ボールが地面から落下しないようにもします。

  1. Unity上でAIをトレーニングするための環境(environment)を作ります。環境は、いくつかのオブジェクトによるシンプルな物理シミュレーションから、ゲームやエコシステム全体まで含みます
  2. Academy(アカデミー)サブクラスを実装してゲームオブジェクトにアタッチして、環境を組み込んだUnityのシーン内に配置します。このゲームオブジェクトは、シーン内の様々なブレイン(Brain=AIの脳)の親として動作します。アカデミークラスには、AIエージェントとは別にシーンを更新するいくつかのオプションのメソッドを実装できます。例えば、環境内に、AIエージェントやその他のオブジェクトを追加、移動、削除したりできます
  3. 1つ以上のAIのブレインオブジェクトを、アカデミーの子供としてシーンに追加します
  4. エージェントサブクラスを実装します。エージェントサブクラスは、環境を観察するためのコードを定義して、行動を割り当てて、強化学習のための報酬(Reward)を計算します。また、学習が終了したり、タスクが失敗した時に、エージェントをリセットするメソッドを実装します
  5. 作成したエージェントサブクラスをゲームオブジェクトにアタッチします。アタッチしたオブジェクトは、シミュレーション内のエージェントとしてシーン内で行動します。エージェントには必ずブレインオブジェクトを割り当てる必要があります
  6. Brain TypeExternalで学習させます
  7. Brain TypeInternalにして、学習してできたモデルデータを使ってUnityでAIを動かします

Unityプロジェクトの作成

まずは、新規でUnityプロジェクトを作成して、ML-Agentsを組み込みます。

  • Unity2017.4以降を起動して、RollerBallという名前で新しいプロジェクトを作成します
  • エクスプローラーで、ダウンロード(あるいはクローン)して展開したml-agentsフォルダーを開きます
  • UnitySDK/Assetsフォルダー内のML-Agentsフォルダーをドラッグして、UnityのProjectウィンドウにドロップしてインポートします

f:id:am1tanaka:20181110121550p:plain

Unityのバージョンによって表示は変わりますが、おおよそ以下のようになります。

f:id:am1tanaka:20181110121637p:plain

ML-Agentsツールキットを有効にする

公式ドキュメントだと、ML-Agentsの設定の手順が抜けているので、以下のようなエラーが発生すると思います。

f:id:am1tanaka:20181110124628p:plain

こちら「この段階ではまだエラーが発生します。設定を続けます。」という文の続きを設定します。以下、簡単な手順です。

  • Editメニューから、Project Settings > Playerを選択します

以下を、利用したいプラットフォームごとに実施します

  • Other Settingsを開きます
  • Scripting Runtime VersionExperimental、あるいは.NET 4.6 Equivalent、あるいは.NET 4.x Equivalentにします
  • Restartするかのウィンドウが表示されたら、Restartします

Unityが一度閉じて再起動するのを待ってください。これでエラーが出なくなります。

環境(Environment)を作る

ML-Agentsの環境用のシーンとして、エージェントが乗っかる床となるPlane、エージェントがゴールや目標として探索するCube、エージェントを表すSphereを作成します。

床のPlaneを作る

  • HierarchyウィンドウのCreateをクリックして、3D Object > Planeを選択します
  • 作成したPlaneの名前をFloorにします
  • 作成したFloorをクリックして選択します
  • InspectorウィンドウのTransform欄を以下の通り設定します
    • Position = 0, 0, 0
    • Rotation = 0, 0, 0
    • Scale = 1, 1, 1
  • InspectorウィンドウのMesh Renderer欄にあるMaterialsの左の三角をクリックして開いたら、Element 0の右の〇をクリックして、ウィンドウからFloorマテリアルを選択して割り当てます

f:id:am1tanaka:20181110123205p:plain

以上で床は完成です。

取るためのキューブを作る

  • HierarchyウィンドウのCreateをクリックして、3D Object > Cubeを選択します
  • 作成したCubeの名前をTargetにします
  • 作成したTargetをクリックして選択します
  • InspectorウィンドウのTransform欄を以下の通り設定します
    • Position = 3, 0.5, 3
    • Rotation = 0, 0, 0
    • Scale = 1, 1, 1
  • InspectorウィンドウのMesh Renderer欄にあるMaterialsの左の三角をクリックして開いたら、Element 0の右の〇をクリックして、ウィンドウからBlockマテリアルを選択して割り当てます

エージェントとするSphereを作る

  • HierarchyウィンドウのCreateをクリックして、3D Object > Sphereを選択します
  • 作成したSphereの名前をRollerAgentにします
  • 作成したRollerAgentをクリックして選択します
  • InspectorウィンドウのTransform欄を以下の通り設定します
    • Position = 0, 0.5, 0
    • Rotation = 0, 0, 0
    • Scale = 1, 1, 1
  • InspectorウィンドウのMesh Renderer欄にあるMaterialsの左の三角をクリックして開いたら、Element 0の右の〇をクリックして、ウィンドウからChekerGoalマテリアルを選択して割り当てます
  • Add Componentをクリックします
  • PhysicsからRigidbodyを選択してアタッチします

後程、このオブジェクトにエージェントサブクラスをアタッチします。

アカデミーとブレインを持たせる空のゲームオブジェクトを作る

  • HierarchyウィンドウのCreateをクリックして、Create Emptyを選択します
  • 作成したGameObjectの名前をAcademyにします
  • 作成したAcademyオブジェクトを右クリックして、Create Emptyを選択して、子供に空のゲームオブジェクトを作成します
  • 作成した子供のGameObjectの名前をRollerBallBrainにします(元記事では単にBrainでしたが、後の設定のため変更しました)

f:id:am1tanaka:20181113214728p:plain

以上でシーンが完成です。Main Cameraの座標や角度を調整して、Gameウィンドウで見やすくなるようにしてください。

f:id:am1tanaka:20181110125921p:plain

アカデミーの実装

アカデミーオブジェクトは、シーン内のML-Agentsを動かして、観測-意思決定-行動というシミュレーションループを制御します。ML-Agentのシーンごとに1つのアカデミーのインスタンスが必要です。アカデミークラスのベースクラスはabstractなので、メソッドを実装する必要がなくてもサブクラスを作成する必要があります。

まずは、先に作成したアカデミーオブジェクトに新しいスクリプトを追加します。

  • HierarchyウィンドウからAcademyオブジェクトをクリックして選択します
  • Inspectorウィンドウで、Add Componentをクリックします
  • New Scriptを選択して、RollerAcademyという名前で作成します
  • 作成したRollerAcademyスクリプトをダブルクリックして、エディターで開きます
  • MLAgentsの機能を使うために、using MLAgents;を定義します
  • クラスの継承元をMonoBehaviourからAcademyに変更します
  • Start()Update()メソッドを削除します

基本的な機能はベースクラスのAcademyが持っているので、実装は不要です。以下の通りにしたら、上書き保存をしてUnityに切り替えます。

using MLAgents;

public class RollerAcademy : Academy { }

デフォルトの設定のまま使います。Inspectorウィンドウで変更する項目はありません。

f:id:am1tanaka:20181111010139p:plain

ブレイン(Brain)を作る

ブレインオブジェクトは、行動を決める処理を担当します。エージェントは自らが観測した情報をブレインに送ります。ブレインは送られてきた観測情報を元に行動を決定してエージェントに伝えます。エージェントは、ブレインから受け取ったデータに従って行動します。Brain Type設定で、ブレインがどのように意思決定を行うかを選択することができます。アカデミーやエージェントクラスと違い、ブレインは既存のスクリプトをそのまま利用します。

  • Hierarchyウィンドウから、Academyの子供のRollerBallBrainオブジェクトを選択します
  • Inspectorウィンドウで、Add Componentをクリックします
  • Scripts > MLAgents > Brainを選択して、コンポーネントをアタッチします

f:id:am1tanaka:20181113214956p:plain

設定は改めて行います。現時点ではBrain TypePlayerのままにしておきます。

エージェントの実装

エージェントを作成します。

  • Hierarchyウィンドウから、RollerAgentオブジェクトをクリックして選択します
  • Inspectorウィンドウで、Add Componentをクリックします
  • New Scriptを選択して、RollerAgentという名前で作成します
  • 作成したRollerAgentスクリプトをダブルクリックして、エディターで開きます
  • Academyの時と同じように、usingを設定して、ベースクラスをMonoBehaviourからAgentに変更します

以下のようなコードになります。

using MLAgents;

public class RollerAgent : Agent { }

上書き保存をして、Unityに戻ります。

ここまでの手順は、あらゆるUnityのプロジェクトにML-Agentsを加える基本的な手順です。次に、強化学習を使ってSphereのエージェントがキューブを取るように動かすためのロジックを加えていきます。

今回のシンプルなシナリオでは、アカデミーオブジェクトに環境の変更は実装しませんが、シミュレーション前やシミュレーション中に、床の大きさを変えたり、エージェントやその他のオブジェクトを追加したり削除したりしたい場合は、アカデミーにメソッドを自由に追加して実装することができます。ここでは、エージェントが成功したり失敗した時に、エージェントや目標物をリセットすることだけを行います。

エージェントの初期化とリセット

RollerBallのエージェントは、Targetに触れたら自分自身でそのことを記録して、Targetをランダムな座標に配置し直すリセット関数を呼び出します。また、RollerBallが地面から落下したら、リセット関数によって地面に戻します。

Targetを追うためには、エージェントはTargetTransformを知っている必要があります。そのために、RollerAgentクラスにpublicTransformを定義します。これにより、Inspectorウィンドウで、ターゲットを設定できるようになります。エージェントの速度をリセットしたり、エージェントを動かすための力を与えるために、Rigidbodyコンポーネントの参照が必要です。Rigidbodyは、Unityで物理シミュレーションをするための基本的なエレメントです。スクリプトのゲームオブジェクトにアタッチされているRigidbodyへの参照は、Start()メソッド内で、GameObject.GetComponent<T>()を使って取得します。

RollerAgent.csは以下のようになります。

using System.Collections.Generic;
using UnityEngine;
using MLAgents;

public class RollerAgent : Agent {
    Rigidbody rBody;
    private void Start()
    {
        rBody = GetComponent<Rigidbody>();
    }

    public Transform Target;
    private float previousDistance = float.MaxValue;
    public override void AgentReset()
    {
        // 前回の距離をリセット
        previousDistance = float.MaxValue;

        if (this.transform.position.y < -1.0)
        {
            // エージェントが落ちた
            this.transform.position = Vector3.zero;
            this.rBody.angularVelocity = Vector3.zero;
            this.rBody.velocity = Vector3.zero;
        }
        else
        {
            // ターゲットを新しい場所へ移動させる
            Target.position = new Vector3(
                Random.value*8-4,
                0.5f,
                Random.value*8-4
            );
        }
    }
}

次は、Agent.CollectObservations()関数(情報を収集する関数)を実装します。

環境を観測する(Observing the Environment)

エージェントは、収集した環境の情報をブレインに送ります。ブレインは送られてきた情報を使って行動を決めます。エージェントをトレーニングする時、あるいは、トレーニングされたモデルを利用する時に、観測したデータをfeature vector(観測結果を表すベクトルデータ)としてニューラルネットワークに与えます。エージェントの学習を成功させるためには、正しい情報を与えなくてはなりません。どの情報を与えるのが正しいかを決めるよい指標は、問題を解決するために何が必要かを分析して考えることです。

今回の場合、エージェントは以下の情報を集めます。

  • ターゲットの座標。一般的に、絶対的な座標よりも、エージェントから見た相対的な座標を使う方がよいでしょう。今回は、Plane上を移動するだけでY座標は変化しないので、X-Z座標のみ利用します
// エージェントからターゲットへの相対座標を計算する(Calculate relative position)
Vector3 relativePosition = Target.position - this.transform.position;

// 相対座標(Relative position)
AddVectorObs(relativePosition.x / 5);
AddVectorObs(relativePosition.z / 5);
  • 床の四隅からのエージェントの距離。エージェントを床に留まらせるための情報です
// 床の角までの距離 (Distance to edges of platform)
AddVectorObs((this.transform.position.x + 5) / 5);
AddVectorObs((this.transform.position.x - 5) / 5);
AddVectorObs((this.transform.position.z + 5) / 5);
AddVectorObs((this.transform.position.z - 5) / 5);
  • エージェントの速度。ターゲットを通り過ぎたり、床から転落しないように速度を学習させるための情報です
// Agent velocity
AddVectorObs(rBody.velocity.x / 5);
AddVectorObs(rBody.velocity.z / 5);

全ての値は5で割って、正規化をしています。これは、ニューラルネットワークが受け取る値が-11の範囲だからです。何故5かというと、今回の床のサイズが10なので、-55でどこにいるかが表されるからです。

(補足) この処理では1を越えることがあるため、正規化としては不十分です。後程、正規化フラグを設定します。 (補足ここまで)

観測した状態(state observation)の数は8つです。それをAddVectorObs()関数でブレインに渡します。

以上をまとめると以下のようになります。RollerAgent.csに加えてください。

public override void CollectObservations()
{
    // 相対座標を計算(Calculate relative position)
    Vector3 relativePosition = Target.position - this.transform.position;

    // 相対座標を正規化して設定(Relative position)
    AddVectorObs(relativePosition.x/5);
    AddVectorObs(relativePosition.z/5);

    // 床の隅からの距離を正規化した値を設定(Distance to edges of platform)
    AddVectorObs((this.transform.position.x + 5)/5);
    AddVectorObs((this.transform.position.x - 5)/5);
    AddVectorObs((this.transform.position.z + 5)/5);
    AddVectorObs((this.transform.position.z - 5)/5);

    // エージェントの速度(Agent velocity)
    AddVectorObs(rBody.velocity.x/5);
    AddVectorObs(rBody.velocity.z/5);
}

エージェントコードの最後は、Agent.AgentAction()関数です。ブレインが決定した行動を受け取ります。

Actions

ブレインが決めた行動は、アクションの配列としてAgentAction()関数に渡されます。この配列の要素数は、エージェントのブレインの設定であるVector Action Space TypeVector Action Space Sizeによって決まります。RollerAgentでは、Continuous Vector Action Space(連続的な行動空間)で、ブレインから2つのcontinuous control signals(連続的な制御信号)を必要とします。よって、Brain Vector Action Sizeには2を設定します。最初のエレメントであるaction[0]X軸方向に加える力で、2つ目のaction[1]Z軸方向に加える力とします(もし、Y方向の移動もあった場合は、Vector Action Size3になります)。ニューラルネットワークから返される値は、-11の間の値になります。ブレインは、これらの値がどのようなものなのかは把握しません。トレーニングは、観測した入力に対して返す値を調整した結果、どのようなリワード(報酬)が得られるかということだけに注目して行われます。

RollerAgentaction[]配列の値をRigidbodyコンポーネントであるrBodyAddForce()で反映させます。

Vector3 controlSignal = Vector3.zero;
controlSignal.x = action[0];
controlSignal.z = action[1];
rBody.AddForce(controlSignal * speed);

実際のコードは後述します。

Rewards(リワード=報酬)

強化学習ではリワードの設定が必要です。リワードはAgentAction()関数内で割り当てます。学習アルゴリズムは、シミュレーションのステップごとにエージェントにリワードを与えて、エージェントにとって最適な行動が何かを学習するのに利用します。エージェントが目的のタスクを達成したら(今回ならTargetオブジェクトに辿り着いたら)報酬を与えて、望まない行動をしたら(今回なら床から転落したら)報酬を取り上げます。

レーニングを短縮するために、エージェントが最終的なタスクを達成するのを助けるための補助的なリワード(sub-rewards)を設定する方法があります。例えば、Targetとの距離が前回より近づていたらリワードを少し与えつつ、毎ステップごとに少しリワードを減らすような設定を加えることで、エージェントはTargetに近づくことが有利だと学習でき、Targetに到着しやすくなります。

RollerAgentは、Targetに辿り着いたかを検出するために距離を計算します。算出した距離が到達したと判断する値以下になっていたら、エージェントに1.0のリワードを与えて、完了したことを報告するためにDone()を呼び出します。

float distanceToTarget = Vector3.Distance(this.transform.position,
                                          Target.position);
// Reached target
if (distanceToTarget < 1.42f)
{
    AddReward(1.0f);
    Done();
}

メモ: Done()を呼び出してエージェントが完了したことを知らせると、リセットするまで動作が停止します。Agent.ResetOnDoneプロパティーをインスペクター上でTrueに設定しておくことで、完了したらすぐに自動的にエージェントをリセットすることもできますし、アカデミーがMaxStepに到達して、環境をリセットするのを待つこともできます。今回はResetOnDoneプロパティーTrueに設定してリセットします。MaxStep0のままにして、アカデミーが環境をリセットしないようにします。

(補足)

Targetへの距離が縮まったら少しリワードを与えて、学習効果を高めるためのコードです。元記事には変数は定義されていたものの、リワードを与えるコードがなかったので補足します。

        // 近づいたサブリワード
        if (distanceToTarget < previousDistance)
        {
            previousDistance = distanceToTarget;
            AddReward(0.03f);
        }

(補足ここまで)

以下は、ステップごとにリワードを少し減らすことで、タスクをより手早く完了できるようにするための工夫です。

// タイムペナルティ
AddReward(-0.05f);

最後に、プラットフォームから転落した時にリワードを大きく減らす処理と、Doneを呼び出して次のステップで自分自身をリセットさせる処理です。

// プラットフォームからの転落
if (this.transform.position.y < -1.0)
{
    AddReward(-1.0f);
    Done();
}

AgentAction()

行動とリワードの考え方は以上の通りです。AgentAction()関数の完成形は以下のようになります。RollerAgent.csに加えてください。

    public float speed = 10;

    public override void AgentAction(float[] vectorAction, string textAction)
    {
        // Targetとの距離
        float distanceToTarget = Vector3.Distance(
            this.transform.position,
            Target.position);

        // Targetに接触したか
        if (distanceToTarget < 1.42f)
        {
            AddReward(1);
            Done();
        }

        // 近づいたサブリワード
        if (distanceToTarget < previousDistance)
        {
            previousDistance = distanceToTarget;
            AddReward(0.03f);
        }

        // 近づかなかったペナルティ
        AddReward(-0.05f);

        // プラットフォームから転落
        if (this.transform.position.y < -1.0)
        {
            AddReward(-1);
            Done();
        }

        // 動作の設定。sizeが2なのでvectorActionは2つ
        Vector3 controlSignal = Vector3.zero;
        controlSignal.x = vectorAction[0];
        controlSignal.z = vectorAction[1];
        rBody.AddForce(controlSignal * speed);
    }

仕上げのエディター設定

全てのゲームオブジェクトとML-Agentコンポーネントの設定が完了したので、データを接続していきます。

  • HierarchyウィンドウのAcademyオブジェクトの左の三角をクリックして開いて、子供のRollerBallBrainオブジェクトを表示します
  • HierarchyウィンドウのRollerAgentゲームオブジェクトをクリックして選択します
  • Hierarchyウィンドウから、RollerBallBrainオブジェクトをドラッグして、InspectorウィンドウのBrainフィールドにドロップします
  • Inspectorウィンドウで、Dicision Frequency1から5に変更します
    • エージェントは、この設定のステップ数ごとにブレインへ観測データを送信して意思決定を要求します。行動(Action)は1ステップごとに行われます(バランスボールのサンプルもこの値は5でした)

また、HierarchyウィンドウからTargetオブジェクトをドラッグして、RollerAgentTargetフィールドにドロップします。

f:id:am1tanaka:20181113220102p:plain

最後に、RollerBallBrainゲームオブジェクトをクリックして選択したら、Inspectorウィンドウで以下を設定します。

  • Vector Observation Space Size = 8
    • ブレインに送る観測データベクトルの次元数(要素数)
  • Stacked Vectors = 1
  • Visual Observation = 0
    • 観測データとして利用するグラフィックの数。今回は未使用なので0
  • Vector Action Space Type = Continuous
    • 行動結果を表すベクトルを、整数(Discrete)で返すか、小数(Continuous)で返すか
  • Vector Action Space Size = 2
    • 行動結果として返すベクトルの次元数(要素数)
  • Action Descriptions = 2
    • Vector Actionの各要素の説明
  • Brain Type = Player
    • 意思決定の種類
      • Player = 人が操作する
      • Heuristic = 行動決定のためのアルゴリズムDecitionインターフェースを実装したクラスを作成して、自前で実装する
      • External = Pythonと通信して、TensorFlowなどに意思決定させる
      • Internal = TensorFlowを使って学習済みのモデルで意思決定する

f:id:am1tanaka:20181113134844p:plain

以上で、トレーニングを開始する前に、環境を試す準備ができました。

環境を試す

実際にトレーニングを開始する前に、手動で環境を試すのはよい手順です。Brain TypePlayerのままにしてあるので、キー入力でエージェントを動かすことができます。前後左右の4方向に4つのキーを割り当てます。

  • HierarchyウィンドウでRollerBallBrainオブジェクトをクリックして選びます
  • InspectorウィンドウのKey Continuous Player Actions欄を開きます(この設定は、Brain TypePlayerの時のみ表示されます)
  • Size4にします
  • 以下の通り、設定します
Element Key Index Value
Element 0 D 0 1
Element 1 A 0 -1
Element 2 W 1 1
Element 3 S 1 -1

Indexの値は、AgentAction()関数に渡す配列のインデックスを表します。Valueの値は、Keyが押された時にvectorAction[Index]に渡される値です。

Playすると、[W][A][S][D]キーで、Agentを操作することができます。エラーが発生しないことと、Targetに触ったり床から落ちたらエージェントがリセットされることを確認してください。ML-Agents SDKには、ゲームウィンドウにエージェントの状態を簡単に表示させることができるMonitorクラスが用意されています。より詳細な状態が見たい場合は利用するとよいでしょう。

(ここではやりませんが)作成した環境とPython APIが想定通りに動くかどうかを、Jupyter notebookを使って試しておくことができます。確認のためには、notebooks/getting-started.ipynbを利用します。ノートブックのenv_nameに、この環境をビルドして生成した実行ファイル名を設定します。

学習の準備が整ったら、ブレインのBrain TypePlayerからExternalに変更します。ここから先の手順は、Training ML-Agentsと同じです。

シーンレイアウトを振り返る

最後に、作成した環境でエージェントがどのように動作するかを振り返っておきましょう。

今回のトレーニングをUnity ML-Agentsで実行するために、3つのゲームオブジェクトを作成しました。

  • Academy
  • Brain
  • Agents

約束事です。

  • 1つのシーンには、Academyゲームオブジェクトを1つだけ配置できます
  • Brainは1つのシーンに複数配置できますが、それらはすべてAcademyゲームオブジェクトの子にする必要があります

以下、シーンのHierarchyの見え方の例です。

example hierarchy

(https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Learning-Environment-Create-New.md より)


元記事には学習手順と学習結果を利用する手順が省略されているので、以下に補足します。

学習手順

前提

Unity ML-AgentsをWindows10で使う 2018年11月版 - tanaka's Programming Memoが完了していることを前提にします。

準備

まずはブレインを外部に変更します。

  • HierarchyウィンドウでRollerBallBrainをクリックして選択します
  • Inspectorウィンドウで、Brain TypePlayerからExternalに変更します

学習の設定をします。デフォルトの5万ステップだと、あまり学習の成果が見られません。3D Ballのサンプルは12個のエージェントを同時にトレーニングしていたので2万ステップ程度で効果が表れましたが、こちらは同時に1つのエージェントしかトレーニングしないので、その10倍の20万ステップは最低でも学習させたいところです。余裕をもって、50万ステップに設定します。

また、ニューラルネットワーク-11の範囲のデータを受け取る仕様になっています。ところが、今回のサンプルでは状況に応じてこの範囲を超えてしまうことがあり、学習の妨げになります。そこで、正規化フラグを有効にすることで範囲外の数値が来たら-11の範囲に収めるように設定します。

  • ML-Agentsのリポジトリーからダウンロードしたml-agents-masterフォルダー内のconifg/trainer_config.yamlを何らかのテキストエディターで開きます
  • 22行目のcuriosity_enc_size: 128という行と、24行目のBananaBrain:の間に、以下を追加します
24:
RollerBallBrain:
    normalize: true
    max_steps: 5.0e5
  • 上書き保存します

これで、UnityのRollerBallBrainという名前のブレインをトレーニングする場合、以下が有効になります(このためにBrainの名前をRollerBallBrainに変更しました)。

  • 入力データを正規化(normalize)する
  • ステップ数を50万回(5.0e5は、5x10^5の意味)

学習させる

Pythonを使って学習を開始します。

  • Windowsの検索バーにanaと入力して、Anaconda Promptを起動します
  • cdコマンドで、ml-agents-masterのフォルダーに移動します。作成したUnityProjectではなく、ML-AgentsのGitHubリポジトリーをダウンロードして展開したフォルダーの方です
  • 以下を入力します
mlagents-learn config/trainer_config.yaml --run-id=RollerBall --train
  • Unityのアスキーアートが表示されて、INFO:mlagents.envs:Start training by pressing the Play button in the Unity Editor.という表示が出たら、Unityに切り替えて、Playすれば、学習が始まります。

f:id:am1tanaka:20181113224106p:plain

ログの見方

コンソール画面に謎の文字が表示されますが、ここから学習がどのように進んでいるかを読み取ることができます。このログは、設定した回数ごとの集計になっています。デフォルトでは千回に1回、集計されて以下のように表示されます。

f:id:am1tanaka:20181114172135p:plain

Step以降を見ていきます。上の例ではStep: 360000.になっています。359001回~360000回のステップのトレーニングが完了した時の情報ということです。

大切なのは、それに続くMean RewardStd of Rewardの2つの数値です。

Mean Reward

リワードの平均値です。今回の例なら、36万ステップまで千ステップの平均リワードが1.149だったということです。この値は、最初は-15ぐらいでしたので、かなりの学習成果が上がっているということになります。

この値は、上がったり下がったりを繰り返しつつ、徐々に大きくなっていくはずです。5万~10万ステップぐらいまでは変化しているように見えない場合もありますので、慌てずじっくりと眺めてください。

20万ステップしても変化がないようなら、学習効果が得られない設定になっている可能性があります。プログラムや考え方を見直してみてください。

Std of Reward

リワードの標準偏差です。標準偏差とは、データのバラつきを表す値です。全てのリワードが同じ値ならこの値は0になり、バラけているほど大きな値になります。

AIがめちゃくちゃに動いている最初の方はリワードがバラバラになるので、大きな値になりがちです。頭が良くなるにつれて行動が似てくるのでリワードのバラつきが減り、この値は小さくなっていくのが一般的です。

いきなりこの値が小さい場合、めちゃくちゃに動いてもリワードが変わらないということなので、エージェントの設定を失敗している可能性があります。

条件によっては学習が進むほどこの値が大きくなることもあります。この値が小さくなれば十分に学習できた、と単純には言えないということです。バラつきを表す値ということを考えて、値を読み取ってください。

学習は何ステップがよいか

条件によって結構変化しますが、とりあえず2Dのものなら50万ステップ程度が目安のように感じています。学習をさせすぎると成績が悪化する過学習という現象が起きる場合があります。学習後にMean Rewardを確認して、スコアが悪化していくようなら、丁度良さそうなステップ数を確認して、その回数で学習し直すとよいでしょう。

(ここから、2019/1/19追記)

0.6.0aのドキュメントを参照したところ、trainer_config.yamlbatch_size10buffer_sizeを`100*にすることで、2万ステップ程度でも十分に学習効果が上がることが分かりました。

batch_size: 10
buffer_size: 100

今回のようなシンプルな学習の場合、バッチやバッファサイズを減らすことで、学習を早めることができました。

また、座標や速度などは、Yを使わなかったとしても、ベクトルでそのままブレインに渡した方が学習効果が高いようでした。

(追記ここまで)

まだスコアが伸びそうな場合は、trainer_config.yamlmax_stepsの値を増やして上書き保存をしてから、Anaconda Promptで以下を実行すれば、先ほどのデータの続きから学習を再開できます(すでに学習ステップ数がmax_stepsに達していたら、学習を再開させてもすぐに終わってしまいます。学習回数を増やす場合は、trainer_config.yamlmax_stepsも増やしてください)。

mlagents-learn config/trainer_config.yaml --run-id=RollerBall --train --load

今回のプロジェクトだと、30万ステップぐらいでほぼ安定して、50万ステップまで少しずつ精度が上がっていった(Std of Rewardが小さくなっていった)感じでした。

考察(おまけ)

前よりターゲットに近づいた時に与えるリワードを0.1にして、毎回の減点より大きい数値にして試すと、100万ステップ学習させてもスコアが伸び続けました。面白いのが、標準偏差も大きくなっていったことです。近寄るごとにリワードがもらえる設定だと、Targetをすぐに取るよりも、ゆっくり近づいて取った方がリワードが多く得られるようでした。そうなると、どこにTargetが出るかによって点数に差が出るようになります。リワードが出現位置の乱数に影響を受けるようになるため、標準偏差の値が大きくなっていたものと思われます。

なるべく早くTargetを取るAIにしたかったので、リワードを見直して今回のパラメーターにしました。

再生手順

最後に、学習したモデルデータをUnityのプロジェクトに組み込んで再生してみましょう。

TensorFlowSharpプラグインをプロジェクトにインポートします。ダウンロードしていない場合は、こちらからダウンロードして、インポートしてください。PC上で試すだけなら、AndroidiOSプラグインは不要なので、チェックを外した方が容量やインポート時間を節約できます。

f:id:am1tanaka:20181114001257p:plain

インポートが完了したら、TensorFlowを有効にするための設定をします。

  • Editメニューから、Project Settings > Playerを選択します

以下を、利用したいプラットフォームごとに実施します

  • Other Settingsを開きます
  • Scripting Defined Symbols欄に、ENABLE_TENSORFLOWを入力して、[Enter]キーを押します
  • [Ctrl]+[S]キーを押して保存します

以上で、Brain ModeInternalが利用できるようになります。

学習したモデルデータをプロジェクトに読み込みます。モデルデータはml-agents-masgterフォルダーの中に、models > run-idで設定した名前のフォルダーの中に入っています。

  • エクスプローラーなどでml-agents-masterフォルダー内の、models/RollerBallフォルダーを開きます
  • .bytesファイルをドラッグして、UnityのProjectウィンドウにドロップします

f:id:am1tanaka:20181114010242p:plain

最後に、ブレインの設定を切り替えて、モデルデータを設定します。

  • Hierarchyウィンドウで、RollerBallBrainをクリックして選択します
  • Inspectorウィンドウで、Brain TypeInternalに変更します
  • Projectウィンドウからモデルデータをドラッグして、InspectorウィンドウのGraph Model欄にドロップします

f:id:am1tanaka:20181114010533p:plain

以上で完了です。Playをすれば、学習させたAIがRollerAgentを操作してTargetを取っていくのが確認できます。

f:id:am1tanaka:20181114011102g:plain

まとめ

新規プロジェクトから学習用の環境を構築して、学習、結果の利用ができました。入力や出力を当てはめられるものであれば、自前のプロジェクトに組み込めそうです。

学習させる上で、以下の知見が得られました。

  • ベクトルの観測データは、使っていない要素があってもベクトルのままブレインに渡した方がよい(2019/1/19追記)
  • 学習設定のbatch_sizebuffer_sizeを小さい値にして学習を試してみると、早く学習が完了する可能性がある(2019/1/19追記)
  • 学習は50万ステップ程度は見込んでおく
  • 同時に複数のエージェントを学習できるように、3DBallのように親オブジェクトからの相対座標で動く環境を作るとよい(この記事の作り方だと、同時に学習できないのでステップ数が多くなるので)
  • サブリワードの設定は、学習の回数を減らしたりイメージに近い行動をさせるのに大いに有効
  • 正規化も重要。コード側で正規化をしていない場合は、設定のnormalizeフラグを利用する

一年前はこの辺りで他のことを初めてしまってとん挫したので、今回は自分で作った環境でAIを鍛えたいです。

参考URL