Unityでディープラーニングする方法調べた

#2020/7/2追記
Barracudaの記事を書きました
この記事の手法より圧倒的に簡単なので、こっち利用推奨です！！
**[Unity Technologies製推論エンジン Barracudaがスゴイという話](https://qiita.com/highno_RQ/items/478e1145f0eb868c0f2e)**

#2020/5/12追記
Unity製推論エンジンのBarracudaがonnxからモデルを直接読み込めるようになっていたようです。
この記事で書いた程度の内容であればこんな面倒な手順を踏まなくてもいけそうです。
~~Barracudaのテストについては後日実施します(おそらく6月中)~~

※参考
[公式ドキュメント ver0.70](https://docs.unity3d.com/Packages/com.unity.barracuda@0.7/manual/index.html)
[Unity Barracuda - ニューラルネットワーク推論ライブラリ](https://note.com/npaka/n/nf7f76871af8f)


# 概要 
ARで深度推定とかするのにml-agentsだけだと足りなかったので
Unityで学習済みモデルを扱う方法を調べて試してみました。
# 実施内容
学習済みモデルVGG19を利用してUnity内に表示した画像の分類を行う。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440309/dcee8652-6828-bf84-ffd2-8c19f1200709.png)

## モデルの形式

|||
|:--|:--|
|モデル名|VGG-19|
|入力|224×224×3|
| 出力 | 1000 |
| データセット | ImageNet (ILSVRCA2012)  |

## 前処理
1. 8bitで表された画像を0-1範囲に変換　= pixel / 255
2. RGBの各値を平均{ 0.485f, 0.456f, 0.406f } 標準偏差　{ 0.229f, 0.224f, 0.225f }で正規化


# 環境

|||
|:--|:--|
|OS|Windows10 Home|
|CPU|Intel(R) Core(TM) i7-7700K|
| Unity | 2019.1.0f2(64-bit) |
| tensorflow | 1.14.0 |



今回、ライブラリの導入に[NuGet](https://www.nuget.org)を利用しました。
UnityではVisualStudioのNuGetパッケージマネージャーは使えないので以下の2手法を利用します。

1. NugetForUnity Assetの利用
AssetStoreから[Nuget For Unity](https://assetstore.unity.com/packages/tools/utilities/nuget-for-unity-104640)をインストールして利用します。
1. 公式ドキュメント記載の方法
公式ドキュメント 「[NuGetからUnity プロジェクトにパッケージを追加する](https://docs.microsoft.com/ja-jp/visualstudio/cross-platform/unity-scripting-upgrade?view=vs-2019#add-packages-from-nuget-to-a-unity-project)」を参照してください。

どちらの手法でもパッケージ同士の依存関係は手動で特定する必要があります。

参考：
Unity であらゆる C#(.NET) パッケージを使う（例：opencv）
　https://qiita.com/kingyo222/items/11100e8f7be396b98453

# 手法一覧 
1. Onnx形式で学習済みモデルを扱う
    - Onnx Runtime
    - OpenCVSharp
1. C#から直接tensorflowを扱う
    - TensorFlowSharp
2. 今回実施できなかった手法
    - WindowsML
    - ML. Net
    - CoreML
    - IronPython
3. おまけ
    - ml-agents
    - The Unity Inference Engine

# 1. Onnx形式で学習済みモデルを扱う
## 概要
機械学習モデルの標準形式であるOnnx(Open Neural Network Exchange)形式を利用します。
この形式に変換することで、TensorflowやPytorch,Chainerといった
主要な深層学習ライブラリで学習したモデルを色々なライブラリで利用できるようになります。

参考：
ONNX Model Zoo(訓練済みモデルの配布場所)
　https://github.com/onnx/models

## - Onnx Runtime
Onnx形式を扱う事に特化した推論エンジンです。
2018/12/04にOSS(MITライセンス)化されたこともあり、使い所は色々とありそうです。

```  C#
    private InferenceSession session;
    public void LoadModel (string model_name) {
        string model_path = Application.dataPath + @"/MLModel/" + model_name + ".onnx";

        var opitions = new SessionOptions ();
        session = new InferenceSession (model_path);
    }
    public int Inference (Texture2D input) {
        var input_nodes_name = session.InputMetadata.First().Key;
        var input_nodes_dim = session.InputMetadata.First().Value.Dimensions;

        // Texture2Dをモデルの入力に合った形に整形、正規化する。
        var input_floats = GetFloatFromTex2DWithFlip (input);
        var input_tensor = new DenseTensor<float> (input_floats, input_nodes_dim);

        // OnnxRuntimeでの入力形式であるNamedOnnxValueを作成する
        var input_onnx_values = new List<NamedOnnxValue> {
            NamedOnnxValue.CreateFromTensor (input_nodes_name, input_tensor)
        };
    
        // 推論を実行
        var results = session.Run (input_onnx_values);
        var scores = results.First().AsTensor<float>().ToArray ();

        return GetBestScorePos(scores);
    }
```

- 利点
  - モデルの扱いが単純
  - 前処理など数値の扱いが容易
- 欠点
  - 依存関係のあるパッケージの特定に時間がかかる
  - モデルの読み込みが不安定(環境依存？)

総評：
入力・モデルの扱い共に非常に簡単です。
モデルの読み込みが最初の数秒不安定ですが、それ以降は特に問題なく実行できます。

参考：
C#でONNXファイルを利用して手書き数字を認識する方法：
　https://kagasu.hatenablog.com/entry/2019/05/21/162445
onnx runtime 公式 github：
　https://github.com/microsoft/onnxruntime

## - OpenCVSharp
画像処理ライブラリとして有名ですが、Onnxを読み込む機能がサポートされているので使ってみました。

```C#
    private Net model;
    public void LoadModel (string model_name) {
        string model_path = Application.dataPath + @"/MLModel/" + model_name + ".onnx";
        model = Net.ReadNetFromONNX (model_path);
    }
    public int Inference (Texture2D input) {
        // Texture2Dをモデルの入力に合った形に整形する
        Mat img = FixTexture2Input (input);

        // OpenCVSharpでの入力形式であるblobを作成、同時に正規化を行う。
        Scalar mean = new Scalar (0.485f, 0.456f, 0.406f);
        var blob = CvDnn.BlobFromImage (img, 1.0 / 255.0 / 0.225f, 
                                        new Size (input.width, input.height), 
                                        mean, swapRB : true, crop : false);
        model.SetInput (blob);

        // 推論を実行
        Mat scores = model.Forward ();

        return GetBestScorePos(scores);
    }
```

- 利点
  - モデルの扱いが単純
  - Nuget For Unityで簡単にプラグインを導入できる
- 欠点
  - 画像をMat形式で扱うため、指定の前処理を行えないことがある

OpenCVSharpに関してはこちらの記事で今回の問題点を解決してくれてます。
[Unityでディープラーニング推論をする方法（マルチプラットフォーム対応）](https://qiita.com/utibenkei/items/adc7f526d629bdfc41fc)

総評：
読み込みが早く、動作も安定しています。
画像関係のモデルを扱うならばこれだけで十分そうです。

参考：
3Dの姿勢推定のOnnxのモデルでUnityちゃんを動かしてみた
　https://qiita.com/yukihiko_a/items/386e3a86a5e523757707

# 2. Unityから直接tensorflowを扱う
## TensorFlowSharp
C#から直接Tensorflowを利用するためのライブラリです。
といっても学習済みモデルの利用が主な機能なので実質推論環境みたいなものです。

```C#

    private TFGraph model;
    public void LoadModel(string model_name)
    {
        string model_path = Application.dataPath + @"/MLModel/" + model_name + ".pb";

        var model_input = File.ReadAllBytes(model_path);
        model = new TFGraph();
        model.Import(model_input);
        
        loaded = true;
    }
    private bool loaded = false;
    public bool GetLoaded() { return loaded; }

    public int Inference(Texture2D input)
    {
        var session = new TFSession(model);

        // Texture2Dをモデルの入力に合った形に整形、正規化する。        
        var float_values = GetFloatFromTex2DWithFlip(input);

        // TensorFlowSharpでの入力形式であるTFTensorを作成する
        var shape = new TFShape(1, input.width, input.height, 3);
        var input_tensor = TFTensor.FromBuffer(shape, float_values, 0, float_values.Length);

        //  データの入力・推論
        //  input_2:0およびoutput_node0:0はpbファイル作成時につけた入力ノードと出力ノードの名前。
        var runner = session.GetRunner();
        runner.AddInput(model["input"][0], input_tensor);
        runner.Fetch(model["content_vgg/prob"][0]);

        // 推論を実行
        var output = runner.Run();
        var scores = ((float[][])output[0].GetValue(true))[0];

        return GetBestScorePos(scores);
    }

```
- 利点
  - TensorFlowでサポートされているモデルを全て実行できる(onnxでは一部のパーツが非対応)
  - 参考になるドキュメントが多い
  - 前処理など数値の扱いが容易
- 欠点
  - 推論が非常に遅い(OnnxRuntime 0.2[sec]に対し16.8[sec]) 
  - Python・TensorFlowが使えることが前提となる
  - モデルの保存・読み込み方法が複雑

総評：
バージョン変更によるAPIの変化やpbファイル(TensorFlowのモデル出力形式)の扱いづらさに加え、
推論速度も今回試した手法の中で圧倒的に遅いです。
Unityでディープラーニングをしたいだけなら他を選んだ方がよいと思います。
TensorFlowを使い慣れていて、複雑なモデルを利用したい場合には使えるかもしれません。

参考：
C#でTensorFlowのCNNを動かす。
　https://qiita.com/Tama_maru/items/6e50edfd8f8dea184d18 
Unityで学習済みのVGG16による1000クラスの画像識別
　https://qiita.com/dipmizu/items/e46591bd4ab03b02ad38

# 3. 今回実施できなかった手法
自分の能力不足や環境の問題で扱えなかった手法です。

## - WindowsML 
Windows 10に搭載されている機械学習のための実行環境です。
Onnx形式をUWP(Universal Windows Platform)アプリで使うのに最適化されています。

- 利点
  - パッケージの導入を必要としない
  - 参考となるドキュメントがやや多い
- 欠点
  - テストの度にビルドしなくてはならない
  - 非同期処理が基本となっており、扱いにある程度知識が必要

総評：
一応試してはみましたが、UWPアプリが良くわからなかったので一旦保留にしました。
HoloLens界隈でよく使われているようです。
非同期処理が分かるようになったらまた挑戦します。

参考：
公式GitHub
　https://github.com/Microsoft/Windows-Machine-Learning/tree/master
HoloLensでWindowsMLを試してみる(Unity利用編)
　http://akihiro-document.azurewebsites.net/post/hololens_windowsmachinelearningunity/

## - ML. Net
C#上で機械学習を行うためのフレームワークです。
モデル構築・訓練まで行えるので、Unityでの入力で直接学習を進めたりできそうです。
今回はランタイムエラーがどうしても解決できなかったため未実施です。
ネット上のドキュメントも少ないのでしばらくは保留にすると思います。

参考：
公式ドキュメント・チュートリアル
　https://docs.microsoft.com/ja-jp/dotnet/machine-learning/

## - CoreML
WindowsMLを紹介したのでこちらも一応。
iOS向けの推論環境で、こちらはOnnxを専用形式に変換してから利用します。
今回はメイン環境がWindowsだったので未実施です。
一応実施例が一つだけありましたが、中でswiftを使っているみたいなので
素のUnityでCoreMLが使えるのかどうかは不明です。
(少なくともNugetにはCoreMLはありませんでした)

参考：
UnityVision-iOS
　https://github.com/possiblecee/UnityVision-iOS

## - IronPython
Unity上でpythonのコードを扱えるようになるライブラリです。
python3.x系がサポートされておらず、自分の環境ではtensorflowが動かないので諦めました。
macやLinuxを使っていて、どうしてもPythonしか書きたくない場合には使えるかもしれません。
(Unityでそんな状況が起こるのかは知りませんが)

参考：
公式ページ
　https://ironpython.net

# 4. おまけ
## - ML-Agents Toolkit
Unity Technorogyが公式に提供している機械学習用アセットです。
機械学習用アセットとは言うものの、サポートしている機能は強化学習だけなので
画像や音声、文章を扱うモデルには適用できません。
大変面白い機能ですが、今回の目的とは合わないのでおまけ扱いに。  

参考：
公式ドキュメント
ML-Agents Installation
　https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Installation.md
Basic Guide
　https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Basic-Guide.md

## - The Unity Inference Engine(Barracuda)
ml-agents v0.7から導入されたUnity Labs謹製推論エンジンです。
Unityに対応する全てのプラットフォームで推論できる事を目的に開発されているようですが、
現段階ではml-agentsを経由しないと利用できない様子です。 
(多分そんなことはないのだけれど、コードを読んでもよくわかりませんでした)
将来的には独立したUnityパッケージとして
他の深層学習アプリケーションにも対応する予定らしいので、
リリースされたらまた調べてみようと思います。

参考：
公式ブログの該当記事
　https://blogs.unity3d.com/jp/2019/03/01/unity-ml-agents-toolkit-v0-7-a-leap-towards-cross-platform-inference/


# おわりに
あまり時間をかけすぎるのも効率が悪いので一旦打ち切りましたが、
WindowsMLやCoreMLあたりはベースになる言語を勉強した上で再挑戦したいと思います。

# 今回の内容(GitHubにあげました)
プロジェクトファイル
　https://github.com/highnoRQ/Unity_ML_Test

Assets/MLModel下に以下のモデルファイルを置いてください
VGG19　tensorflow pbファイル
　https://drive.google.com/file/d/10XFhmr43zIZD4dPKP55hUg9YxKUZ3cBV/view?usp=sharing
VGG19 onnxファイル
　https://github.com/onnx/models/tree/master/vision/classification/vgg

また、ビルドしてテストする場合。
ビルド済みフォルダのDataフォルダにMLModelフォルダごと移してください。