Unityプロジェクト「ML-Agents:Penguin」part3
0.はじめに
本記事では、Unityが無料で提供しているプロジェクト「ML-Agents:Penguin」の制作方法を日本語版にして全6回に分けてやっていきます。
第3回は、コードの記述(前半)です。
1.C#スクリプトの作成
注意:
スクリプト作成時に名前を付けますが、ファイル名とプログラム内の名前が一致していないとエラーになります。対処方は次の2つです。
・間違った名前のファイルを削除し、正しく作り直す
・プログラム内の名前部分を修正する
ファイル作成段階では上でいいですが、プログラムを入力し終えた後は下がオススメです。
プロジェクトウィンドウ内にある「Penguin → Scripts」のフォルダー内に、次の4つのスクリプトを作成します。
・PenguinAcademy
・PenguinArea
・PenguinAgent
・Fish
2.PenguinAcademy.cs
"PenguinAcademy"は、Academyクラスの上に構築されます。
Academyクラスは「ML-Agents → Scripts → Academy.cs」内にあり、機械学習のトレーニングと推論を管理するために、ML-Agentsには必要なものです。ただし、抽象クラスであるためにSceneに直接追加することができないので、"Academy"から継承する"PenguinAcademy"クラスを作成します。
(1)『PenguinAcademy.cs』を「Visual Studio 2017」で開く
(2)次のコードを入力します(コピー可)。開いたプログラムを全部選択し、上書きすることでスムーズに進みます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MLAgents;
public class PenguinAcademy : Academy
{
/// Gets/sets the current fish speed
public float FishSpeed { get; private set; }
/// Gets/sets the current acceptable feed radius
public float FeedRadius { get; private set; }
}
プログラム解説:
・"UnityEngine" と "MLAgents"のusing宣言を追加
・"Monobehaviour"ではなく"Academy"から継承するようクラスを定義
・魚の速度と小魚に餌を与えるための許容範囲を指定するために、2つのfloat型のアクセスを追加
(3)PenguinAcademy クラス内({}間)に次のプログラムを入力する。
/// Called when the academy first gets initialized
public override void InitializeAcademy()
{
FishSpeed = 0f;
FeedRadius = 0f;
// Set up code to be called every time the fish_speed parameter changes
// during curriculum learning
FloatProperties.RegisterCallback("fish_speed", f =>
{
FishSpeed = f;
});
// Set up code to be called every time the feed_radius parameter changes
// during curriculum learning
FloatProperties.RegisterCallback("feed_radius", f =>
{
FeedRadius = f;
});
}
プログラム解説:
・InitializeAcademy関数をオーバーライドする
・2つのCallbackを登録して、"Academy" が "curriculum" の新しい値でリセットするたびにアクセスできるようにした
3.PenguinArea.cs
"PenguinArea"は、1つのペンギンと赤ちゃん(ペンギン)、複数の魚でトレーニングエリアを管理します。また、魚の除去や魚の産卵(発生)、ペンギンのランダム配置の責任があります。
(1)『PenguinArea.cs』を「Visual Studio 2017」で開く
(2)次のコードを入力します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MLAgents;
using TMPro;
public class PenguinArea : Area
{
}
プログラムの解説:
・Start関数とUpdate関数を削除
・"UnityEngine" と "MLAgents" と "TMPro" のusing宣言を追加
・"Monobehaviour"ではなく"Area"から継承するようクラスを定義
(3)PenguinAcademy クラス内({}間)に次のプログラムを入力する
[Tooltip("The agent inside the area")]
public PenguinAgent penguinAgent;
[Tooltip("The baby penguin inside the area")]
public GameObject penguinBaby;
[Tooltip("The TextMeshPro text that shows the cumulative reward of the agent")]
public TextMeshPro cumulativeRewardText;
[Tooltip("Prefab of a live fish")]
public Fish fishPrefab;
private PenguinAcademy penguinAcademy;
private List<GameObject> fishList;
プログラム解説:
・これらの変数は、Scene内の重要なオブジェクトを追跡する。オブジェクトをUnityのpublic変数に接続します。
(4)先ほどのプログラムの続きに、次のプログラムを入力する
/// Reset the area, including fish and penguin placement
public override void ResetArea()
{
RemoveAllFish();
PlacePenguin();
PlaceBaby();
SpawnFish(4, penguinAcademy.FishSpeed);
}
/// Remove a specific fish from the area when it is eaten
/// <param name="fishObject">The fish to remove</param>
public void RemoveSpecificFish(GameObject fishObject)
{
fishList.Remove(fishObject);
Destroy(fishObject);
}
/// The number of fish remaining
public int FishRemaining
{
get { return fishList.Count; }
}
プログラム解説:
・ResetArea関数を追加
・RemoveSpecificFish関数を追加
・FishRemaining関数を追加
・ペンギンが魚を捕まえると、PenguinAgentスクリプトはRemoveSpecificFish関数を呼び出して、水から魚を取り除きます。
(4.5)これからの関数について
次のいくつかの関数は、エリア内の動物の配置を処理します。水中には魚を生み、赤ちゃんペンギンは陸上に配置します。ペンギンは陸と水の間を移動できるので、どちらにも配置できます。次の図は、ランダムに各タイプの動物を配置する場所の図です。
(5)さらに続けて、次のプログラムを入力します。
/// Choose a random position on the X-Z plane within a partial donut shape
/// <param name="center">The center of the donut</param>
/// <param name="minAngle">Minimum angle of the wedge</param>
/// <param name="maxAngle">Maximum angle of the wedge</param>
/// <param name="minRadius">Minimum distance from the center</param>
/// <param name="maxRadius">Maximum distance from the center</param>
/// <returns>A position falling within the specified region</returns>
public static Vector3 ChooseRandomPosition(Vector3 center, float minAngle, float maxAngle, float minRadius, float maxRadius)
{
float radius = minRadius;
float angle = minAngle;
if (maxRadius > minRadius)
{
// Pick a random radius
radius = UnityEngine.Random.Range(minRadius, maxRadius);
}
if (maxAngle > minAngle)
{
// Pick a random angle
angle = UnityEngine.Random.Range(minAngle, maxAngle);
}
// Center position + forward vector rotated around the Y axis by "angle" degrees, multiplies by "radius"
return center + Quaternion.Euler(0f, angle, 0f) * Vector3.forward * radius;
}
プログラム解説:
・ChooseRandomPosition関数を追加
・この関数は、特別な半径と角度の制限を使用して、エリアの中心点の周りのウェッジ内のランダムな位置を選択します。
(6)さらに続けて、次のプログラムを入力します。
/// Remove all fish from the area
private void RemoveAllFish()
{
if (fishList != null)
{
for (int i = 0; i < fishList.Count; i++)
{
if (fishList[i] != null)
{
Destroy(fishList[i]);
}
}
}
fishList = new List<GameObject>();
}
プログラム解説:
・RemoveAllFish関数を追加
・ResetArea関数は、RemoveAllFish関数を呼び出して、新しい魚を産卵(発生)する前に、そのエリアに魚がいないことを確認します。
(7)さらに続けて、次のプログラムを入力します。
/// Place the penguin in the area
private void PlacePenguin()
{
Rigidbody rigidbody = penguinAgent.GetComponent<Rigidbody>();
rigidbody.velocity = Vector3.zero;
rigidbody.angularVelocity = Vector3.zero;
penguinAgent.transform.position = ChooseRandomPosition(transform.position, 0f, 360f, 0f, 9f) + Vector3.up * .5f;
penguinAgent.transform.rotation = Quaternion.Euler(0f, UnityEngine.Random.Range(0f, 360f), 0f);
}
/// Place the baby in the area
private void PlaceBaby()
{
Rigidbody rigidbody = penguinBaby.GetComponent<Rigidbody>();
rigidbody.velocity = Vector3.zero;
rigidbody.angularVelocity = Vector3.zero;
penguinBaby.transform.position = ChooseRandomPosition(transform.position, -45f, 45f, 4f, 9f) + Vector3.up * .5f;
penguinBaby.transform.rotation = Quaternion.Euler(0f, 180f, 0f);
}
プログラム解説:
・PlacePenguin関数を追加
・PlaceBaby関数を追加
・これらの関数はペンギンを配置します。
・どちらの関数も、100倍の速度で長時間トレーニングすると予期しない事態が発生する可能性があるため、剛体速度をゼロに設定します。
・例えば、ペンギンが床から落ちて、下に向かって加速した時、エリアがリセットされると位置もリセットされるが、下降速度がリセットされなかったら、ペンギンは地面によって爆発するかもしれないからです。
(8)さらに続けて、次のプログラムを入力します。
/// Spawn some number of fish in the area and set their swim speed
/// <param name="count">The number to spawn</param>
/// <param name="fishSpeed">The swim speed</param>
private void SpawnFish(int count, float fishSpeed)
{
for (int i = 0; i < count; i++)
{
// Spawn and place the fish
GameObject fishObject = Instantiate<GameObject>(fishPrefab.gameObject);
fishObject.transform.position = ChooseRandomPosition(transform.position, 100f, 260f, 2f, 13f) + Vector3.up * .5f;
fishObject.transform.rotation = Quaternion.Euler(0f, UnityEngine.Random.Range(0f, 360f), 0f);
// Set the fish's parent to this area's transform
fishObject.transform.SetParent(transform);
// Keep track of the fish
fishList.Add(fishObject);
// Set the fish speed
fishObject.GetComponent<Fish>().fishSpeed = fishSpeed;
}
}
プログラム解説:
・SpawnFish関数を追加
・指定された数の魚をエリアに配置し、それらのデフォルトの遊泳速度を設定します。
(9)さらに続けて、次のプログラムを入力します。
/// Called when the game starts
private void Start()
{
penguinAcademy = FindObjectOfType<PenguinAcademy>();
ResetArea();
}
プログラム解説:
・Start関数を追加
・この関数は、Sceneで"PenguinAcademy"を見つけ、"Academy"をリセットします。
・1つまたは複数の"PenguinArea"を制御するSceneには、1つの"PenguinAcademy"のみが存在することに注意すること。
(10)さらに続けて、次のプログラムを入力します。
/// Called every frame
private void Update()
{
// Update the cumulative reward text
cumulativeRewardText.text = penguinAgent.GetCumulativeReward().ToString("0.00");
}
プログラム解説:
・Update関数を追加
・この関数は、エリアの背面の壁にある累積報酬表示テキストをフレームごとに更新します。
・トレーニングに直接関係はないですが、ペンギンのパフォーマンスを確認するのに役立ちます。
4.最後に
今回、"PenguinAcademy.cd" と "PenguinArea.cs" をプログラムしました。この記事は、強化学習の体験的な感覚で書いているので、具体的な解説は省いております。興味を持った方は、ぜひプログラムを理解し、新たなプロジェクトの構築に挑戦してもらえたらと思います。
次回、"PenguinAgents.cs" と "Fish" のプログラム記述についてやっていきます。