はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】はじめてニューラルネットワーク推論ライブラリ「Barracuda」を触ってみる(MNIST)

はじめに

今回はUnity公式のニューラルネットワーク推論ライブラリであるBarracudaを触ってみるという記事になります。

docs.unity3d.com

ただ最初に言っておくと機械学習について私は生まれたばかりの赤子並みの知識しかないので、間違ったことも多く含んでいる可能性があるのであしからず。

概要

Barracudaクロスプラットフォームで動作可能(ビルドすることのできるプラットフォームであればいける)な軽量ニューラルネットワーク推論ライブラリです。

The Barracuda package is a lightweight cross-platform neural network inference library for Unity.

Barracuda can run neural networks on both the GPU and CPU. For details, see Supported platforms.

Introduction to Barracuda | Barracuda | 3.0.0

またCPUだけでなくGPUを利用した動作も可能だそう。(プラットフォームによる規制あり)

Barracuda supports the following platforms:

CPU inference: all Unity platforms are supported.

GPU inference: all Unity platforms are supported except:

OpenGL ES on Android/iOS: use Vulkan/Metal.
OpenGL Core on Mac: use Metal.
WebGL: use CPU inference.

Supported platforms | Barracuda | 3.0.0

Barracudaを利用するにあたって、以下のステップを踏みます。

  1. ONNXファイルをPytorch, TensorFlowKerasから出力する
  2. .onnxをプロジェクトにインポート
  3. アセットからモデルを読み込む
  4. 推論エンジン(Worker)を作成
  5. モデルを実行する
  6. 結果を取得する


また現在(2021/4/13)でのBarracudaの最新のバージョンがv3.0.0で、Unity2019.4以上で利用可能になっています。

環境

Unity 2020.3.29f1
Barracuda v3.0.0

インストール

PackageManagerGit経由(それかmanifest.json)でcom.unity.barracudaと打ち込めばインストールできます。

f:id:hanaaaaaachiru:20220324220013p:plain
Add package from Git Url
f:id:hanaaaaaachiru:20220324220127p:plain
Barracudaのインストール

onnxを用意する

まず.onnxを用意するところからですが、ネットに転がっていた手書き文字認識のONNXモデル(mnist-8.onnx)を利用させていただきます。
github.com

こちらをUnityのAsset以下の好きなところにインポートすればOKです。

f:id:hanaaaaaachiru:20220324222059p:plain
onnxをインポート

コードからモデルを読み込む

NNModelをインスペクターから読み込み、ランタイムで利用する際にModel型に変換を行う必要があります。

[SerializeField] private NNModel model;

private void Start()
{
    // 利用する際はrun-time model(Model型)に変換する
    Model runtimeModel = ModelLoader.Load(model);
}

推論エンジン(Worker)を構築する

Workerモデルを実行可能なタスクに分解し、CPUもしくはGPUにスケジュールをします。

A Worker breaks down the model into executable tasks and schedules them on the GPU or CPU.

Introduction to Barracuda | Barracuda | 3.0.0

private void Start()
{
    // 利用する際はrun-time model(Model型)に変換する
    Model runtimeModel = ModelLoader.Load(model);
        
    // Workerを作成する
    IWorker worker = WorkerFactory.CreateWorker(runtimeModel); 
        
    // GPU
    worker = WorkerFactory.CreateWorker(WorkerFactory.Type.ComputePrecompiled, runtimeModel);
    worker = WorkerFactory.CreateWorker(WorkerFactory.Type.Compute, runtimeModel);
    // slow - GPU path
    worker = WorkerFactory.CreateWorker(WorkerFactory.Type.ComputeRef, runtimeModel);

    // CPU
    worker = WorkerFactory.CreateWorker(WorkerFactory.Type.CSharpBurst, runtimeModel);
    worker = WorkerFactory.CreateWorker(WorkerFactory.Type.CSharp, runtimeModel);
    // very slow - CPU path
    worker = WorkerFactory.CreateWorker(WorkerFactory.Type.CSharpRef, runtimeModel);
}

モデルの実行

モデルの読み込みとWorkerの作成ができたら、モデルの実行をすることができます。

また入力する画像データ(Mnistは28x28のグレースケール)に対応したTensorを生成します。

// Mnistは28x28のグレースケールな画像なので、1x28x28x1のTensorを用意
Tensor input = new Tensor(n: 1, h: 28, w: 28, c: 1);

Working with data | Barracuda | 3.0.0

生成したTensorにデータを入れなければいけないのですが、こちらは後でサンプルを用意しておきます。

// 以下のように値を代入していく
input[0, 10, 12, 0] = 0.4f;

ちなみにonnxの入出力のテンソルのbatch, height, width, channelsはインスペクターより確認できたりします。

f:id:hanaaaaaachiru:20220324230631p:plain
onnxのインスペクター

推論を開始するのは以下のコードを書くだけです。

worker.Execute(input);


今までの処理のサンプルは以下の通り。

// 利用する際はrun-time model(Model型)に変換する
Model runtimeModel = ModelLoader.Load(model);
        
// Workerを作成する
IWorker worker = WorkerFactory.CreateWorker(runtimeModel);

// Mnistは28x28のグレースケールな画像なので、1x28x28x1のTensorを用意
// inputに情報を書き込む処理を書く必要あり
Tensor input = new Tensor(n: 1, h: 28, w: 28, c: 1);

// 推論を実行する
worker.Execute(input);

出力を取得する

出力を取得するにはworker.PeekOutput()を使います。

// 利用する際はrun-time model(Model型)に変換する
Model runtimeModel = ModelLoader.Load(model);
        
// Workerを作成する
IWorker worker = WorkerFactory.CreateWorker(runtimeModel);

// Mnistは28x28のグレースケールな画像、画像を1x28x28x1のTensorに変換する
// inputに情報を書き込む処理を書く必要あり
Tensor input = new Tensor(n: 1, h: 28, w: 28, c: 1);

// 推論を実行する
worker.Execute(input);
        
// 出力を取得する
Tensor output = worker.PeekOutput();

// 結果を出力する Mnistは「0」~「9」の確率が出力されます。
for (var i = 0; i < output.channels; i++)
{
    Debug.Log($"[{i}] : {output[0,0,0,1]}");
}
        
// 使い終わったらお片付け
input.Dispose();
worker.Dispose();

コメントにも記載しましたが、Mnist0~9までの確率が出力されます。
softmax関数を通すと、全体を1としたときの0 ~ 1の確率の値になります。

/// <summary>
/// SoftMax関数
/// </summary>
public static IEnumerable<float> SoftMax(this IEnumerable<float> source)
{
    var exp = source.Select(Mathf.Exp).ToArray();
    var sum = exp.Sum();
    return exp.Select(x => x / sum);
}
f:id:hanaaaaaachiru:20220324231457p:plain
出力された様子

手書きできるようにする

手書きをして、それを当てられるようにコードを追記しました。

ただブログにベタッと貼ると長くなってしまうので、Githubに上げておきましたので気になる方は見てみてください。
github.com