
独自のやり方で学習させたニューラルネットのモデルを、ゲームエンジン側で動かします。

関連
- ◯
- ML-Agents の低レベルAPIを使う(トレーニング編):LLAPI, Python, Unity
- ◯
- コンテナに強化学習のフレームワークを設置する:Docker, ML-Agents
概要
せっかく独自のやり方で強化学習を行ったわけですから、学習させたニューラルネットのモデル(NNモデル)もゲームエンジンで使いたいですよねーー
次は、ML-Agents の低レベルAPI(LLAPI )を使って学習させたNNモデルを、ゲームエンジン側(Unity )で動かすやり方です。
実装
次のように実装します:[※1]
- ・
- Python 側 …… 学習の最後に、パラメータを更新したNNモデルを、標準形式で保存するコードを追加します。
- ・
- Unity (C#)側 …… 標準形式のNNモデルを使って、エージェントを動かします。
- ※1
- ML-Agents のフレームワークから得たNNモデルなら、訓練に使った3Dモデルにそのまま取り付ければ動きますーーしかし独自のコードで学習させたものは、そういう工夫がされているわけではありませんーーなので訓練とはべつに、NNモデルに観察を与え〜行動の指針を得るスクリプトを書く必要があります。
実装:Python
Python側のスクリプトを記述します。
エージェントのクラスに、NNモデルの実体(インスタンス)を得るためのメソッドを追加します:
class Agent:
...
def getnnm(self):
return self.pi
あとは訓練部分に、NNモデルを標準形式で保存する記述を加えるだけです:[※1]
# トレーニングの冒頭に追加:
pthnnx = '<nn_model>.onnx' # NNモデルの保存場所
cntsve = int(trials) - 1
# エピソードの最後に追加:
if cntepi == cntsve:
nnmtgt = agtnnm.getnnm()
vernnx = 9 # オプセットのバージョンはこれが推奨されていますが、これ以上でも動きます
namset = 'sensor'
namget = 'action'
dmyset = torch.zeros(4).unsqueeze(0)
torch.onnx.export(
nnmtgt,
dmyset,
pthnnx,
opset_version=vernnx,
export_params=True,
do_constant_folding=True,
input_names=[namset],
output_names=[namget]
)
- ※1
- Pytorch のonnx.export メソッドを使うことで、標準形式(ONNX)で保存できます。
実装:Unity
Unity 側のスクリプトを記述します。
必要なモジュール群を読み込み:[※1]
using UnityEngine; using Unity.Barracuda;
- ※1
- Barracuda は、Unity で標準のNNモデル(ONNX)をあつかうライブラリです。
名前空間とクラスを定義し、クラス内で使う変数群を定義〜初期化します:
namespace <namespace_project> {
public class <monobehaviour_class>: MonoBehaviour {
[SerializeField] public NNModel nnmtgt; // この項目からNNモデルを取得します
public int prddcs = 5;
public float fcemov = 100.0f;
private int cntstp;
private int flgact = 0;
private GameObject objtgt, objpol;
private Rigidbody rgdtgt, rgdpol;
private Model nnmrun;
private IWorker nnmwrk;
private float[] lstget = new float[] {0, 0};
// ... 各メソッドの定義
}
}
以降、それぞれのメソッドの定義ですーーゲーム開始時の初期設定を行います:
void Start() {
objtgt = this.gameObject;
rgdtgt = objtgt.GetComponent<Rigidbody>();
objpol = objtgt.transform.Find("<name_pole>").gameObject;
rgdpol = objpol.GetComponent<Rigidbody>();
nnmrun = ModelLoader.Load(nnmtgt);
nnmwrk = WorkerFactory.CreateWorker(WorkerFactory.Type.CSharp,nnmrun); // ワーカはもっとも無難なタイプを使っていますが、動きそうならより効率的なタイプを試せます
cntstp = 0;
}
ゲームのフレームごとの処理を記述しますーーML-Agents は、強化学習の環境を再現させるよう、フレームの更新は(Updateではなく)時間と連動するFixedUpdate を使っています。なので再生には、こちらのメソッドを使います:[※2]
void FixedUpdate() {
float postgt, vlotgt, angpol, vlopol;
float[] lstset;
Tensor tenset; // (n:1,h:1,w:1,c:4)
Tensor tenget; // (n:1,h:1,w:1,c:2)
// ... 観測の記述
// ... 行動の記述
}
- ※2
- 参照:
- ・
- https://docs.unity3d.com/Packages/com.unity.ml-agents@1.0/api/Unity.MLAgents.Academy.html
- ・
- https://genkitech.net/unity-ml-agents-step-frame
環境からの観測をNNモデルに与え、行動の指針を得ますーーBarracudaは、NNモデルを画像認識を前提にした形式であつかっているので、その形式に沿って値を与えます(ここでは、最後のチャネル次元に観測結果の配列を与えます)。なお学習時のフレームごとの観測取得タイミング(DecisionRequester > decisionPeriod )と合わせるために、指定のフレーム数だけスキップします:[※3]
cntstp = cntstp + 1;
if (cntstp >= prddcs) {
postgt = objtgt.transform.position.x;
vlotgt = rgdtgt.velocity.x;
angpol = SymmetricalRadian(objpol.transform.localEulerAngles.z * Mathf.Deg2Rad);
vlopol = rgdpol.angularVelocity.z;
lstset = new float[] {postgt,vlotgt,angpol,vlopol};
tenset = new Tensor(1,1,1,4,lstset);
nnmwrk.Execute(tenset);
tenget = nnmwrk.PeekOutput();
lstget = new float[] {tenget[0,0,0,0],tenget[0,0,0,1]};
cntstp = 0;
}
- ※3
- NNモデルにはExecute() で入力を与え、PeekOutput()から出力を得ます。
NNモデルが返した行動の指針(ここでは行動のそれぞれの確率)から、エージェントに行動を与えますーーここでは、確率の大きい方の行動を選択するようにしています:
if (lstget[0] >= lstget[1]) {
rgdtgt.AddForce(new Vector3(+ fcemov,0.0f,0.0f));
}
else {
rgdtgt.AddForce(new Vector3(- fcemov,0.0f,0.0f));
}
次は、観察で得たポールの傾き(ラジアンの値)を、正負で対称にするための変換です:
private float SymmetricalRadian(float radorg) {
if (radorg > Mathf.PI) {
radorg = radorg - Mathf.PI * 2;
}
return radorg;
}
再生
Unity のエディタからプロジェクトを実行すれば、取り付けたNNモデルでエージェントが動きます:[※1]
- ※1
- あまり訓練に時間をかけてないので、学習できてないですね……
