前回の記事の続きです。
前回は、Keras で学習させたモデルを Barracuda で使える「.nn」ファイルに変換するところまでを解説しました。
本記事では、いよいよ、Unity 上での実装に取り掛かります。
実装も、基本的には公式ドキュメントに沿った形で作業を進めていきます。
また、サンプルプログラムはこちらにあります。
まず、最低限モデルの推論が動作するところまで実装しましょう。
はじめに、モデルを使用するための Script を作成します。
モデルの推論は、大まかに次の手順を踏んで実行します。
・学習済みモデルのロード
・ワーカーの作成(Barracuda では推論エンジンのことをワーカーと呼びます)
・入力テンソルの作成
・推論の実行
・実行結果を出力
・後片付け(メモリの解放など)
それらを踏まえた上で、サンプルコードをご覧ください。
サンプルコード:TestModel.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Barracuda;
namespace BarracudaSample
{
public class TestModel : MonoBehaviour
{
[SerializeField, Tooltip("学習済みモデルのファイル名 (.nn ファイル)")]
// 必ず Assets/StreamingAssets 直下に置くこと.
private string m_ModelName;
[SerializeField, Tooltip("入力する文字画像")]
private Texture2D m_InputTexture;
// Start is called before the first frame update
void Start()
{
// 動作確認のサンプルなので, すべて Start() 上で実行しています.
// モデルのロード
var model = ModelLoader.LoadFromStreamingAssets(m_ModelName + ".nn");
// ワーカー (推論エンジン) の作成
var worker = BarracudaWorkerFactory.CreateWorker(BarracudaWorkerFactory.Type.ComputePrecompiled, model);
// 入力の作成. 第2引数はチャンネル数.
var tensor = new Tensor(m_InputTexture, 1);
// 推論の実行
worker.Execute(tensor);
// 推論結果の取得
var O = worker.Peek();
// 結果の表示
int pred = 0;
float maxVal = float.MinValue;
for (int i = 0; i < 10; ++i)
{
if (maxVal < O.readonlyArray[i])
{
pred = i;
maxVal = O.readonlyArray[i];
}
}
Debug.Log("Pred: " + pred.ToString());
// 後片付け (メモリの解放など)
O.Dispose();
worker.Dispose();
}
// Update is called once per frame
void Update()
{
}
}
}
それでは、重要なところを中心に順を追って説明していきます。
using Barracuda;
Barracuda の API を利用するために、まず using Barracuda を宣言します。
モデルのロードには、
ModelLoader.LoadFromStreamingAssets()
を使用します。
ワーカー(推論エンジン)の作成には、
BarracudaWorkerFactory.CreateWorker()
を使用します。
ここまでは、ドキュメントに沿って実装していけば問題ないと思われます。
次は、いよいよ推論です。
推論を実行する前に、モデルに渡す入力を用意します。
Barracuda では入力として Tensor 型の変数を使用します。
この Tensor 型の変数は、いわゆる NHWC 型※1と呼ばれるデータ構造で初期化することができますが、NHWC 型による初期化の代わりにテクスチャーを用いて初期化をすることもできます。
※1:Batch 数、Height、Width、Channel といったいわゆる2次元画像を入力として用いる際によく使われるデータ構造だそうです。
ここでは、
var tensor = new Tensor(m_InputTexture, 1);
という形で、第1引数に Texture2D を、第2引数にチャンネル数を入れています。
ここで、一度入力として使用するテクスチャーについて詳しく見ていきます。
せっかくなので、入力する文字画像はエディター実行中にマウスなどを使って動的に生成したいところですが、まずは動作確認ということで、作成済みのテクスチャーを入力画像として使用します(エディター実行中にマウスを使って動的に画像を生成する方法については後ほど解説します)。
元となる画像は、以下の点に気を付けていただければ、ペイントなどで自由に作っていただいて構いません。
まず、モデルを学習させる時点で、入力画像のサイズは 28 pixel × 28 pixel で作成したので、入力画像も同様に 28 pixel × 28 pixel で作成します。
また、学習した MNIST データは、数字が描かれた部分が1、何も描かれていない部分が0となる画像から構成されており、入力する画像もそれに合わせて、黒の背景に白文字で数字が描かれていると都合が良いです。
作成した画像は「.png」などで保存し、エディターにインポートします。
インポート後、テクスチャーの設定を少しだけ修正します。
Unity にインポートされたテクスチャーは、そのサイズが2のべき乗(2, 4, 8, 16, 32, 64……のこと)になっていない場合、デフォルトで2のべき乗にサイズが修正されてしまいます。
そうならないように、テクスチャーの設定から、
Advanced > Non-Power of 2 の設定を「None」に変更します。
これで、インポートしたテクスチャーのサイズが28 × 28に保たれます。
このとき、インポートされたテクスチャーのチャンネルは(R, G, B, Alpha)の4チャンネルで構成されていますが、Tensor 型の入力初期化時に、チャンネル数を1と設定しておけば、自動的に R チャンネルのみが使われるよう Barracuda 側で処理してくれるみたいです。
また、モデルの学習時に、入力値を0~1の間に収まるよう正規化したので、こちらの入力でも値の正規化が必要に思えますが、Texture2D として扱う時点で Unity の内部で既に各チャンネルの値は0~1に正規化されており、特にこちら側で処理をしてやる必要はありません。
これで入力の準備が整いました。やっと推論をおこなえます。
推論の実行は、
worker.Execute();
を呼ぶだけでおこなえます。
推論結果は次のように取得します。
var O = worker.Peek();
ここで前回のサンプルノートブックの内容を思い出していただきたいのですが、モデルの最終出力は、入力された数値が 0~9 の数字それぞれに対して、どれくらいその数字らしいか、を数値で表したものでした。
これらの数値は、O.readonlyArray[] のなかに格納されています。
O.readonlyArray[] は要素数10の配列で、それぞれ要素0から順に「入力された数字は0らしいか」「入力された数字は1らしいか」……「入力された数字は9らしいか」を表す数値が入っています。
ですので、入力された数値が、どの数値に対して最もそれらしいかは、次のコードで取得することができます。最もそれらしいと判断された数値が変数 pred に代入されます。
int pred = 0;
float maxVal = float.MinValue;
for (int i = 0; i < 10; ++i)
{
if (maxVal < O.readonlyArray[i])
{
pred = i;
maxVal = O.readonlyArray[i];
}
}
最後に後片付けをします。
O.Dispose();
worker.Dispose();
これは、推論の実行に使用したメモリを解放するためにおこなうそうです。
スクリプトをシーン上の適当なゲームオブジェクトにアタッチして、そのオブジェクトの Inspector ビューから、
private string m_ModelName;
にインポートしたモデルの名前を、
private Texture2D m_InputTexture;
に入力用のテクスチャーをセットして、エディターを実行してみてください。
Debug.Log として、推論した値が出力されれば成功です。
これで、Unity 上での推論の実行は一通りおこなえました。
動的に文字を入力して推論を実行させる
ワーカー(推論エンジン)に与える入力テクスチャーを動的に書き換えられるようにすれば、エディター実行中にユーザーが動的に文字を入力してモデルに推論を実行させることができます。
動的なテクスチャーの書き換えは、敬愛するおもちゃラボ様の記事を参考に実装させていただきました。
基本的には記事の通り実装していけば、動的な文字入力は実現できます。
詳細な実装については GitHub のサンプル(Assets > Scenes > SampleDynamicTexture.unity)をご覧ください(そろそろ文章を書くのに飽きてきました)。
注意点としては、入力テクスチャーの上下方向です。上下が逆の場合、とんでもな推論結果が出力されてしまいます。出力結果がおかしいな、と思った場合は Panel オブジェクトの Rotation の Y 軸の値を 180 にしてみてください。恐らく正しい値が出力されるはずです。
これで、Keras で学習させたモデルを Unity 上で実行する方法の解説は終わりです。
長文にお付き合いいただきありがとうございました。
皆様、良い Unity & Deep Learning ライフを! Enjoy:)
Comments