Udon で計算する ONNX モデル(試験中)
この章は試験中です。 [Onnx("…")] は、入力と出力を配列で渡し、結果を数として読み出します。
例にするモデル
Section titled “例にするモデル”| モデルのファイル | Judge.cs と同じフォルダの gesture.onnx |
| 入力 | pose — float32 の要素が 12 個(形は [1,12]) |
| 出力 | scores — float32 の要素が 4 個(形は [1,4]) |
| 生成されるファイル | Judge.Gesture.Model.cs |
| 組み込む手順 | 組み込む手順のとおりです |
属性を書いたスクリプト
Section titled “属性を書いたスクリプト”using Tsukimi;using UnityEngine;
// 属性の引数は、このスクリプトから見たモデルのファイルの経路です。// 属性を 2 つ以上書くと、モデルごとに名前が生えます。[Onnx("gesture.onnx")]public partial class Judge : TsukimiBehaviour{ // 生成されたファイルがこのクラスの残りになるので、partial が必要です。
// 入力に詰める値の取り出し元。Inspector で 12 個挿します。 public Transform[] joints;
// 入力と出力をまとめて持つ値。型は生成されたファイルが宣言します。 private GestureIO io;
// 選ばれた番号。他の Behaviour から読めます。 public int chosen;
void Start() { io = new GestureIO(); // 配列の確保は 1 度だけです。以後は同じ配列の中身を書き換えます。 // 要素数は生成された定数を使います(モデルを差し替えると値も変わります)。 // 配列の型はモデルの要素の型で決まります(このモデルは float32 なので float[])。 io.In0 = new float[SizeIn0]; io.Out0 = new float[SizeOut0]; }
public void Decide() { // 入力は全部詰めます。詰め残した要素は前回の値のまま計算されます。 for (int i = 0; i < SizeIn0; i++) io.In0[i] = joints[i].localPosition.y;
// 計算します。返るまでに全部の層が進みます。 RunGesture(io);
// 結果は io.Out0 に入ります。ここでは最も大きい成分の番号を選びます。 chosen = 0; for (int i = 1; i < SizeOut0; i++) if (io.Out0[i] > io.Out0[chosen]) chosen = i; }}生成されたファイル
Section titled “生成されたファイル”重みと層の中身を省いた抜き書きです。
// This file was generated from an ONNX model. Manual edits are lost on the next generation.// Source model: gesture
using UnityEngine;
// 入力と出力をまとめた型。もとのテンソルの名前と形がコメントに残ります。public struct GestureIO{ public float[] In0; // pose [1,12] public float[] Out0; // scores [1,4]}
public partial class Judge{ // 配列を確保するときに使う要素数。コンパイルの時点で決まっています。 public const int SizeIn0 = 12; // [1,12] public const int SizeOut0 = 4; // [1,4]
// private なので、入力を詰めずに外から呼ぶことはできません。 private void RunGesture(GestureIO io) { float[] In0 = io.In0; // pose float[] Out0 = io.Out0; // scores
// 学習した重みは、配列への代入として並びます。 float[] c0 = new float[12]; c0[0] = 0.0002085293f; c0[1] = 0.71990794f; // …(重みの数だけ続きます)…
// 層と層の間に挟む配列。4 つずつまとめられる層は Vector4 になります。 float[] t5 = new float[12]; // centered [1,12] Vector4[] t7 = new Vector4[6]; // g0 [1,24]
// 層は、もとのモデルでの名前と演算つきで 1 つずつ並びます。 // center: Elementwise pose mean -> centered for (int i = 0; i < 12; i++) { t5[i] = In0[i] - c0[i]; }
// …(層の数だけ続きます)…
// out: Copy g2 -> scores for (int i = 0; i < 4; i++) { Out0[i] = t11[i]; } }}生成される名前の一覧
Section titled “生成される名前の一覧”| 生成される名前 | 説明 |
|---|---|
GestureIO | 入力と出力を持つ型。中身はモデルの要素の型の配列で(要素の型)、In0・Out0 のように並びます |
SizeIn0 / SizeOut0 | 配列ごとの要素数。配列は Start で 1 度だけ確保します |
RunGesture(io) | その値の入力で計算し、出力を同じ値へ書き戻します |
In0 / In1 … | 番号はモデルの入力の並び順です。出力も同じ規則です |
| 呼び出せる場所 | 計算するメソッドは外部から呼び出せません。入力を代入しないまま計算することを防ぐためです |
| 費用 | 計算は Udon の上で 1 つずつ進みます。毎フレーム計算する前にプロファイラで計測してください |
| 重みの格納先 | 重みはコードの中の定数です。モデルのファイルは配布物に含める必要がありません |
| 計算しない出力 | どのノードも計算しない出力(モデルに保存した定数・出力にも挙げた入力)を持つモデルはエラーです。その定数や入力の配列は、自分のコードで読みます |
| モデルの要素の型 | 入力と出力の配列 |
|---|---|
| float32 | float[] |
| float64 | double[] |
| uint8 / int8 | byte[] / sbyte[] |
| uint16 / int16 | ushort[] / short[] |
| uint32 / int32 | uint[] / int[] |
| uint64 / int64 | ulong[] / long[] |
| bool | bool[] |
| float16・bfloat16・float8・4 ビットの整数・複素数・文字列 | Udon に無い型なので、入力か重みに 1 つでもあれば、型を名指してコンパイルのエラーになります(float16・bfloat16 のモデルは float32 に変換してから書き出します) |
計算はその型のまま行い、整数は桁あふれで折り返します(uint8 の 0 - 1 は 255・演算ごとに受け取る型はONNX モデルの演算(試験中)にあります)。