MkItYs

MkItYs > 音楽・漫画・VR・自律制御 > 

images

ML-Agents の低レベルAPIを使う(NNモデル適用編):LLAPI, Barracuda, ONNX

images

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


images

関連


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
あまり訓練に時間をかけてないので、学習できてないですね……