今回はUnityと機械学習を組み合わせて簡単なシミュレーションを行なっていきたいと思います。世間ではAIとか人工知能とか呼んだりもしますが正直誰でも作れます。AI搭載って商品に書いとけば儲かるっしょ〜って人も多いので気をつけてくださいね。
先に言っておきますがレベル感で言うと駆け出しエンジニア向けくらいの記事になりますので内容が技術チックになります。ご了承くださいませ、、、
Unityとは?という方やインストールしていない方は以下の記事をご覧ください。


さて早速始めていきたいところですがまずは全体像を説明しておきます。今回行うことはUnityとML-Agentsというツールを使って以下の青い球が箱に向かって自発的に転がっていくような簡単な物理シミュレーションを組んでいくということです。

ML-Agentsはpythonで動くためpythonの環境づくりから必要になります。他の方の記事を見ているとpythonをインストールして仮想環境を立ち上げて、、、的なことをしていますがOS差分やpythonの環境差分で詰む人が多そうなのでもっと簡単な方法で行きます。
事前準備
今回の環境づくりで必要なことはDockerとVSCodeをインストールするだけです。VSCodeに関しては既に使ってる人も多いかと思います。またDockerと聞くと概念や操作が難しいというイメージもあるかと思いますがインストールするだけで大丈夫です。操作は全部自動で行われるようにしておくので気にしないでください。
VSCodeのインストール方法は山ほど記事が転がっているので他の方の記事をご覧ください。Dockerのインストールは以下で行なってください。

VSCodeとDockerのインストールが終わったらVSCodeで「Remote – Containers」をインストールします。

これで準備完了です。
ML-Agentsセットアップ
ML-Agentsのセットアップを行います。僕がpython実行環境とML-Agentsのパッケージを組み合わせたものを作ったのでGitHubからダウンロードしてください。「Code」→「Download ZIP」をクリック。ファイルサイズは116MBほどあるので1分くらいかかります。

zipファイルをクリックして開くと「unity_ml-agents-main」というフォルダができます。

VSCodeでunity_ml-agents-mainを開きます。VSCodeを右クリックし「新しいウィンドウ」をクリック。

「Open Folder」からunity_ml-agents-mainフォルダを開きます。

以下のように表示されていればOKです。

このままPython環境を立ち上げます。VSCodeの左下の「><」をクリック。

上部に出てくる「Reopen in Container」をクリック。

初回起動時は時間がかかります。1,2分で以下になれば立ち上げ完了です。

上部の「ターミナル」→「New Terminal」でターミナルを出します。

ターミナルでpythonのバージョンを確認します。以下を実行してください。pythonのバージョンが表示されれば問題ありません。
root@a50a488486b2:/workspaces/unity_ml-agents-main# python -V
Python 3.8.15
ターミナルで以下2つをコピペして実行します。
pip install -e /workspaces/unity_ml-agents-main/ml-agents-release_19/ml-agents-envs
pip install -e /workspaces/unity_ml-agents-main/ml-agents-release_19/ml-agents
Unityセットアップ
次にUnityをセットアップします。Unityを起動し「新しいプロジェクト」から「3D」で起動。
上部メニュー「Window」→ 「Package Manager」をクリック。

「+」→「Add package from disk」をクリック。

先ほどダウンロードした「unity_ml-agents-main/ml-agents-release_19/com.unity.ml-agents/package.json」を選択しOpen。

読み込みが完了したらPackage Managerは閉じてしまってOKです。
マテリアル作成
Projectウィンドウで「+」→「Material」をクリック。

すると以下のような画面になります。赤枠の作成されたマテリアルの名前が選択されている状態になっているので名前を「Gray」に変更します。このマテリアルは後々の床になります。

「Gray」マテリアルの色をその名の通り灰色に変えます。右側のInspectorウィンドウでMain Maps内の「Albedo」をクリックしRGBの3つとも168を入力します。


同じように「Brown」マテリアルRGB=(212,154,33)と「Blue」マテリアルRGB=(0,35,255)を作成します。Assetsが以下のようになっていればOKです。

オブジェクト作成
オブジェクトを作成します。まずは床です。Hierarchyウィンドウの「+」→「3D Object」→「Plane」をクリック。すると床が設置されます。

Inspectorウィンドウで名前を「Plane」から「Floor」に変更します。また、Position(0,0,0)、Rotation(0,0,0)、Scale(1,1,1)になっているか確認してください。デフォルトでこの数値になってない場合は変更してください。

次にFloorにマテリアルのGrayを適用させます。InspectorウィンドウMaterialsのElement 0内の白丸部分をクリックしGrayを選択します。

以下になればOKです。これで床が灰色になります。

同様に箱を作成します。Hierarchyウィンドウの「+」→「3D Object」→「Cube」。Inspectorウィンドウで名前を「Target」に変更し、数値をPosition(3,0.5,3)、Rotation(0,0,0)、Scale(1,1,1)にします。MaterialsのElement 0にBrownを指定。

次に球を作成します。Hierarchyウィンドウの「+」→「3D Object」→「Sphere」。Inspectorウィンドウで名前を「RollerAgent」に変更し、数値をPosition(0,0.5,0)、Rotation(0,0,0)、Scale(1,1,1)にします。MaterialsのElement 0にBlueを指定。

また球(RollerAgent)は転がしたいのでInspectorウィンドウ「Add Component」→「Rigidbody」で検索、選択し物理特性を付与します。


ML-Agentsコンポーネント追加
次に球(RollerAgent)にML-Agentsコンポーネントを追加します。球に行動を設定するようなイメージです。HierarchyウィンドウでRollerAgentを選択し、Inspectorウィンドウ「Add Component」→「Behavior Parameters」を追加。パラメータを以下に設定します。
Behavior Name = RollerBall
Vector Observation → Space Size = 6
Vector Observation → Stacked Vectors = 1
Actions → Continuous Actions = 2
Actions → Discrete Branches = 0

さらにInspectorウィンドウ「Add Component」→「New script」→Nameを「RollerAgent」→「Create and Add」で新しいスクリプトを作成します。


Assetsの「RollerAgent」を選択します。

Inspectorウィンドウで「Open」。

するとVSCodeでRollerAgent.csが開かれるので中身を全て削除し以下を貼り付けて保存(command+s)してください。
using System.Collections.Generic;
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Policies;
// RollerAgent
public class RollerAgent : Agent
{
public Transform target; // TargetのTransform
Rigidbody rBody; // RollerAgentのRigidBody
// 初期化時に呼ばれる
public override void Initialize()
{
// RollerAgentのRigidBodyの参照の取得
this.rBody = GetComponent<Rigidbody>();
}
// エピソード開始時に呼ばれる
public override void OnEpisodeBegin()
{
// RollerAgentが床から落下している時
if (this.transform.localPosition.y < 0)
{
// RollerAgentの位置と速度をリセット
this.rBody.angularVelocity = Vector3.zero;
this.rBody.velocity = Vector3.zero;
this.transform.localPosition = new Vector3(0.0f, 0.5f, 0.0f);
}
// Targetの位置のリセット
target.localPosition = new Vector3(
Random.value*8-4, 0.5f, Random.value*8-4);
}
// 状態取得時に呼ばれる
public override void CollectObservations(VectorSensor sensor)
{
sensor.AddObservation(target.localPosition.x); //TargetのX座標
sensor.AddObservation(target.localPosition.z); //TargetのZ座標
sensor.AddObservation(this.transform.localPosition.x); //RollerAgentのX座標
sensor.AddObservation(this.transform.localPosition.z); //RollerAgentのZ座標
sensor.AddObservation(rBody.velocity.x); // RollerAgentのX速度
sensor.AddObservation(rBody.velocity.z); // RollerAgentのZ速度
}
// 行動実行時に呼ばれる
public override void OnActionReceived(ActionBuffers actionBuffers)
{
// RollerAgentに力を加える
Vector3 controlSignal = Vector3.zero;
controlSignal.x = actionBuffers.ContinuousActions[0];
controlSignal.z = actionBuffers.ContinuousActions[1];
rBody.AddForce(controlSignal * 10);
// RollerAgentがTargetの位置にたどりついた時
float distanceToTarget = Vector3.Distance(
this.transform.localPosition, target.localPosition);
if (distanceToTarget < 1.42f)
{
AddReward(1.0f);
EndEpisode();
}
// RollerAgentが床から落下した時
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");
}
}
Unityに戻りHierarchyウィンドウで「RollerAgent」を選択し、Inspectorウィンドウの「Roller Agent(Script)」でMax Stepを1000に、Targetには「Target」を設定します。

最後に「RollerAgent」→「Add Component」→「Decision Requester」を追加。Decision Periodに10を設定します。

これで設定完了です。
動作確認
Unity上部の再生ボタンを押すと動作確認ができます。方向キーを押すと球を動かすことができます。

Macで以下が出た場合はMacの「システム環境設定」→「セキュリティとプライバシー」→「一般」を開きます。

以下で「このまま許可」をクリックでOKです。

視点が見づらい時はHierarchyウィンドウの「Main Camera」を選択し、InspectorウィンドウのPositionを(0,3,-12)など適宜変更すると見やすくなります。

球が箱にぶつかると箱の位置がランダムで変わります。今回は球に箱を目指すように学習させ、突撃ボールを作ろうというわけです。
強化学習を行う
最後に強化学習を行います。VSCodeに戻り「unity_ml-agents-main/ml-agents-release_19/config」にRollerBall.yamlファイルを作成します。unity_ml-agents-main/ml-agents-release_19/configを選択し、「new file」をクリック。ファイル名を「RollerBall.yaml」にします。

RollerBall.yamlに以下を貼り付けます。
behaviors:
RollerBall:
# トレーナー種別
trainer_type: ppo
# 基本設定
max_steps: 500000
time_horizon: 1000
summary_freq: 1000
keep_checkpoints: 5
# 学習アルゴリズムの設定
hyperparameters:
batch_size: 10
buffer_size: 100
learning_rate: 0.0003
beta: 0.001
epsilon: 0.2
lambd: 0.99
num_epoch: 3
learning_rate_schedule: linear
# ニューラルネットワークの設定
network_settings:
normalize: true
hidden_units: 128
num_layers: 2
# 報酬の設定
reward_signals:
extrinsic:
gamma: 0.99
strength: 1.0
ターミナルに行き、以下を実行します。
cd /workspaces/unity_ml-agents-main/ml-agents-release_19/
そして以下のコマンド実行します。–run-idは学習するたびに変更してください。
mlagents-learn ./config/RollerBall.yaml --run-id=run-test
その後Unityに戻り再生ボタンを押すと学習が開始します。ただし学習は永遠とし続けるのでStepが30000程度まで行ったらVSCodeのターミナルで「control+C」コマンドを打ち学習を止めます。ターミナルでは以下のような表示になっていると思います。
[INFO] RollerBall. Step: 28000. Time Elapsed: 257.932 s. Mean Reward: 1.000. Std of Reward: 0.000. Training.
[INFO] RollerBall. Step: 29000. Time Elapsed: 265.907 s. Mean Reward: 1.000. Std of Reward: 0.000. Training.
[INFO] RollerBall. Step: 30000. Time Elapsed: 274.536 s. Mean Reward: 1.000. Std of Reward: 0.000. Training.
[INFO] RollerBall. Step: 31000. Time Elapsed: 283.468 s. Mean Reward: 1.000. Std of Reward: 0.000. Training.
^C[INFO] Learning was interrupted. Please wait while the graph is generated.
[INFO] Exported results/run-test/RollerBall/RollerBall-31222.onnx
[INFO] Copied results/run-test/RollerBall/RollerBall-31222.onnx to results/run-test/RollerBall.onnx.
/workspaces/unity_ml-agents-main/ml-agents-release_19/results/run-test/RollerBall.onnxには学習された脳みそが入っています。これを推論モデルと言います。
ちなみにフォルダ名の「run-test」は先ほど実行したコマンドで指定したものになっていますね。そのため2回目を実行したい場合などは–run-idを変える必要があるわけです。
推論モデル実行
最後に学習された推論モデルをUnity上で実行してみましょう。UnityのProjectウィンドウのAssets内で右クリック→「Import New Assets」をクリックしunity_ml-agents-main/ml-agents-release_19/results/run-test/RollerBall.onnxをインポート。以下のように「RollerBall」がAssets内にインポートされてればOKです。

HierarchyウィンドウのRollerAgentを選択し、Inspectorウィンドウの「Behavior Parameters」→「Model」で「RollerBall」を選択。

再生ボタンを押すと推論モデルが実行されます。球が箱に向かっていく様子が確認できます。かわいい!!
こんなふうに世の中で話題の「AI(人工知能)」は簡単に作れてしまいます。AIってパッケージに書いとけば売れるだろ〜っていう人たちも多いので気をつけてください笑
(+α)興味がある方向け
学習結果をグラフで見ることもできます。以下のコマンドをターミナルで実行。
tensorboard --logdir=results
ブラウザでhttp://localhost:6006/を実行するとグラフが表示されます。
