OKI「AE2100」向け OpenVINO Model Optimizerの使い方 (画像分類モデル編)

<font color="Red">**※この記事はAE2100のUbuntu + OpenVINOコンテナ(ubuntu_openvino_2020R3.tar)を対象としています。**</font>

# 要約

この記事では、画像分類のディープラーニング・モデルに対してModel Optimizerを適用する方法を説明します。
また、Model Optimizerによる変換後のモデルをAE2100のUbuntu + OpenVINOコンテナへデプロイし、推論を実行します。

# はじめに

OKIのAIエッジコンピュータ「AE2100」には、Intel製のディープラーニング推論用ハードウェア・アクセラレータであるMovidius Myriad X VPUが搭載されています。
またAE2100のUbuntu + OpenVINOコンテナ`ubuntu_openvino_2020R3.tar`には、ディープラーニングの推論環境としてOpenVINOがインストールされています。

この記事ではOpenVINOのModel Optimizerを利用してディープラーニング・モデルの形式を変換し、変換後のモデルによる推論をAE2100上で実行する方法について説明します。

# Model Optimizerとは

AE2100上でOpenVINOを利用した推論を実行するにあたり、モデルをIntermediate Representation (IR)という形式に変換します。
この変換を行うツールがModel Optimizerです。

[Model Optimizer Developer Guide - OpenVINO™ Toolkit](https://docs.openvinotoolkit.org/2020.3/_docs_MO_DG_Deep_Learning_Model_Optimizer_DevGuide.html) 

例えば各種フレームワーク(TensorFlow、PyTorch、Caffe2、Chainer、MXNetなど)を使用してご自身で訓練を行ったモデルや、webから入手した訓練済みモデルを用いて推論を行うために、各種形式のモデルをIRへ変換します。

また、Model Optimizerによる変換を行う際、IRのデータ型を指定できます。
例えばMyriad X VPUは演算精度としてFP16に対応していますが、FP32には非対応です。
したがって、変換前のモデルのデータ型がFP32の場合、AE2100でMyriad X VPUを使用した推論を行うためには、IRのデータ型としてFP16を指定する必要があります。
この場合、重みやバイアスはFP32からFP16へ量子化されます。

# 推論実行までの手順

開発環境上でのModel Optimizerの実行からAE2100上での推論実行までの大まかな手順は、下記の通りです。

1. モデルを用意する(自身でモデルを訓練する、webで公開されているモデルを取得するなど)
2. Model Optimizerのパラメータを確認する
3. Model Optimizerを実行してモデルをIRへ変換する
4. 推論用アプリケーションを用意する
5. アプリケーションとIRをAE2100のコンテナへデプロイする
6. アプリケーションの実行により推論を実行する

また下図では、上記手順におけるAE2100、OpenVINO、モデルの関係を表しています。
IRを用いた推論には、OpenVINOに含まれるライブラリ群であるInference Engineを利用します。
また、Inference Engineを介してVPUなどのIntel製デバイスを利用した推論を行います。

![openvino-mo-ae2100.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1371094/40523254-c7da-a9ad-2cbc-4fa244e694ae.png)

以降は上記の手順に沿ってModel Optimizerを使用方法を説明します。

# 画像分類モデルへのModel Optimizer使用方法

## 事前準備

Ubuntu開発環境上にOpenVINOおよびModel Optimizerのインストールを行ってください。

なおAVX命令に非対応のプロセッサー上でModel Optimizerを使用したTensorFlowモデルの変換を行うと、エラー「core dumped (Illegal instruction)」 が発生します。
開発環境として、Intel製プロセッサーを用いる場合にはCore i もしくはXeon系のプロセッサーを搭載したPC等をご用意ください。

詳細は「AE2100 シリーズ SDK 取扱説明書 ―DeepLearning 編―」(版数: 1.2) p. 10をご参照ください。

## モデルの用意

開発環境上でモデルを用意します。

ディープラーニング・モデルのファイル形式は、各種フレームワークで出力可能な形式が様々にあります。
このうちOpen Neural Network Exchange (ONNX)は、様々なフレームワーク間の互換性をサポートするモデル形式です。

今回はONNX Model Zooから訓練済み画像分類モデル(`resnet50-v1-7.onnx`)を取得し、AE2100上で画像分類を行います。

[ONNX Model Zoo](https://github.com/onnx/models)

ホームディレクトリ直下にモデルとアプリケーションの保存用ディレクトリを作成し、移動します。
また、`wget`コマンドを用いてwebからモデルファイルをダウンロードし、`tar`コマンドで解凍します。

``` console
# cd
# mkdir mo_classification
# cd mo_classification
# wget https://media.githubusercontent.com/media/onnx/models/master/vision/classification/resnet/model/resnet50-v1-7.onnx
```

上記コマンドを実行すると、モデルファイル`resnet50-v1-7.onnx`が得られます。
以降ではこの`resnet50-v1-7.onnx`に対してModel Optimizerを適用します。

## Model Optimizerのパラメータ確認

Model Optimizerを実行する際、モデルの入出力ノードの名前や、入力の形状、入力の平均値、入力のスケール値などのパラメータを引数として指定します。
モデルの訓練をご自身で行った場合にはこれらのパラメータがわかりますが、webからモデルを入手した場合にはパラメータを調べる必要があります。

ここでは取得したモデルの入手元のドキュメントを参照し、パラメータを調べます。

[models/vision/classification/resnet at master · onnx/models · GitHub](https://github.com/onnx/models/tree/master/vision/classification/resnet)

上記のドキュメントには、モデルへの入力は3チャネルRGB画像のミニバッチであり、形状は(N x 3 x H x W)であるという記載があります。
また、Nはバッチサイズ、HとWは少なくとも224です。
したがって、Model Optimizerのパラメータとしての入力の形状[1,3,224,224]が得られます。

また、上記のドキュメントには、モデルへ画像を入力する前に行う前処理についても説明があります。
具体的には、画素値の範囲を[0, 1]として画像を読み込んだ上で、RGBについて平均値[0.485, 0.456, 0.406]、標準偏差[0.229, 0.224, 0.225]の標準化を行うという記載があります。

IRへ変換後のモデルで推論を行う際にも、モデルへ前述の通りの標準化を行うべきです。
しかしながら、後述するサンプルアプリケーションでは標準化が実装されていません。
そこでModel Optimizerのパラメータとして、平均値および標準偏差(スケール値)を与えることで、IRに標準化を取り入れます。

後述するサンプルアプリケーションでは、画素値の範囲を[0, 255]として画像を読み込みます。
画素値の範囲は前述の通り[0, 1]へ変換されているため、前述の平均値に255を乗じることで、Model Optimizerのパラメータとしての平均値[123.675‬, 116.28‬, 103.53]が得られます。
同様に前述の標準偏差に255を乗じることで、Model Optimizerのパラメータとしての標準偏差[58.395, 57.12, 57.375]が得られます。

なお今回のモデル`resnet50-v1-7.onnx`に対しては入出力ノードの名前をModel Optimizerの引数として指定する必要はありませんが、入出力ノードの名前を調べるにはモデルビューアを使用する等の方法があります。

以上でModel Optimizerのパラメータを得ることができました。

## Model Optimizerの実行

開発環境にてModel Optimizerを実行し、モデルファイル`resnet50-v1-7.onnx`をIRへ変換します。

Model OptimizerはPythonにより実行します。Pythonの仮想環境を起動します。

``` console
# cd 
# cd ./mo_classification
# source /opt/intel/openvino/deployment_tools/model_optimizer/venv/bin/activate
```

下記のコマンドで`/opt/intel/openvino/deployment_tools/model_optimizer/mo.py`を実行し、モデルファイル`resnet50-v1-7.onnx`をIRへ変換します。

``` console
# python3 /opt/intel/openvino/deployment_tools/model_optimizer/mo.py \
--framework onnx \
--input_model resnet50-v1-7.onnx \
--data_type FP16 \
--input_shape [1,3,224,224] \
--mean_values [123.675,116.28,103.53] \
--scale_values [58.395,57.12,57.375] \
--reverse_input_channels
```

下記の表にパラメータ引数の説明を示します。

| 引数 | 説明 |
| --- | --- |
| `--framework {tf,caffe,mxnet,kaldi,onnx}` | 訓練済みモデルのフレームワーク名 |
| `--input_model INPUT_MODEL` | 変換対象のモデルファイル |
| `--data_type {FP16,FP32,half,float}` | IRのデータ型 |
| `--input INPUT` | 入力ノード名またはそのリスト |
| `--input_shape INPUT_SHAPE` | 入力データの形状 |
| `--mean_values MEAN_VALUES` | 入力画像のチャネルごとの平均値 |
| `--scale SCALE` | 入力画像の全チャネル共通のスケール値 |
| `--scale_values SCALE_VALUES` | 入力画像のチャネルごとのスケール値 |
| `--output OUTPUT` | 出力オペレーション名|
| `--reverse_input_channels` | 入力チャネルの並びをRGBからBGRへ(またはその逆へ)入れ替える |

[Converting a Model Using General Conversion Parameters - OpenVINO™ Toolkit](https://docs.openvinotoolkit.org/2020.3/_docs_MO_DG_prepare_model_convert_model_Converting_Model_General.html)


IRへの変換対象となるモデルの構造にあわせてこれらの引数を変更します。
今回の変換では下記のように引数を指定します。

- `--framework onnx`: ONNX形式のモデルを変換するため、フレームワーク名として`onnx`を指定
- `--input_model resnet50-v1-7.onnx`: モデルファイル`resnet50-v1-7.onnx`を指定
- `--data_type FP16`: Myriad X VPUで推論を行うため、データ型としてFP16を指定
- `--input_shape [1,3,224,224]`: 入力の形状`[1,3,224,224]`を指定
- `--mean_values [123.675,116.28,103.53]`: 入力画像のチャネルごとの平均値`[123.675,116.28,103.53]`を指定
- `--scale_values [58.395,57.12,57.375]`: 入力画像のチャネルごとの標準偏差`[58.395,57.12,57.375]`を指定
- `--reverse_input_channels`: 次節でビルドするサンプルアプリケーションでの画像読込に合わせて、入力チャネルの並びをRGBからBGRへ入替

Model Optimizerの実行が成功すると、IRとして`.xml`形式のファイルと`.bin`形式のファイルが得られます。
今回の変換では`resnet50-v1-7.onnx`の変換後のIRとして下記のファイルが得られます。

- `resnet50-v1-7.xml`
- `resnet50-v1-7.bin`

## サンプルアプリケーションのビルド

IRを利用した推論を行うアプリケーションを開発環境上に用意します。

今回はOpenVINOに付属しているInference Engine SamplesのC++アプリケーションを一括ビルドし、画像分類用のサンプルアプリケーション(`classification_sample_async`)を使います。

[Image Classification C++ Sample Async - OpenVINO™ Toolkit](https://docs.openvinotoolkit.org/2020.3/_inference_engine_samples_classification_sample_async_README.html)


下記のコマンドでサンプルの保存フォルダへ移動し、ビルドを行います。

``` console
# cd /opt/intel/openvino/inference_engine/samples/cpp
# ./build_samples.sh
```

ビルドが成功すると`~/inference_engine_cpp_samples_build/intel64/Release`下にアプリケーションが保存されます。

## AE2100へのデプロイ

IRに変換したモデルと推論用アプリケーションを、開発環境からAE2100へデプロイします。

開発環境でビルドした画像分類用のサンプルアプリケーション`classification_sample_async`を保存用ディレクトリへコピーします。
また`classification_sample_async`の実行に必要な共用ライブラリ`libformat_reader.so`を保存用ディレクトリへコピーします。

``` console
# cd 
# cd mo_classification
# cp ~/inference_engine_cpp_samples_build/intel64/Release/classification_sample_async .
# cp ~/inference_engine_cpp_samples_build/intel64/Release/lib/libformat_reader.so .
```

また、画像分類の対象とする画像データを用意します。
ここではOpenVINOから提供されている猫の画像とバナナの画像を、下記のコマンドにより保存用ディレクトリへダウンロードします。

``` console
# wget https://storage.openvinotoolkit.org/data/test_data/images/cat.jpg
# wget https://storage.openvinotoolkit.org/data/test_data/images/banana.jpg
```

なお、アプリケーション`classification_sample_async`は、ラベルのテキストファイル(`.labels`)をIRと同じファイル名で配置しておくことで、分類結果のラベル名を表示します。
例えばIRが`resnet50-v1-7.xml`の場合、ラベルのテキストファイルは`resnet50-v1-7.labels`とします。
アプリケーションにて分類結果のラベル名を表示するには、下記コマンドでラベルファイルを取得し、ファイルの名前を変更しておきましょう。

``` console
# wget https://raw.githubusercontent.com/onnx/models/master/vision/classification/synset.txt
# mv synset.txt resnet50-v1-7.labels
```

`ls`コマンドにより、AE2100へデプロイするファイルが`~/mo_classification`に保存されていることを確認します。

``` console
# cd
# ls mo_classification
```

少なくとも下記のファイルが`~/mo_classification`に保存されていることを確認してください。

- `banana.jpg`
- `cat.jpg`
- `classification_sample_async`
- `libformat_reader.so`
- `resnet50-v1-7.xml`
- `resnet50-v1-7.bin`
- `resnet50-v1-7.labels`

ファイルが保存されていることを確認したら`~/mo_classification`を`tar`コマンドで圧縮し、開発環境からAE2100へ送信するファイルの準備は完了となります。

``` console
# cd
# tar czf mo_classification.tar.gz mo_classification
```

作成した圧縮ファイル`mo_classification.tar.gz`をAE2100へ送信してください。
また、圧縮ファイルをUbuntu + OpenVINOコンテナのホームディレクトリへコピーしてください。

## AE2100上での推論実行

以降はコンテナのシェルに入った後の操作になります。

下記コマンドにより、コンテナ内のホームディレクトリにて`mo_classification.tar.gz`を解凍し、ディレクトリ`mo_classification`へ移動します。

``` console
# cd
# tar xzf mo_classification.tar.gz
# cd mo_classification
```

OpenVINOの環境変数を設定します。
また`mo_classification`に保存した共用ライブラリ`libformat_reader.so`を使用するためにパスを設定します。

``` console
# source /opt/intel/openvino/bin/setupvars.sh
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/mo_classification
```

以上でアプリケーション`classification_sample_async`の実行準備が済みました。
それでは`classification_sample_async`を実行し、画像分類の推論をさせてみましょう。

`classification_sample_async`を実行する際は、引数`-i`に推論対象の画像ファイルパス、`-m`にIRのファイルパス(`.xml`)、`-d`に推論デバイス名を指定します。

猫の画像`cat.jpg`を分類するには、下記のコマンドを実行してください。

``` console
# ./classification_sample_async -i cat.jpg -m resnet50-v1-7.xml -d HDDL
```

`classification_sample_async`が正しく動作すると、`cat.jpg`の分類結果(クラスID、スコア、クラスのラベル)が下記のように上位10位まで表示されます。
(今回使用したモデルはSoftmax関数が適用されていないスコアを出力するため、probabilityの列は確率ではなくスコアが表示されています)

``` 
Top 10 results:

Image cat.jpg

classid probability label
------- ----------- -----
281     11.1406250  n02123045 tabby, tabby cat
285     10.4062500  n02124075 Egyptian cat
435     10.0781250  n02808440 bathtub, bathing tub, bath, tub
876     10.0312500  n04493381 tub, vat
896     9.9609375   n04553703 washbasin, handbasin, washbowl, lavabo, wash-hand basin
282     9.7109375   n02123159 tiger cat
904     9.3906250   n04589890 window screen
794     8.9921875   n04209239 shower curtain
728     8.3515625   n03958227 plastic bag
478     8.1484375   n02971356 carton
```

続いてバナナの画像`banana.jpg`に対しても分類を行ってみましょう。

``` console
# ./classification_sample_async -i banana.jpg -m resnet50-v1-7.xml -d HDDL
```

```
Top 10 results:

Image banana.jpg

classid probability label
------- ----------- -----
954     13.2343750  n07753592 banana
114     7.7851562   n01945685 slug
940     6.1835938   n07716906 spaghetti squash
390     6.1406250   n02526121 eel
502     5.9296875   n03047690 clog, geta, patten, sabot
110     5.8789062   n01924916 flatworm, platyhelminth
950     5.4960938   n07747607 orange
942     5.4882812   n07717556 butternut squash
600     5.3945312   n03532672 hook, claw
956     5.3867188   n07760859 custard apple
```

分類結果を見ると、Model Optimizerで変換したIRを用いて妥当な推論結果が得られたといえます。

以上でModel Optimizerを利用して画像分類モデルをIRへ変換し、AE2100上で推論を実行することができました。

# おわりに

今回はOpenVINOのModel Optimizerを利用して画像分類モデルをIRへ変換し、AE2100上で推論を実行しました。
ご自身で訓練した画像分類モデルやwebから取得した訓練済み画像分類モデルをAE2100へデプロイする際には、本記事をご参照ください。

次回は物体検出モデルに対してModel Optimizerを適用する方法について説明する予定です。

# 関連記事

- [OKI AI エッジコンピューター「AE2100」でOpenVINOのサンプルプログラムを動かしてみよう Ubuntuコンテナ版 (2)](https://qiita.com/TWAT/items/e7cd34f8c97f895c39b2)
- [OKI AIエッジコンピューター「AE2100」で動く物体検出プログラムを作ろう (1) ―基礎編―](https://qiita.com/TWAT/items/0cbf6286e921f7c5664e)