日本語 English
1. はじめに
いつも左中間を狙うようなプチニッチなふざけた記事ばかりを量産しています。 この記事の手順を実施すると、 最終的に PyTorch製 高精度Semantic Segmentation の U^2-Net
を TensorFlow Lite へ変換することができます。 下図のような感じです。
TensorFlow めちゃくちゃ扱いにくいです。 日々公開される最新のとても面白いモデルは軒並みPyTorch実装ですし、なんでTensorFlowで実装してくれないんだ!! と、常日頃思っています。 論文のベンチマークの結果から精度やパフォーマンスが出ることがあらかじめ分かっているモデルにも関わらず、いちいちTensorFlowで再実装してトレーニングし直すのはかなり面倒くさいですし不毛です。 また、みなさんが 「TensorFlowは扱いにくい」 と感じてみえるポイントと私が扱いにくいと感じているポイントはズレているかもしれませんが、概ね下記のようなことがネックになっているのではないか、と考えます。
- 他のフレームワークが軒並み
NCHW形式
なのに対し、TensorFlowだけNHWC形式
-
NHWC形式
がデフォルトのせいで他のフレームワークへの転用をしにくい - 同じ処理・同じ名前・異なるインタフェースのオペレーションがあちこちに散在していて超カオス
- 最新のおもしろ実装が比較的少ない (少なく感じる)
- 構文が難しい
でも、良いところもちゃんとあると思います。私が考える良いところは下記です。
- デバイス向けにゴリゴリにモデルチューニングして爆速体験ができる
- TensorFlow.js, TensorRT, TF-TRT, TensorFlow Lite, MediaPipe など、実行環境への最適化の適応幅が広い
- ちゃんと最適化すればモデルの構造がとても美しい
はい、 3. は完全に私の趣味の話ですね。 この記事では TensorFlow のメリットを活かしつつ、デメリットを打ち消すためのノウハウを主に共有したいと思います。 トレーニングは好きなフレームワークで実施し、推論はゴリゴリにチューニングした状態で実行する。 そんなことを目指していきます。
なお、前回書いたモデル変換に関する泥臭いノウハウ集その1はコチラ [Tensorflow Lite] Various Neural Network Model quantization methods for Tensorflow Lite (Weight Quantization, Integer Quantization, Full Integer Quantization, Float16 Quantization, EdgeTPU). As of May 05, 2020. の記事をご覧ください。 タイトルは英語ですが中身は日本語です。 また、必要ないとは思いますが英語版も用意してあります。 [English ver.] [Tensorflow Lite] Various Neural Network Model quantization methods for Tensorflow Lite (Weight Quantization, Integer Quantization, Full Integer Quantization, Float16 Quantization, EdgeTPU). As of May 05, 2020.
2. モデル変換用の様々なツール
フレームワーク間でモデルを変換し、面白いモデルを好きなフレームワーク上で実行できると嬉しいですよね。 現在世に出回っているモデル変換用のツールは下記のようなものがあります。 ONNX最狂説。 今回は、ONNXはPyTorchのモデルをフリーズするための一時的な中継フレームワークとして使用するのみです。 ちなみに、私が作成した雑な変換ツール (openvino2tensorflow) と下記の主要ツール群との大きな違いは、 NCHW形式
を NHWC形式
にストレートに変換しつつ 量子化
まで実施できるところです。
-
onnx-tensorflow
ONNXをTensorFlowへ変換するツール -
tensorflow-onnx
TensorflowをONNXへ変換するツール -
onnx2keras
ONNXをKerasへ変換するツール -
tflite2onnx
TensorFlowLiteをONNXへ変換するツール -
sklearn-onnx
sklearnをONNXへ変換するツール -
onnx-mlir
ONNXをMLIRへ変換するツール -
keras-onnx
KerasをONNXへ変換するツール -
onnx-tensorrt
ONNXをTensorRTへ変換するツール -
onnx-coreml
ONNXをCoreMLへ変換するツール -
onnx-simplifier
ONNXモデル構造の最適化を行うツール -
OpenVINO Deep Learning Deployment Toolkit (DLDT) - Model Optimizer
TensorFlow,ONNX,MXNet,CaffeをOpenVINO IR形式への変換やその他の各種便利ツールキット -
coremltools
各種フレームワークからCoreMLモデルへ変換するツール -
MMdnn
各種フレームワーク間でモデルを変換するツール -
torch2tflite
PyTorch -> ONNX -> TF2/TFLite -
openvino2tensorflow
OpenVINO IRモデルをTensorFlowへ変換するツール onnx-tensorflowを使用すると大量のゴミTransposeが外挿されるため、キレイにNCHWのモデルをNHWCへ変換するためのツールとして作成 -
tflite2tensorflow
tfliteをTensorFlowへ逆変換するツール 100種類以上のOPに対応 -
PINTO_model_zoo
PyTorch(ONNX),Caffe,TensorFlow,TensorflowLite,CoreML,TF-TRT,TFJSのモデルコレクション、モデル変換用スクリプトを大量にコミットしてあります
3. NCHW形式のモデルからNHWC形式のモデルへ変換するツラさ
実際にやってみると分かると思うのですが、前節で挙げた私が作成したツール以外のツール群は、NCHW形式
を NHWC形式
へうまく変換することができません。 できたとしてもゴミのようなTransposeレイヤーが大量に埋め込まれることが多いです。 NHWC形式
から NCHW形式
の方向への変換は概ね対応されています。 NCHW形式をNHWC形式へ綺麗に変換しようとした場合、モデルに記録された重み情報をNumpy配列として抽出し、全てに対してTranspose処理で転置を施す必要がありとても手間が多いです。 私も実際に手作業で何個もモデルをコンバージョンしましたが、とにかくミスが重なります。 巨大なモデル あるいは 構造が複雑なモデル(例えば分岐が異様に多いモデル) を手作業で変換するのは誰でもできる作業ですが、人間ですのでどうしてもミスが発生します。 とにかくツラい。 異常にツラい。
「やったー! 2日かけてようやく変換出来たー!」
↓
「。。。。なんや、コレ。。。ワイ、ゴミ錬成してもうた。。。」
の繰り返しが関の山です。 心が病みます。 PyTorch製 EfficientDet-D0
の手作業による TensorFlow Lite
変換にチャレンジして成功しましたが、モデルの特性上あまりにも離合が多くて吐きそうでした。
NCHW to NHWC変換に関して、実は YoloV4 の論文著者の Alexeyさん と私の間で直接議論を交わしていた経緯があります。 今の所、37回もキャッチボールをしている長命なissueですが、どういう経緯があったか興味がある方は一度覗かれてみると面白いかもしれません。
GitHub issue: Is there an easy way to convert ONNX or PB from (NCHW) to (NHWC)?
4. あえてOpenVINOを経由して変換する理由
この記事では PyTorch
-> ONNX
-> OpenVINO
-> TensorFlow / Tensorflow Lite
の流れでモデルを最適化しながらNCHW to NHWC変換を行います。 ONNXやその他のNCHW形式のフォーマットからTensorFlowのNHWC形式のフォーマットへは一気に変換しません。 端的に言うと、OpenVINO の Model Optimizer がかなり優秀なのであえてワンクッションだけOpenVINOへの変換をワークフローに組み込むということを行います。
OpenVINOへの変換をワンクッション経由することに関して私なりに考えたメリットは下記の5点です。
- OpenVINO の Model Optimizer が変換過程のモデルを勝手に最適化してくれる
- OpenVINO自体が推論専用フレームワークとして推論とフレームワーク間コンバージョンの役目に特化しているため、共通フォーマットとしては洗練されている
- PyTorch(ONNX), TensorFlow, Caffe, MXNet などの豊富なフレームワークの変換に対応している
- 全てのオペレーションの情報とオペレーション間の接続情報が人間が読解可能な簡単なXMLファイルに出力されるため、学習後のモデルの構造をエディタを使用して後から簡単に書き換えることができる
- OpenCVに取り込まれている
5.に関してはメリットとは言いにくいですが、まぁ、そんなところです。
ちなみに、ロシア語の記事ですが ニューロンをコーヒーメーカーに詰め込む方法
という内容の記事のコメント欄で openvino2tensorflow
の有用性を Alexeyさん が解説して紹介してくれています。
Как запихать нейронку в кофеварку - ニューロンをコーヒーメーカーに詰め込む方法
5. モデル構造の美しさ
ディープラーニングモデルを生成するうえで重要な要素は、
1. サイズ
2. 精度
3. 構造の美しさ
です。 ウソです。 ごめんなさい。 美しさを判断要素にあげているのはたぶん私だけだと思います。 私はモデルをコレクションしていますので、作業を続けているうちにいつの間にか 美しさ
を追究要素として加えていることに気づきました。 では、 美しさ
とはどのようなことを指すか。 下図を見ていただくとなんとなく分かって頂けるかもしれません。 見た目だけ、の話ですが、TensorFlow Liteの形式へ変換すると、アクティベーション関数やBatchNormarizationが Convolution にマージされて元のONNXモデルの3分の2ほどのサイズにキレイにまとまっています。
ONNXからTensorFlow Liteへの変換テストに使用させて頂いたモデルは、Digital- Standard Co., Ltd.
さんの3D骨格検出モデル ThreeDPoseUnityBarracuda
のONNXモデルです。 趣味や研究で利用する場合はフリーとして公開いただいていますが、商用利用する場合は 制約事項 がありますので、LICENSE条文をよく読んでご利用ください。
https://digital-standard.com/threedpose/models/Resnet34_3inputs_448x448_20200609.onnx
ONNX | OpenVINO | TFLite |
---|---|---|
6. 変換手順
PyTorch
-> ONNX
-> OpenVINO
-> TensorFlow / Tensorflow Lite
の流れでモデルを変換します。 OSS上でいただいたアドバイスなどをもとに独自のワークフローとして組み入れた手順をご紹介します。
6-1. TensorFlow と OpenVINO のインストール
TensorFlow と OpenVINO をインストールします。 TensorFlow は v2.3.1
以降、 OpenVINO は 2021.1
以降を導入してください。
$ sudo pip3 install tensorflow==2.3.1 --upgrade
OpenVINO はOSによって導入の手順が異なります。 下記に記載の手順に従ってインストールしてください。 なお、この手順でサポートする OpenVINO のバージョンは 2021.1
以降のものです。
-
Linux - 2021.1+
https://docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_linux.html -
Windows - 2021.1+
https://docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_windows.html
6-2. openvino2tensorflow のインストール
OpenVINO IR のモデルを TensorFlow の saved_model や .pbファイル、.tfliteファイル、.h5ファイル へ自動変換することができる私の独自ツール openvino2tensorflow
をインストールします。 下記のコマンドを実行するだけで最新のパッケージをインストールすることができます。 ほぼ毎日のようにバグフィックスと対応レイヤーの増加を行っていますので、作業開始時には毎回実行いただくことをおすすめします。
$ sudo pip3 install openvino2tensorflow --upgrade
【参考】GitHub: https://github.com/PINTO0309/openvino2tensorflow
6-3. PyTorch のインストール
下記のホームページから、ご利用の環境に適したバージョンのインストーラを選択してください。 私の場合は下記の通りの指定を行いました。 注意点はご利用中の CUDA のバージョンを適切に選択すること、ぐらいです。
$ sudo pip3 install torch==1.7.0+cu101 \
torchvision==0.8.1+cu101 torchaudio==0.7.0 \
-f https://download.pytorch.org/whl/torch_stable.html
6-4. ONNX Runtime のインストール
$ sudo pip3 install onnxruntime --upgrade
【参考】GitHub https://github.com/microsoft/onnxruntime
6-5. ONNX Simplifier のインストール
ONNXのモデルを出力したことがある方は感じたことがあるかもしれませんが、ONNXのモデル構造はかなり冗長です。 例えば下図の構造が、
ONNX変換するとこうなります。 うげげげ。。。 美しくない。。。
ココで導入する ONNX Simplifier
にONNXモデルを投入すると、モデル全体の重みのサイズの最適化と構造の最適化を同時に実施してくれます。下図のとおりです。 最適化具合が一目瞭然ですね。
$ sudo pip3 install onnx-simplifier --upgrade
【参考】GitHub: https://github.com/daquexian/onnx-simplifier
6-6. PyTorch -> ONNX 変換
では、ようやく本題のモデル変換の実作業に入っていきます。 この記事ではPyTorchをONNX変換する際の特殊手順をご紹介します。 この手順では、比較的シンプルで高精度なモデル U^2-Net (ユースクエアネット)
を例に変換手順を残したいと思います。 なお TorchScript については触れませんので、気になる方はコチラの記事 TorchScriptを使用してPyTorchのモデルを保存する - Qiita - hirune924さん がとても参考になります。
6-6-1. 変換手順に使用するサンプルリポジトリのClone
高精度Semantic Segmentationのモデル U^2-Net
のPyTorch実装のリポジトリをCloneします。 U^2-NetによるSemantic Segmentationの実行イメージは下図のとおりです。 驚くほど精細でカッコイイですね。
$ git clone https://github.com/NathanUA/U-2-Net.git
$ cd U-2-Net
6-6-2. OpenVINO のmodel_downloaderのバックエンドモジュール pytorch_to_onnx.py を使用してonnxを生成
OpenVINO の付属ツール model_downloader
は各種モデルをダウンロードすると同時に OpenVINO IR へ自動変換してくれるモジュールをバックエンドでコールしてONNXへ変換してくれます。 どのようなものかを調べていくと分かるのですが、単なるPythonスクリプトで提供されていることが分かります。 この特殊手順は model_downloader
がコールしている pytorch_to_onnx.py
を使用してストレートにPyTorchのモデルをONNXへ変換してしまいます。 メリットは超特殊なPyTorchモデルを除き、ほとんどの場合PyTorchプログラムに変更を加えなくてもコマンド一発でお手軽に.pthをONNXに変換できることです。
では、実際にサンプルのPyTorchモデル U^2-Net
を変換してみます。 モデル変換用スクリプト pytorch_to_onnx.py
のパラメータは下記のとおりです。
No. | Parameter | 意味 |
---|---|---|
1 | import-module | モデル構造が記録された.pyファイル名の.pyを除いた部分を指定します。 フォルダ階層の何段か下にモデルファイルがある場合は フォルダ名1.フォルダ名2.u2net というように、"." 区切りでフォルダ名を指定します。 例:カレント階層の u2net.py の場合は**u2net **と指定 |
2 | model-name | No.1 の.pyファイルに記載されているモデルのCLASS名を指定します。 例:class U2NETP(nn.Module): の U2NETP の部分 |
3 | input-shape | モデルへの入力解像度を 空白無しのカンマ区切りのNCHW形式 で指定します。 |
4 | weights | PyTorchの重みが記録された.pthファイルまでの 相対パス あるいは 絶対パス を指定します。 |
5 | output-file | エクスポート後のONNXファイル名を指定します。 |
6 | input-names | モデルの入力変数名を指定します。大抵の場合、 No.1 の forward関数に指定されている引数名で問題ありません。複数の変数を指定する場合は空白無しのカンマ区切りで指定します。 例:"x,y,z"
|
7 | output-names | モデルの出力変数名を指定します。大抵の場合、 No.1 の forward関数のreturn文に指定されている変数名で問題ありません。複数の変数を指定する場合は空白無しのカンマ区切りで指定します。 例:"out1,out2,out3,out4"
|
Cloneしたリポジトリフォルダの直下で下記のようにコマンドを実行します。 |
$ python3 ${INTEL_OPENVINO_DIR}/deployment_tools/tools/model_downloader/pytorch_to_onnx.py \
--import-module model.u2net \
--model-name U2NETP \
--input-shape 1,3,320,320 \
--weights saved_models/u2netp/u2netp.pth \
--output-file u2netp_320x320.onnx \
--input-names "x" \
--output-names "F.sigmoid(d0),F.sigmoid(d1),F.sigmoid(d2),F.sigmoid(d3),F.sigmoid(d4),F.sigmoid(d5),F.sigmoid(d6)"
下図のように ONNX check passed successfully.
と表示されれば成功です。
u2netp_320x320.onnx
がちゃんと生成されていますね。
6-7. ONNXモデルの最適化
先ほど生成したONNXファイルをパラメータに指定して下記のコマンドを実行するだけです。
$ python3 -m onnxsim u2netp_320x320.onnx u2netp_320x320_opt.onnx
めちゃくちゃ最適化されました。 複雑なモデルであればあるほど効果が高くなります。
最適化前 u2netp_320x320.onnx | 最適化後 u2netp_320x320_opt.onnx |
---|---|
【参考】GitHub https://github.com/daquexian/onnx-simplifier |
6-8. ONNX -> OpenVINO IR 変換
では、先ほど最適化して生成した u2netp_320x320_opt.onnx
を入力として、OpenVINO のコンバーターを使用して IR形式へ変換します。 下記のコマンドを実行します。 Caffeのモデルを変換する場合はココから先の手順を実施するだけでよいです。
$ python3 ${INTEL_OPENVINO_DIR}/deployment_tools/model_optimizer/mo.py \
--input_model u2netp_320x320_opt.onnx \
--input_shape [1,3,320,320] \
--output_dir openvino/320x320/FP32 \
--data_type FP32
.bin
.mapping
.xml
の3種類のファイルが生成されました。
model_optimizer
へ指定可能なパラメータは下記をご覧ください。
6-9. OpenVINO IR -> TensorFlow / TensorFlow Lite 変換
いよいよ最後の手順です。 openvino2tensorflow
という私お手製の雑ツールを使用して、 NCHW形式のIRモデルからNHWC形式の TensorFlow / TensorFlow Lite のsaved_modelや.pb、.h5、.tflite を生成します。 下記のコマンドを実行します。
No. | Parameter | 意味 |
---|---|---|
1 | model_path | IRモデルのxmlファイルまでの相対パスあるいは絶対パスを指定します。 xmlファイルとbinファイルは同じフォルダに存在している必要があります。 |
2 | model_output_path | NHWC形式へ変換したあとのモデルファイルの出力先パスを相対パスあるいは絶対パスで指定します。 |
3 | output_saved_model | True 又は False、saved_model形式で出力する場合は True を指定します。 |
4 | output_h5 | True 又は False、.h5 形式で出力する場合は True を指定します。 |
5 | output_weight_and_json | True 又は False、重みとJSONを出力する場合は True を指定します。 |
6 | output_pb | True 又は False、.pb 形式で出力する場合は True を指定します。 |
7 | output_no_quant_float32_tflite | True 又は False、.tfliteのFloat32精度で出力する場合は True を指定します。 |
8 | output_weight_quant_tflite | True 又は False、.tfliteの重みを量子化して出力する場合は True を指定します。 |
9 | output_float16_quant_tflite | True 又は False、tfliteの重みをFloat16量子化して出力する場合は True を指定します。 |
10 | replace_swish_and_hardswish | True 又は False、活性化関数のSwishとHard-Swishを入れ替える場合は True を指定します。 EfficientDetのパフォーマンス検証用です。 |
11 | debug | デバッグモードを有効にします。デバッグプリントで変換途中の特定のレイヤーの構成情報を出力します。 |
12 | debug_layer_number | デバッグプリントで形状を確認したいレイヤーの番号を指定します。 --debug が指定されているときのみ有効です。 |
$ openvino2tensorflow \
--model_path openvino/320x320/FP32/u2netp_320x320.xml \
--model_output_path saved_model_320x320 \
--output_saved_model \
--output_h5 \
--output_pb \
--output_no_quant_float32_tflite \
--output_weight_quant_tflite \
--output_float16_quant_tflite
PyTorch (NCHW) -> ONNX (NCHW) -> OpenVINO (NCHW) -> TensorFlow Lite (NHWC) の変換が終わりました。 どうでしょうか、簡単でしたでしょうか? このツールで saved_model 形式で出力しておくと、 TFJS や TF-TRT や CoreML への変換がOSSのツール一発でできるようになります。 そのあたりの追加の手順はコチラ GitHub - PINTO0309/PINTO_model_zoo にサンプルスクリプトを大量にコミットしてありますので、気になる方はご参考までにどうぞ。 2020.11.14時点では、 71種類のモデルを変換してコミットしてあります。
TFLiteのモデル構造 model_float32.tflite |
---|
【参考】 https://github.com/PINTO0309/openvino2tensorflow
7. おわりに
私は別に TensorFlow の信者ではありません。 にも関わらずここまでして何故 TensorFlow のモデルへの変換にこだわっているかというと、 TensorFlow はサポートされている実行環境が豊富だから、という点に尽きます。 PyTorch は面白いモデルが豊富なこともありワクワクが凄いので嫌いではないのですが、 ONNX へエクスポートができないトリッキーなモデルがはびこっていて今の所はあまり好きにはなれていません。 というか、TorchScriptを使えばいいじゃん、というご指摘があると思いますが、フレームワーク自身の標準機能で用意されたモデル出力機能にも関わらずエラーが頻発するのが理解不能で耐えられません。(今の所は、です。) より洗練されていけば使いやすくなってとっつきやすくなるのかな、と考えています。