見出し画像

Unity Barracuda - ニューラルネットワーク推論ライブラリ

以下の記事を参考に書いてます。

Unity Barracuda

1. Unity Barracuda

「Unity Barracuda」は、Unity向けの軽量でクロスプラットフォームのニューラルネットワーク推論ライブラリです。「Barracuda」はGPUとCPUの両方でニューラルネットを実行できます。現在プレビュー版(0.5.0)のため、今後大きな変更が加えられる可能性があります。

2. サポートしているネットワーク

サポートしているネットワークの種類は次のとおりです。
[情報源]

・MobileNet
・VGG
・YOLOv2
・ESRGAN
・Pix2Pix
・SSD object detection(NEW)
・Mask-RCNN(NEW)
・Faster-RCNN(NEW)

3. Unity Barracuda の取得

◎ Unity Package Manager 経由
「Unity Editor」で「Package Manager」を開き、「Preview Package」を有効にし、「Barracuda」を選択してインストールします。

◎ GitHub 経由
Unityプロジェクトの「Packages/manifest.json」を編集し、「Barracuda」 GitHubリポジトリに依存関係を追加します。

"com.unity.barracuda" : "https://github.com/Unity-Technologies/barracuda-release.git"

4. Unity Barracuda の使用

アプリケーションで「Barracuda」を使用するには、次の手順が必要です。

(1) プロジェクトに.onnxファイルを追加
(2) モデルのロード
(3) 推論エンジン(Worker)の生成
(4) モデルの実行
(5) 結果の取得

プロジェクトに直接.onnxファイルを追加するだけでONNXモデルをインポートできます。Tensorflowモデルは、現時点ではPythonスクリプトで実行する際に注意が必要です。

5. モデルのロード

ONNXモデルをロードするには、.onnxファイルをプロジェクトに追加します。インポートされ、NNModelのアセットとして表示されます。

次に、NNModelのpublicフィールドをC#スクリプトに追加し、EditorのUI経由でアセットの参照を指定します。そして、ModelLoaderでモデルを読み込みます。

public NNModel modelSource;

<省略>

var model = ModelLoader.Load(modelSource);

Pythonスクリプト「tensorflow_to_barracuda.py」で変換したTensorflowモデルを読み込むには、次のコードを使用します。

var model = ModelLoader.LoadFromStreamingAssets(modelName + ".nn");

6. 推論エンジン(Worker)の生成

「Barracuda」の推論エンジンは(Worker)は、モデルを実行可能なタスクに分割し、GPU/CPUでそれらをスケジューリングします。

var worker = BarracudaWorkerFactory.CreateWorker(BarracudaWorkerFactory.Type.ComputePrecompiled, model)

7. モデルの実行

入力は1つのTensor(入力が1つ)、または名前とTensorのペアの辞書を指定できます。

var inputs = new Dictionary<string, Tensor>();
inputs[name1] = new Tensor(...);
inputs[name2] = new Tensor(...);
worker.Execute(inputs);

GPUバックエンドの実行は非同期です。現在、CPUバックエンドの実行は同期的ですが、将来すべてのバックエンドが非同期になることを想定した方が良いです。

8. 結果の取得

モデルに1つの出力しかない場合は、worker.PeekOutput()を使用できます。
それ以外の場合は、出力名を指定する必要があります。

var O = worker.PeekOutput(outputName);

worker.PeekOutput()の戻り値のTensorの所有権は、Barracudaクライアント側に譲渡されません。引き続きWorkerが所有されます。これにより、メモリ割り当てを減らすことができています。

ただし、より長い時間Tensorを使用したい場合は、worker.Fetch()を呼びます。これを使わない場合は、worker.Execute()への次の呼び出し後、またはworker.Dispose()への呼び出し後にTensorの値が失われます。

9. クリーンアップ

Barracudaクライアントは、Worker、作成した入力と出力、worker.Fetch()などで所有権を取得したTensorを破棄する責任があります。これは、GPUリソースを適切に解放するために必要になります。

O.Dispose();
worker.Dispose();

worker.PeekOutput()で受け取ったTensorを破棄する必要はありません。

10. Tensorの操作

「Barracuda」のTensor値は、NHWCまたはchannels-lastとしても知られる「batch」「height」「width」「channels」を介してアクセスします。
多次元配列演算子を介してTensorデータとやり取りできます。

var tensor = new Tensor(batchCount, height, width, channelCount);
tensor[n, y, x, c] = 1.0f; // as N batches of 3 dimensional data: N x {X, Y, C}
tensor[n,       c] = 2.0f; // as N batches of 1 dimensional data: N x {C}
tensor[         i] = 3.0f; // as flat array

さまざまなシナリオをカバーする多くのTensorコンストラクタがあります。
デフォルトでは、初期化配列が提供されない限り、構築時にテンソルは0で初期化されます。

tensor = new Tensor(batchCount, height, width, channelCount);    // batch of 3 dimensional data, 0 initialized: batchCount x {height, width, channelCount}
tensor = new Tensor(batchCount, elementCount);                   // batch of 1 dimensional data, 0 initialized: batchCount x {elementCount}

var stridedArray = new float[batchCount * elementCount] { ... };
tensor = new Tensor(batchCount, elementCount, stridedArray);     // batch of 1 dimensional data, initialized from strided array

var jaggedArray = new float[batchCount][elementCount] { ... };
tensor = new Tensor(batchCount, elementCount, jaggedArray);      // batch of 1 dimensional data, initialized from jagged array

Texture2D texture = ...;
tensor = new Tensor(texture);                                    // tensor initialized with texture data: 1 x { texture.width, texture.height, 3}

Tensorオブジェクトの形状を照会できますが、変更することはできません。
Tensorの形状は不変です。Tensorの形状を変えたい場合は、Tensorオブジェクトの新しいインスタンスを作成する必要があります。

var shape = tensor.shape;
Debug.Log(shape + " or " + shape.batch + shape.height + shape.width + shape.channels);

11. テクスチャの入力

CPU上の個々のピクセルにアクセスせずに、「Texture2D」「Texture2DArray」「Texture3D」「RenderTexture」を「Barracuda」に直接渡すことができます。

var channelCount = 3; // you can treat input pixels as 1 (grayscale), 3 (color) or 4 (color with alpha) channels
var tensor = new Tensor(texture, channelCount);

複数のテクスチャを単一のTensorオブジェクトにバッチできます。

var textures = new [] { texture0, texture1, texture2, texture3 }; // these textures will form a batch
var tensor = new Tensor(textures, channelCount);

バッチを形成するには、すべてのテクスチャの幅と高さの寸法が同じである必要があることに注意してください。

12. テクスチャの出力

グラフィックパイプラインで「Barracuda」の実行結果をさらに使用する場合、CPUまたはGPUをストールさせることなく、「Tensor」から「RenderTexture」にデータをコピーできます。

var tensor = worker.PeekOutput();
var texture = BarracudaTextureUtils.TensorToRenderTexture(tensor);

必要に応じて、同じ「RenderTexture」を複数回再利用できます。

var texture = new RenderTexture(width, height, 0);
// ...
var tensor = worker.PeekOutput();
BarracudaTextureUtils.TensorToRenderTexture(tensor, texture);

13. Barracudaモデルの内観

Barracudaモデルのメモリ表現は非常に単純です。モデルがロードされると、入力と出力を照会できます。

string[] inputNames = model.inputs;   // query model inputs
string[] outputNames = model.outputs; // query model outputs

または、レイヤーを直接反復して、どのモデルが実行されるかを調査できます。

foreach (var layer in model.layers)
    Debug.Log(layer.name + " does " + layer.type);

14. Verboseモード

Barracudaのさまざまな部分でVerboseモードをオンにできます。

bool verbose = true;
var model = ModelLoader.LoadModel(onnxAsset, verbose); // verbose loader
var worker = BarracudaWorkerFactory.CreateWorker(BarracudaWorkerFactory.Type.ComputePrecompiled, model, verbose); // verbose execution

15. TensorFlowモデルのBarracuda形式への変換

「Barracuda」には、学習済みモデル(.pb)を変換する専用のPythonスクリプトが付属しています。

TensorFlow 1.xでは、「freeze_graph」を使用します。SavedModelの例は次のとおりです(その他の例はこちら)。

from tensorflow.python.tools import freeze_graph
freeze_graph.freeze_graph(None, None, <input_is_binary>, None, None
                         <output_name>, None, None, <output_path>, False,
                         clear_devices, None, None, None, False, False,
                         <saved_model_dir>, tag_constants.SERVING)

TensorFlow 2.xでは、「convert_to_constants」を使用します。 Kerasモデルの例は次のとおりです(その他の例はこちら)。

from tensorflow.python.framework import convert_to_constants
@tf.function(input_signature=[tf.TensorSpec(shape=[<input_shape>], dtype=tf.float32)])
def to_save(x):
   return model(x)
f = to_save.get_concrete_function()
constantGraph = convert_to_constants.convert_variables_to_constants_v2(f)
tf.io.write_graph(constantGraph.graph.as_graph_def(), <output_dir>, <output_file>)

constant graphをBarracudaに変換する例は次のとおりです。

python tensorflow_to_barracuda.py Models/3DBall-tf-model.pb Destination/3DBall-bc.nn

ONNXのレガシーコンバーターを次に示します。

python onnx_to_barracuda.py Models/mnist/model.onnx Destination/mnist-bc.nn

ネットワークに複数の出力があるが、推論中に特定の出力のみが必要な場合、未使用の出力と計算を削除するオプション「-trim」があります。

python tensorflow_to_barracuda.py Models/3DBall-tf-model.pb Destination/3DBall-bc.bytes -trim action$

最初に、trimは正規表現パターンに一致しない出力をグラフから削除します。2番目のtrimは、出力の評価に関与しないすべてのノードを取り除きます。上記の例では、アクションで終わる出力のみが残されます。

--print-supported-opsを渡すと、特定のコンバーターでサポートされている操作/アクティベーションのおおよそのリストを取得できます。

【注】Python 3.5または3.6が推奨されます。
【注】将来TensorflowコンバーターをPythonからC#に移行する予定です

16. BarracudaがサポートするONNXオペレーション

◎ Operations

Add
Sum
Sub
Mul
Div
Pow
Min
Max
Mean
AveragePool
MaxPool
GlobalAveragePool
GlobalMaxPool
Upsample
Gemm
MatMul
Conv
ConvTranspose
BatchNormalization
InstanceNormalization
Greater
Less
Equal
Or
And
Not
Xor
Pad
Constant
Identity
Cast
Dropout
Reshape
Unsqueeze
Squeeze
Flatten
Concat
Slice
【注】これらの操作の一部は限定的なサポートの下にあります。

◎ Activations

Softmax
Tanh
Sigmoid
Elu
LeakyRelu
Selu

17. Barracuda Script ConverterがサポートするTensorFlowノード

◎ Operations

Add
AvgPool
BatchNormalization
BatchNormalizationRuntime
BiasAdd
Ceil
Concat
Conv2D
Conv2DBackpropInput
Dense
DepthwiseConv2dNative
Exp
Flatten
Floor
FusedBatchNorm
GlobalAveragePool
GlobalAvgPool
InstanceNormalization
MatMul
Max
MaxPool
Maximum
Mean
Min
Minimum
Mul
Multinomial
Nop
Neg
OneHot
Pad
Pow
Prod
RandomStandardNormal
RandomUniform
RealDiv
Reshape
ResizeBicubic
ResizeBilinear
ResizeNearestNeighbor
StridedSlice
Sqrt
Sub
Sum

◎ Activations

Elu
LeakyRelu
Linear
Log
LogSoftmax
Relu
Relu6
Selu
Sigmoid
Softmax
Softplus
Softsign
Swish
【注】これらの操作の一部は限定的なサポートの下にあります。

【おまけ】 GPUを使うべきかどうかの判断基準

(1) Visual Observation
(2) 大きなVector Observation(エージェト数*Observationサイズ > 1024)
(3) 大きな隠れ層(エージェント数*隠れ層数 > 1024)


この記事が気に入ったらサポートをしてみませんか?