はじめに
こんにちは。陰キャ大学生です。
前置きはさておき、Pythonを使って機械学習していると、「物理エンジン使ってやってみたいなぁ」なんて思う時があります。ありません
そこで今回は、Unityのために作られた機械学習ライブラリ__ML Agents__ を使って物理エンジンで機械学習してみました。
実はすでにQiita上には多くのMLAgentsの記事が上がっています。
【ML-Agents】UnityとPythonのTensorFlowをつかって機械学習させてみた(v0.11β対応)
しかし、このMLAgents、__バージョンアップが頻繁なうえに、バージョン変わると関数やメソッド名も変わる__というなんともQiitaライター泣かせな仕様となっています。
そこで今回は、現在(2021年2月25日)最新であるRelease13 について記事を書きます。
おそらく数か月後には古くて使い物にならない記事になると思いますが、今この瞬間にMLAgentsを使いたいんだ!!という人に届いてほしいと思います。
#MLAgentsについて
こちらの記事が大変わかりやすく参考になりますので、ぜひご覧ください
環境
MLAgentsの推奨環境
- Unity (2018.4以降)
- Python (3.6.1以降)
今回私が使用した環境
- Unity (2019.4.15f1)
- Python (3.7.9) Anaconda
推奨環境を満たしていれば大丈夫だと思いますが、Unityはバージョン等によりUIが変わる可能性があるのでご容赦ください。
前準備
MLAgentsのダウンロード
こちらのサイトから、__Release13__をダウンロードしてください。
ダウンロードされたZIPファイルを任意の場所に展開してください。
PythonとUnityのダウンロード&インストール
参考記事がわかりやすくまとめてくださったので引用します。参照されてください。
【ML-Agents】UnityとPythonのTensorFlowをつかって機械学習させてみた(v0.11β対応)
準備
Pythonの環境構築
Pytorchのインストール
ターミナルで以下を実行します
pip install torch~=1.7.1 -f https://download.pytorch.org/whl/torch_stable.html
関連ライブラリのインストール
__展開したディレクトリに移動__して、以下を実行
pip install -e ./ml-agents-envs
pip install -e ./ml-agents
実際、pip install mlagents
でもインストールできますが、この際は常に最新版がインストールされますので、バージョンの整合性をとるためにもダウンロードしたSetup.pyから参照することをお勧めします。
__0.24.0__がインストールされたことを確認してください
Unitでプロジェクト作成
上のメニューから、Window>Package Manager
の順に進んで、左上の+マークから、Add Package from disk...
を選択して、<展開したフォルダ>/com.unity.ml-agents/package.json
を選択します。
インポートが始まり、In Project
にML Agents 1.8.0
があれば大丈夫です。
今回は、箱をボールが追いかけるAIを作成します。(詳しくは記事の最後を見てね)
Unityの詳しい説明等はここでは省略します。
ステージの作成
GameObject>3D Object>Plane
を選択し、Stageに改名、Positionを(0, 0, 0)Scaleを(1, 1, 1)に設定
箱の設定
GameObject>3D Object>Cube
を選択し、Targetに改名、Positionを(3, 0.5, 3)Scaleを(1, 1, 1)に設定
ボールの設定
GameObject>3D Object>Sphere
を選択し、RollerAgentに改名、Positionを(0, 0.5, 0)Scaleを(1, 1, 1)に設定
Inspector下部のAdd Component
から、Physics>Rigidbody
を追加 👈これめちゃ大事です!!!
後で複製できるように、グループ化しておきます。
GameObject>Create Empty
を選択し、__Stage、Target、RollerAgentをドラッグしてグループ化します__名前は何でもいいです。
ボールのスクリプト作成
UnityのProject内にScripts
フォルダを作り、その中にRollerAgent.cs
を作成します
中身をお好きなエディタで以下のように書き換えてください。
解説は適宜コメントで挿入してあるので見てみてくださいね。
// 使用ライブラリのインポート
using System.Collections.Generic;
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;
// Agentクラスを継承し、必要なところだけ書き換えていきます
public class RollerAgent:Agent
{
Rigidbody rBody;
// スタートしたときに呼び出される関数
void Start () {
// ボールの物体を変数に格納
rBody = GetComponent<Rigidbody>();
}
// Targetをグローバルに宣言
public Transform Target;
// エピソード(学習のステップ)が始まった時に呼び出される関数
public override void OnEpisodeBegin()
{
// ボールのY座標が0=落下したとき
if (this.transform.localPosition.y < 0)
{
// 初期化
this.rBody.angularVelocity = Vector3.zero;
this.rBody.velocity = Vector3.zero;
this.transform.localPosition = new Vector3( 0, 0.5f, 0);
}
// ターゲットをランダムな位置に
Target.localPosition = new Vector3(Random.value * 8 - 4,
0.5f,
Random.value * 8 - 4);
}
// 観測データ(この場合で言うと学習に必要な数値)の取得
public override void CollectObservations(VectorSensor sensor)
{
// ボールと箱の座標(x, y, z) x 2
sensor.AddObservation(Target.localPosition);
sensor.AddObservation(this.transform.localPosition);
// ボールのスピード(x, z)
sensor.AddObservation(rBody.velocity.x);
sensor.AddObservation(rBody.velocity.z);
// トータルで入力次元は8次元になる
}
// 力を加えるときの乗数
public float forceMultiplier = 10;
// アクションが起きたときに呼び出される関数
public override void OnActionReceived(ActionBuffers actionBuffers)
{
// XとZ軸の入力に合わせてボールに力を加える
Vector3 controlSignal = Vector3.zero;
controlSignal.x = actionBuffers.ContinuousActions[0];
controlSignal.z = actionBuffers.ContinuousActions[1];
rBody.AddForce(controlSignal * forceMultiplier);
// 箱とボールの距離
float distanceToTarget = Vector3.Distance(this.transform.localPosition, Target.localPosition);
// 箱にボールが到達したとき
if (distanceToTarget < 1.42f)
{
// 報酬を1に設定
SetReward(1.0f);
// エピソードを終了
EndEpisode();
}
// ボールが落下したとき
else if (this.transform.localPosition.y < 0)
{
// エピソードを終了
EndEpisode();
}
}
// 手で動かす際の設定
public override void Heuristic(in ActionBuffers actionsOut)
{
var continuousActionsOut = actionsOut.ContinuousActions;
continuousActionsOut[0] = Input.GetAxis("Horizontal");
continuousActionsOut[1] = Input.GetAxis("Vertical");
}
}
ファイルのアタッチ
作成したスクリプトをRollerAgent
にドラッグします。
RollerAgent (Script)
のTarget
に、Target(ゲームオブジェクト)
をドラッグ
Add Component
から、ML Agents>Behavior Parameters
を追加
Vector Observation>Space Size
を 8
に変更
Actions>Continuous Actions
を 2
に変更
Behavior name
を RollerBall
に変更
Add Component
から、ML Agents>Decision Requester
を追加
Decision Period
を 10
に変更
テスト
Behavior Parameters
のBehavior Type
をHeuristic Only
に変更
中央上部のプレイボタンを押して、矢印キーで遊べるか確認してみてください
学習
__<展開したディレクトリ>/config/rollerball.yaml
を作成__して、以下を書き込みます
behaviors:
RollerBall:
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
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: 1000
Behavior Parameters
のBehavior Type
をDefault
に変更
__展開したディレクトリに移動__して、以下を実行
mlagents-learn config/rollerball.yaml --run-id=RollerBall
Listening on port 5004. Start training by pressing the Play button in the Unity Editor.
この文言がでたら、Unityに戻って、プレイボタンを押すと、学習が始まります。
最初のうちはボールがオロオロ動くのがなかなかに可愛いです笑
学習並列化
物理エンジンのいいところは、モデルを複製して同時に学習を進められるところです。
先ほどグループ化したものをプレハブ化し、複製してみましょう。(特にコード等変える必要はありません)
ログはこのようになります。Mean Reward
が1に近づけばうまく学習が行われています。
終了したいときは、CTRL+C
で止められます。その時のモデルデータを自動的に保存してくれます。
また、バックボーンはTensorFlowなので、別ターミナルでコマンドを実行することで、TensorBoardも確認することができます。
tensorboard --logdir results --port 6006
学習モデルでテスト
<展開したディレクトリ>/results/RollerBall/RollerBall.onnx
を、Unityプロジェクト内のAssets
にドロップしたのちに、RollerAgentのInspector
内ののBehavior Parameters>Model
にドロップ。
プレイボタンを押すと、学習されたモデルでボールが動く様子を見ることができます。
床から落ちることなく箱を一生懸命追っている姿が確認できますね。可愛い。
最後に
たびたびバージョンの変わってしまうMLAgentsですが、使い方によっては機械学習の世界が広がると思います。
僕も早くコマンドラインで学習できるようにならないかなぁ