この記事について
前回は、既に用意したncnn用モデル(.param, .bin) を使用しました。
今回はこのncnn用モデルを作ります。
ncnnの方で有名どころのフレームワークからの変換ツールは用意してくれています。ただし、ソースコード形式なのでまずはビルドをします。
その後、Keras -> ONNX -> ncnn と変換してみます。
変換ツールは https://github.com/Tencent/ncnn/tree/master/tools
- 今の時点で用意されているのは以下:
- caffe
- mxnet
- onnx
-
tensorflow(2日前(2019/05/03)に消えてた。開発者さんが諦めた模様)
- 以下のフレームワークは、有志の作っているツールを使うか一度ONNX形式に変換する方法が提示されている
- pytorch
- darknet
ビルド済みツールバイナリ
ビルドするのが面倒な方は以下をご利用ください。
https://github.com/take-iwiw/NcnnMultiPlatformProject/tree/master/ExternalLibs/ncnn_prebuilt/tools
ncnnモデル変換ツールのビルド
ソースコードの取得
変換ツールをビルドするためにはprotobufが必要なので、まずはそちらのビルド、(場合によってはインストール)を行います。
変換ツールのソースコードは前回取得済みのncnnリポジトリ内にあるのですが、一応再取得するところからやります。
以下、Windows, Linux共通のソース取得とバージョン指定(2019/05/05現在で最新のリリースバージョン)です。
cd ~/ # Windowsの場合は適当なところで実施。以後の説明では'C:\work\protobuf'とする
git clone https://github.com/protocolbuffers/protobuf.git
cd protobuf
git checkout v3.7.1
cd ~/ # Windowsの場合は適当なところで実施。以後の説明では'C:\work\ncnn'とする
git clone https://github.com/Tencent/ncnn.git
cd ncnn
git checkout 20190320
Windows
Protobufのビルド
cmake-guiを起動し、以下のように設定します。(パスはどこでもいいです)(設定項目を出すために、パス設定後に一度Configureをクリックしてください。その時のワーニングは無視して大丈夫です)
-
Where is the source code:
:C:/work/protobuf/cmake
-
Where to build the binaries:
:C:/work/build_protobuf
-
CMAKE_INSTALL_PREFIX:
:./install
-
protobuf_BUILD_TEST:
: チェックを外す
その後、Configure, Generateします。
生成されたprotobuf.sln
をVisual Studioで開き、ALL_BUILD->右クリック->ビルド、と、INSTALL->右クリック->ビルド をします。念のためReleaseとDebugの両方でやります。
C:\work\build_protobuf\install
に必要なファイルが生成されました。
ncnnモデル変換ツールのビルド
cmake-guiを起動し、以下のように設定します。(パスはどこでもいいです)(設定項目を出すために、パス設定後に一度Configureをクリックしてください。その時のワーニングは無視して大丈夫です)
Protobuf用の設定をするために、cmake-gui上のAdvanced
をチェックしてください。
-
Where is the source code:
:C:/work/ncnn
-
Where to build the binaries:
:C:/work/build_ncnn
-
Protobuf_LITE_LIBRARY_DEBUG:
:C:/work/build_protobuf/install/lib/libprotobuf-lited.lib
-
Protobuf_INCLUDE_DIR:
:C:\work\build_protobuf\install\include
-
Protobuf_LITE_LIBRARY_RELEASE:
:C:/work/build_protobuf/install/lib/libprotobuf-lite.lib
-
Protobuf_LIBRARY_RELEASE:
:C:\work\build_protobuf\install\lib\libprotobuf.lib
-
Protobuf_PROTOC_EXECUTABLE:
:C:/work/build_protobuf/install/bin/protoc.exe
-
Protobuf_PROTOC_LIBRARY_DEBUG:
:C:/work/build_protobuf/install/lib/libprotocd.lib
-
Protobuf_PROTOC_LIBRARY_RELEASE:
:C:/work/build_protobuf/install/lib/libprotoc.lib
-
Protobuf_LIBRARY_DEBUG:
:C:\work\build_protobuf\install\lib\libprotobufd.lib
その後、Configure, Generateします。
生成されたncnn.sln
をVisual Studioで開き、caffe2ncnn
、mxnet2ncnn
、onnx2ncnn
、ncnn2mem
で右クリック->ビルドします。バイナリ実行形式でツールとして確保しておきたい場合は、Releaseビルドで良いと思います。
onnx2ncnnのビルドで、LNK2038エラー(RuntimeLibraryの不一致)が発生するかもしれません。その時は、onnx2ncnn->右クリック->C/C++->コード生成->ランタイムライブラリの設定を/MT(Releaseの場合)、/MTd(Debugの場合)に変更してください。
build_ncnn\tools
下にそれぞれフォルダが作られて、バイナリが生成されます。
Debugバージョンは必要?
「ツール」なのだからReleaseバージョンだけで良いと思っていたのですが、結構デバッグします。変換がうまくいかないときなど、何が悪いのかを探す必要が出てきます。
とりあえずReleaseビルドしたバイナリツール群をどこかにまとめて、普段はそっちを使えばいいと思います。デバッグが出来るVSプロジェクトも用意しておいて、いざというときにデバッグ出来ればいいと思います。
Linux
続いて、Linux(Ubuntu)でのビルド方法です。
上述のコード取得とバージョン指定が完了しているものとします。
Protobufは/usr/libにインストールしてしまいました。そのためncnnビルド時のprotobufディレクトリの指定は不要です。(最初cmake時に指定する方法でやろうとしたが、うまくいかなかった)
なお、VirtualBoxの場合だと、Windowsとの共有フォルダで作業するとうまくいかないかもしれません。
cd ~/protobuf
mkdir build && cd build
cmake ../cmake -Dprotobuf_BUILD_TESTS=OFF
make -j2
sudo make install
sudo ldconfig
cd ~/ncnn
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j2
ls tools
生成物
- onnx2ncnn.exe : ONNX形式モデル(.onnx)をNCNN形式モデル(.param, .bin)に変換する
- caffe2ncnn.exe : Caffe形式モデルをNCNN形式モデル(.param, .bin)に変換する
- mxnet2ncnn.exe : MXNet形式モデルをNCNN形式モデル(.param, .bin)に変換する
tensorflow2ncnn.exe : TensorFlow形式モデル(.pb)をNCNN形式モデル(.param, .bin)に変換する- ncnn2mem.exe : NCNN形式モデル(.param, .bin)をC言語の配列形式に変換する(コード埋め込み用)
使い方はいずれも、ツールの引数に変換元のモデル名を設定するだけです。
例: onnx2ncnn.exe mobilenetv2.onnx
Keras->ONNX->NCNNへの変換
ONNXの理想に基づけば、ONNX->NCNNへの変換が出来ればどのようなフレームワークのモデルもNCNN形式へ変換が可能となります。そのため、ONNX->NCNNへの変換をやります。
ただし、元になるONNXモデルが必要なので、まずはKeras->ONNXへの変換をします。
(ちなみに、Keras2NCNN
というツールも存在はしましたが、あまり開発はされていないようです。)
以後、Keras形式のMobilenetモデルを変換していきます。
変換する
keras2onnx.convert_keras
や、nnxmltools.convert_keras
、一度Tensorflow形式にしてからのtf2onnx.convert
など色々試しましたが、MMdnn
を使うのが一番うまくいきました。
これ以外の方法だと変換自体は出来ても、余計なレイヤが残ってしまい、onnx2ncnnするときに「xxx not supported yet!」エラーが出まくります。
pip install mmdnn # mmdnnインストール
mmdownload -f keras -n mobilenet # Keras形式のMobilenet取得
mmconvert -sf keras -iw imagenet_mobilenet.h5 -df onnx -om imagenet_mobilenet.onnx # Keras -> ONNX
onnx2ncnn.exe imagenet_mobilenet.onnx # ONNX -> NCNN
# ncnn.bin と ncnn.paramが出来るので適当にRenameする
備考
- ncnnのwikiには
python -m onnxsim 元モデル.onnx スリム化モデル.onnx
でstrip的なことをした方が良いと書いてましたが、MMdnnの場合には不要でした-
onnx.utils.polish_model
やonnx.optimizer.optimizer
もなくて大丈夫でした。
-
- Mobilenetだと問題なく変換できましたが、MobilenetV2やVGG16など、Dense層(Gemm)があるモデルはonnx2ncnnでうまく変換できませんでした(エラーは出ないが、.paramファイルで一部情報が欠ける)。
- https://github.com/Tencent/ncnn/blob/master/tools/onnx/onnx2ncnn.cpp#L693 を見ると、現時点(2019/05/05)でToDoのようです
- 自作モデルの場合は、Reshapeとかに置き換えればうまくいくかも
- MMdnnで変換する場合には、元モデルは
from tensorflow.python.keras.xxx
で生成したものは使えませんでした。変換時にValueError: Unknown initializer: GlorotUniform
エラーが出ました。from keras.xxx
を使う必要があります。
変換したモデルを使う
>>> model.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, 224, 224, 3) 0
_________________________________________________________________
conv1_pad (ZeroPadding2D) (None, 225, 225, 3) 0
_________________________________________________________________
conv1 (Conv2D) (None, 112, 112, 32) 864
_________________________________________________________________
~略~
dropout (Dropout) (None, 1, 1, 1024) 0
_________________________________________________________________
conv_preds (Conv2D) (None, 1, 1, 1000) 1025000
_________________________________________________________________
act_softmax (Activation) (None, 1, 1, 1000) 0
_________________________________________________________________
reshape_2 (Reshape) (None, 1000) 0
=================================================================
7767517
95 95
Input input_1_orig 0 1 input_1_orig
Permute input_1 1 1 input_1_orig input_1 0=4
Padding conv1_pad 1 1 input_1 conv1_pad 0=0 1=0 2=0 3=0 4=0 5=0.000000
Convolution conv1 1 1 conv1_pad conv1 0=32 1=3 11=3 2=1 12=1 3=2 13=2 4=0 14=0 5=0 6=864
BatchNorm conv1_bn 1 1 conv1 conv1_bn 0=32
~ 略 ~
Flatten global_average_pooling2d_1_flatten 1 1 global_average_pooling2d_1 global_average_pooling2d_1_flatten
Reshape reshape_1 1 1 global_average_pooling2d_1_flatten reshape_1 0=1 1=1 2=1024
Dropout dropout 1 1 reshape_1 dropout
Convolution conv_preds 1 1 dropout conv_preds 0=1000 1=1 11=1 2=1 12=1 3=1 13=1 4=0 14=0 5=1 6=1024000
Softmax act_softmax 1 1 conv_preds act_softmax 0=0 1=1
Reshape reshape_2 1 1 act_softmax reshape_2 0=1000
ncnn.paramから、入出力名を探します。Keras形式のmodel.summary()の名前とほとんど一致しています。
出力はreshape_2
だと分かります。
入力はinput_1_orig
ではなく、input_1
でした。input_1_orig
が何なのかは謎。ONNXに変換した時点で付いているっぽい。
前回のプロジェクト(https://github.com/take-iwiw/NcnnMultiPlatformProject/tree/master/NcnnSimpleProject )にモデルと入出力名を差し替えるだけで動きます。今回もちゃんとオウムを識別してくれました。