20201113のTensorFlowに関する記事は1件です。

PyTorch, ONNX, Caffe, OpenVINO (NCHW) のモデルをTensorflow / TensorflowLite (NHWC) へお手軽に変換する

日本語 English

1. はじめに

いつも左中間を狙うようなプチニッチなふざけた記事ばかりを量産しています。 この記事の手順を実施すると、 最終的に PyTorch製 高精度Semantic Segmentation の U^2-Net を TensorFlow Lite へ変換することができます。 下図のような感じです。
ezgif.com-gif-maker (2).gif

TensorFlow めちゃくちゃ扱いにくいです。 日々公開される最新のとても面白いモデルは軒並みPyTorch実装ですし、なんでTensorFlowで実装してくれないんだ!! と、常日頃思っています。 論文のベンチマークの結果から精度やパフォーマンスが出ることがあらかじめ分かっているモデルにも関わらず、いちいちTensorFlowで再実装してトレーニングし直すのはかなり面倒くさいですし不毛です。 また、みなさんが 「TensorFlowは扱いにくい」 と感じてみえるポイントと私が扱いにくいと感じているポイントはズレているかもしれませんが、概ね下記のようなことがネックになっているのではないか、と考えます。

  1. 他のフレームワークが軒並み NCHW形式 なのに対し、TensorFlowだけ NHWC形式
  2. NHWC形式 がデフォルトのせいで他のフレームワークへの転用をしにくい
  3. 同じ処理・同じ名前・異なるインタフェースのオペレーションがあちこちに散在していて超カオス
  4. 最新のおもしろ実装が比較的少ない (少なく感じる)
  5. 構文が難しい

でも、良いところもちゃんとあると思います。私が考える良いところは下記です。

  1. デバイス向けにゴリゴリにモデルチューニングして爆速体験ができる
  2. TensorFlow.js, TensorRT, TF-TRT, TensorFlow Lite, MediaPipe など、実行環境への最適化の適応幅が広い
  3. ちゃんと最適化すればモデルの構造がとても美しい

はい、 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形式 にストレートに変換しつつ 量子化 まで実施できるところです。

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)?
Screenshot 2020-11-12 09:18:11.png

4. あえてOpenVINOを経由して変換する理由

この記事では PyTorch -> ONNX -> OpenVINO -> TensorFlow / Tensorflow Lite の流れでモデルを最適化しながらNCHW to NHWC変換を行います。 ONNXやその他のNCHW形式のフォーマットからTensorFlowのNHWC形式のフォーマットへは一気に変換しません。 端的に言うと、OpenVINO の Model Optimizer がかなり優秀なのであえてワンクッションだけOpenVINOへの変換をワークフローに組み込むということを行います。

OpenVINOへの変換をワンクッション経由することに関して私なりに考えたメリットは下記の5点です。

  1. OpenVINO の Model Optimizer が変換過程のモデルを勝手に最適化してくれる
  2. OpenVINO自体が推論専用フレームワークとして推論とフレームワーク間コンバージョンの役目に特化しているため、共通フォーマットとしては洗練されている
  3. PyTorch(ONNX), TensorFlow, Caffe, MXNet などの豊富なフレームワークの変換に対応している
  4. 全てのオペレーションの情報とオペレーション間の接続情報が人間が読解可能な簡単なXMLファイルに出力されるため、学習後のモデルの構造をエディタを使用して後から簡単に書き換えることができる
  5. 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
Resnet34_3inputs_448x448_20200609 onnx_ Resnet34_3inputs_448x448_20200609 xml model_float32 tflite

6. 変換手順

PyTorch -> ONNX -> OpenVINO -> TensorFlow / Tensorflow Lite の流れでモデルを変換します。 OSS上でいただいたアドバイスなどをもとに独自のワークフローとして組み入れた手順をご紹介します。

6-1. TensorFlow と OpenVINO のインストール

TensorFlow と OpenVINO をインストールします。 TensorFlow は v2.3.1 以降、 OpenVINO は 2021.1 以降を導入してください。

TensorFlowのインストール
$ sudo pip3 install tensorflow==2.3.1 --upgrade

OpenVINO はOSによって導入の手順が異なります。 下記に記載の手順に従ってインストールしてください。 なお、この手順でサポートする OpenVINO のバージョンは 2021.1 以降のものです。

6-2. openvino2tensorflow のインストール

OpenVINO IR のモデルを TensorFlow の saved_model や .pbファイル、.tfliteファイル、.h5ファイル へ自動変換することができる私の独自ツール openvino2tensorflow をインストールします。 下記のコマンドを実行するだけで最新のパッケージをインストールすることができます。 ほぼ毎日のようにバグフィックスと対応レイヤーの増加を行っていますので、作業開始時には毎回実行いただくことをおすすめします。

openvino2tensorflowのインストール
$ sudo pip3 install openvino2tensorflow --upgrade

【参考】GitHub: https://github.com/PINTO0309/openvino2tensorflow

6-3. PyTorch のインストール

下記のホームページから、ご利用の環境に適したバージョンのインストーラを選択してください。 私の場合は下記の通りの指定を行いました。 注意点はご利用中の CUDA のバージョンを適切に選択すること、ぐらいです。

https://pytorch.org/
Screenshot 2020-11-13 00:25:38.png

PyTorchのインストール
$ 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 のインストール

onnxruntimeのインストール
$ sudo pip3 install onnxruntime --upgrade

【参考】GitHub https://github.com/microsoft/onnxruntime

6-5. ONNX Simplifier のインストール

ONNXのモデルを出力したことがある方は感じたことがあるかもしれませんが、ONNXのモデル構造はかなり冗長です。 例えば下図の構造が、
simple_reshape.png
ONNX変換するとこうなります。 うげげげ。。。 美しくない。。。
complicated_reshape.png
ココで導入する ONNX Simplifier にONNXモデルを投入すると、モデル全体の重みのサイズの最適化と構造の最適化を同時に実施してくれます。下図のとおりです。 最適化具合が一目瞭然ですね。
comparison.png

ONNX_Simplifierのインストール
$ 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の実行イメージは下図のとおりです。 驚くほど精細でカッコイイですね。
u2netqual.png

U^2-NetのClone
$ 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に変換できることです。
Screenshot 2020-11-12 22:16:18.png
では、実際にサンプルの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したリポジトリフォルダの直下で下記のようにコマンドを実行します。

pytorch_to_onnx.pyによる.pthファイルのONNX変換
$ 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. と表示されれば成功です。
Screenshot 2020-11-12 23:45:15.png
u2netp_320x320.onnx がちゃんと生成されていますね。
Screenshot 2020-11-12 23:46:27.png

6-7. ONNXモデルの最適化

先ほど生成したONNXファイルをパラメータに指定して下記のコマンドを実行するだけです。

ONNXモデルの最適化実行
$ python3 -m onnxsim u2netp_320x320.onnx u2netp_320x320_opt.onnx

めちゃくちゃ最適化されました。 複雑なモデルであればあるほど効果が高くなります。

最適化前 u2netp_320x320.onnx 最適化後 u2netp_320x320_opt.onnx
u2netp_320x320.onnx.png u2netp_320x320_opt.onnx (1).png

【参考】GitHub https://github.com/daquexian/onnx-simplifier

6-8. ONNX -> OpenVINO IR 変換

では、先ほど最適化して生成した u2netp_320x320_opt.onnx を入力として、OpenVINO のコンバーターを使用して IR形式へ変換します。 下記のコマンドを実行します。 Caffeのモデルを変換する場合はココから先の手順を実施するだけでよいです。

OpenVINO_IR形式への変換
$ 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種類のファイルが生成されました。
Screenshot 2020-11-13 21:52:37.png
model_optimizer へ指定可能なパラメータは下記をご覧ください。

【参考】 https://docs.openvinotoolkit.org/latest/openvino_docs_MO_DG_prepare_model_convert_model_Convert_Model_From_ONNX.html

【参考】 https://docs.openvinotoolkit.org/latest/openvino_docs_MO_DG_prepare_model_convert_model_Converting_Model_General.html

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 が指定されているときのみ有効です。
OpenVINO_IR形式(NCHW)からTensorFlow形式(NHWC)のモデルを生成
$ openvino2tensorflow \
  --model_path openvino/320x320/FP32/u2netp_320x320.xml \
  --model_output_path saved_model_320x320 \
  --output_saved_model True \
  --output_h5 True \
  --output_pb True \
  --output_no_quant_float32_tflite True \
  --output_weight_quant_tflite True \
  --output_float16_quant_tflite True

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
u2netp_320x320_float32.tflite.png

【参考】 https://github.com/PINTO0309/openvino2tensorflow

7. おわりに

私は別に TensorFlow の信者ではありません。 にも関わらずここまでして何故 TensorFlow のモデルへの変換にこだわっているかというと、 TensorFlow はサポートされている実行環境が豊富だから、という点に尽きます。 PyTorch は面白いモデルが豊富なこともありワクワクが凄いので嫌いではないのですが、 ONNX へエクスポートができないトリッキーなモデルがはびこっていて今の所はあまり好きにはなれていません。 というか、TorchScriptを使えばいいじゃん、というご指摘があると思いますが、フレームワーク自身の標準機能で用意されたモデル出力機能にも関わらずエラーが頻発するのが理解不能で耐えられません。(今の所は、です。) より洗練されていけば使いやすくなってとっつきやすくなるのかな、と考えています。

8. 参考記事

  1. https://github.com/digital-standard/ThreeDPoseUnityBarracuda
  2. TorchScriptを使用してPyTorchのモデルを保存する - Qiita - hirune924さん
  3. モデル構造の可視化ツール Netron
  4. https://github.com/PINTO0309/PINTO_model_zoo
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む