2019年11月30日土曜日

MediaPipeのObject detectionのサンプルでSSDLite MobileNet V3を試す

目的


MediaPipeのAndroidのObject detectionのサンプルを動かしたときの備忘録を残す。
今回の手順は公式にもあるので、あまり参考にはならない。


動機


MediaPipeについてはTLに流れてから、ずーっと気になっていた。
今回、モバイルに最適化されたSSDLite MobileNet V3が登場したので試してみた。





Xperia Z5でSSDLite MobileNet V3 Small をGPU実行したときのスクリーンキャプチャである。



手順


手順としては、以下となる。
  1. Android用の環境を準備(Bazel、Android関連のインストール)
  2. MediaPipe用にモデルをエクスポート
  3. モデルをMediaPipeのプロジェクトへの組み込み、ビルド
  4. Andoridへのインストール


Android用の環境を準備


Linux環境(筆者はFedora 31)にBazel、Andorid Studio(Android SDK)、Android NDKをインストールする。インストールの方法は、「TensorFlow Lite GPU delegateのAndroid用ライブラリをビルドする」と同じである。


MediaPipe用にモデルをエクスポート


TF-Liteモデルをエクスポートするのだが、通常のTF-Liteモデルのエクスポート手順とは異なる。これは、PostProssessing(NonMaxSuppression)の処理をMediaPipeが行うためである。
手順は公式でも記載されているが、ここではSSDLite MobileNet V3 Smallのpre-trainedモデルで行う。

まず、tensorflow / models をcloneし、object detection apiのインストールを行う(TF1.15でmodelsはmasterで試している)。

Tensorflow detection model zooからssd_mobilenet_v3_small_cocoをダウンロードして展開する。

$ wget http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v3_small_coco_2019_08_14.tar.gz
$ tar xf ssd_mobilenet_v3_small_coco_2019_08_14.tar.gz

MediaPipeの手順に沿ってモデルをfreeze graphする。ここで注意することは、通常の手順と異なる(パラメータが異なる)ことである。具体的には、export_tflite_ssd_graph.pyの引数に--add_postprocessing_op=falseを指定する。

$ CONFIG_FILE=xxxx/ssd_mobilenet_v3_small_coco_2019_08_14/pipeline.config
$ CHECKPOINT_PATH=xxx/ssd_mobilenet_v3_small_coco_2019_08_14/model.ckpt
$ OUTPUT_DIR=path_to_output_dir
$ python object_detection/export_tflite_ssd_graph.py --pipeline_config_path=$CONFIG_FILE --trained_checkpoint_prefix=$CHECKPOINT_PATH --output_directory=$OUTPUT_DIR --add_postprocessing_op=false

出力ディレクトリには2つのファイルが作成される。
  • tflite_graph.pb
  • tflite_graph.pbtxt

これをtoco(tflite_convert)で変換する。
input_arrays, output_arraysの名前に注意。また、input_shapesはSSDLite MobileNetV3は320, 320である。
$ tflite_convert --graph_def_file=$OUTPUT_DIR/tflite_graph.pb \
  --output_file=$OUTPUT_DIR/ssdlite_object_detection.tflite \
  --input_format=TENSORFLOW_GRAPHDEF \
  --output_format=TFLITE \
  --inference_type=FLOAT \
  --input_shapes=1,320,320,3 \
  --input_arrays=normalized_input_image_tensor \
  --output_arrays=raw_outputs/box_encodings,raw_outputs/class_predictions


モデルをMediaPipeのプロジェクトへの組み込み、ビルド


mediapipeのリポジトリをcloneして、作成したTF-Liteモデルをmediapipe/mediapipe/model配下にコピーする

$ git clone https://github.com/google/mediapipe.git
$ cp $OUTPUT_DIR/ssdlite_object_detection.tflite mediapipe/mediapipe/model/ssdlite_object_detection.tflite

ANDROID_NDK_HOME, ANDROID_HOMEを設定し、ビルド。
(ANDROID_NDK_HOME, ANDROID_HOMEはインストールした環境にあわせて変更)
$ export ANDROID_HOME=$HOME/Android/Sdk
$ export ANDROID_NDK_HOME=$HOME/Android/Sdk/ndk-bundle

CPU版
$ bazel build -c opt --config=android_arm64 mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetectioncpu

GPU版
$ bazel build -c opt --config=android_arm64 mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetectiongpu


Andoridへのインストール


あとはadbコマンドを使ってインストールする。
$ adb install bazel-bin/mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetectiongpu/objectdetectiongpu.apk

メニューに Object Detection CPU / Object Detection GPUのアイコンがあるので、タップすればOK。
(再度、インストールする場合は、一度アンインストールしてから)。

2019年11月24日日曜日

Jetson NanoでTF-TRTを試す(Object detection)

目的


Object detectionのモデルについて、TF-TRT(TensorFlow integration with TensorRT)を使ってFP16に最適化したモデルを生成し、Jetson Nanoでどの程度最適化の効果ががあるのかを確認する。


動機


Jetson NanoでTF-TRTを試す(Image Classification)その2では、モデルの生成時、 Relu6(x )を relu(x) - relu(x - 6)に置き換えをやめることにより、処理時間が短縮されることを確認した。
また、2019年10月にSSDLite MobileNet V3のモデルが公開された。
このため、Object detectionのモデルについて、TF−TRTの変換および、効果を確認する。


まとめ

  • 前回と同様、Relu6の置き換えを行うことで推論の処理時間短縮になることを確認した。
  • 本家リポジトリが行っているGraphの書き換え(NonMaxSuppressionのCPU実行)が推論の処理時間の短縮になっていることを確認した。
  • Jetson Nanoでは、SSDLite MobileNet V3 Large、Smallは、精度(mAP)が同等のモデルと比べた場合、推論の処理時間が遅いことがわかった。
  • (個人的には)SSD MobileNet V1 FPNが動作することに驚いた。
    ターゲットにもよるがこれは使える可能性がある。


環境


2019.11.23時点の環境で確認。

  • JetPack 4.2.2 (r32.2.1)
  • TensorRT: 5.1.6
  • TensorFlow: 1.14.0+nv19.10
  • Python:3.6.8


モデルの作成


変換用のスクリプトは、本家よりForkしたリポジトリにアップしている。



今回は、pre-trained models(Tensorflow detection model zoo)をダウンロードしてFP16のモデルへの変換と、自前で学習したモデルの変換を行うようにした。

インストール方法については、本家と同様の手順である。
モデルの変換および、ベンチマークの際は、CUIモード&jetson_clocksで実行している。


TF-TRTモデルの変換


本家リポジトリでは、チェックポイントからGraphを作成する際に大きく2つ変更を行っている。これが最終的な推論の速度にも影響している。

  • Relu6(x )を relu(x) - relu(x - 6)に置き換え
    これは、前回と同様。
    本家のリポジトリでは置き換えが有効になっている。
    現在はRelu6のままTF-TRTモデルに変換したほうが推論の処理時間の短縮につながるため、置き換えをしない。
  • NonMaxSuppressionをCPUで実行するように書き換え
    "Optimize NVIDIA GPU performance for efficient model inference"に詳細が解説されているが、NonMaxSupperssionはGPUで実行するコストが高い。
    (GPUに対応していないOpeがCPUで実行されるため、CPU-GPU間でデータ転送を頻繁に行ってしまう。制御文が含まれるOpeはGPUでの実行が遅い。)
    本家のリポジトリではNonMaxSuppressionをCPUで実行するようにGraphを書き換えている。今回は書き換えを行う・行わないでの違いを確認する。

TF-TRTモデルに変換する際のTrtGraphConverterのパラメータは前回と同様である。
    converter = trt.TrtGraphConverter(
        input_graph_def=frozen_graph,
        nodes_blacklist=output_names, #output nodes
        max_batch_size=1,
        is_dynamic_op=False,
        max_workspace_size_bytes = 1 << 25,
        precision_mode=trt.TrtPrecisionMode.FP16,
        minimum_segment_size=50)



Pre-trained modelsの変換


リポジトリをClone後、"tf_trt_models/examples/detection"ディレクトリに移動して変換用スクリプトを実行する。

python3 convert.py --model="pre-trained model prefix" --output="path to output dir"

--modelで変換したいpre-trained modelを指定する。サポートしているpre-trained modelsは、こちらを参照。
--outputにTF-TRTで変換したFP16のモデルが出力される。モデルのファイル名は"model_frozen_16.pb"である。


自前で学習したモデルの変換


TensorFlow Object detection APIで学習したモデルを変換する。学習後、Exporting a trained model for inferenceに従って、モデルをエクスポートする。
エクスポート後のディレクトリ(model.ckpt.*、pipeline.configファイルを含む)をJetson Nanoにコピーする。

python3 convert.py --path="path to export model dir" --output="path to output dir"

--pathフラグでコピーしたディレクトリのパスを指定する。


ベンチマーク


ベンチマークスクリプトはここ
初回の推論と、2回目以降の推論100回の平均をベンチマークする。

なお、画像サイズはモデルのinputと合わせるように推論前にリサイズしている(推論の時間に含めてない)。

対象のモデルはpre-trained models(Tensorflow detection model zoo)で公開されているSSDのモデルとする。

  • ssd_mobilenet_v1_0.75_depth_coco
  • ssd_mobilenet_v1_coco
  • ssd_mobilenet_v1_fpn_coco
  • ssdlite_mobilenet_v2_coco 
  • ssd_mobilenet_v2_coco
  • ssd_mobilenet_v3_small_coco
  • ssd_mobilenet_v3_large_coco
  • ssd_inception_v2_coco
  • ssd_resnet_50_fpn_coco


モデルサイズ


前回でも判明していたが、モデルサイズは変換後(FP16)のほうがサイズが大きくなる。
なお、ssd_inception_v2、ssd_resnet_50_fpnは変換ができなかった(変換中にKilledとなってしまう)。おそらく、メモリが不足していると思われる。

Model
モデルサイズ [MB]
FP32FP16
ssd_mobilenet_v1_07.5_depth1926
ssd_mobilenet_v12842
ssd_mobilenet_v1_fpn130135
ssdlite_mobilenet_v21930
ssd_mobilenet_v267100
ssdlite_mobilenet_v3_small813
ssdlite_mobilenet_v3_large1321
ssd_inception_v2980
ssd_resnet_50_fpn_coco1290



Jetson Nanoでの初回の推論


変換前(FP32)、変換後(FP16)について、NonMaxSupperssionをCPUで実行する・市しないで比較した。
表・グラフ中で

  • FP32 nms_gpu
    変換前かつ、NonMaxSupperssionの書き換えを行わない
  • FP32nms_cpu
    変換前かつ、NonMaxSupperssionをCPU実行に書き換え
  • FP16 nms_gpu
    FP16に変換かつ、NonMaxSupperssionの書き換えを行わない
  • FP32nms_cpu
    FP16に変換かつ、NonMaxSupperssionをCPU実行に書き換え
である。

結果から、
  • FP32よりFP16のモデルのほうが処理時間が短くなっている。
  • 初回の推論ではNonMaxSupperssionの効果は確認できない。
ことがわかる。


Model
Inference time [ms]
FP32 nms_gpuFP32 nms_cpuFP16 nms_gpuFP16 nms_cpu
ssd_mobilenet_v1_0.75_depth30694301951042010076
ssd_mobilenet_v1377183852873279294
ssd_mobilenet_v1_fpn1023241052941863021492
ssd_mobilenet_v247367661181154516043
ssdlite_mobilenet_v282924145826152566360
ssdlite_mobilenet_v3_small246232439298458208
ssdlite_mobilenet_v3_large380773842550125431
ssd_inception_v20000
ssd_resnet_50_fpn0000

※ssd_inception_v2, ssd_resnet_50_fpnは実行時にKilledとなってしまう。

結果をグラフ化してみる。
ssdlite_mobilenet_v2のFP32 nms_gpuの場合、突出して処理時間がかかっているため、対数目盛とした。また、ssd_inception_v2, ssd_resnet_50_fpnは除く。



Jetson Nanoでの2回目以降の推論


2回目以降の推論の処理時間を比較する。

  • FP32よりFP16のモデルのほうが処理時間が短くなっている。
  • NonMaxSupperssionをCPU実行に書き換えたほうが処理時間が短くなっている(ssd_mobilenet_v1_fpnのFP32 は除く)。
  • ssdslite_mobilenet_v3_smallは、ssd_mobilenet_v1_0.75_depthよりも処理時間がかかり、ssd_mobilenet_v1とほぼ同等の処理時間である(ssd_mobilenet_v1_0.75_depthとはmAPがほぼ同等)。
  • ssdlite_mobilenet_v3_largeは、ssdlite_mobilenet_v2よりも処理時間がかかる(ssdlite_mobilenet_v2とはmAPがほぼ同等)。
ことが確認できる。

MobileNet V3の論文では、MobileNet V2より早い処理時間となっている。
もともと論文にもあるとおり、MobileNet V3はモバイルCPU向けであり、
  • TF-Liteモデルでのベンチマークであること。
    (6.1.2 Measurement setupを参照)
  • TF-LiteでH-swishのOpeを最適化していること。
    (6.1.2 Measurement setupおよび、Figure 9を参照)

から、GPU実行やTF-TRTのOpeの最適化がないことが影響していると考えられる(後述も参照。このため、今後Opeの最適化があれば十分早くなる可能性あり?)。ただ、これはあくまでも私感であり、間違っている可能性がある。

Model
Inference time [ms]
FP32 nms_gpuFP32 nms_cpuFP16 nms_gpuFP16 nms_cpu
ssd_mobilenet_v1_0.75_depth83545742
ssd_mobilenet_v199726348
ssd_mobilenet_v1_fpn8351146474460
ssd_mobilenet_v21031836955
ssdlite_mobilenet_v216066757252
ssdlite_mobilenet_v3_small119599647
ssdlite_mobilenet_v3_large1298310964
ssd_inception_v20000
ssd_resnet_50_fpn0000

※ssd_inception_v2, ssd_resnet_50_fpnは実行時にKilledとなってしまう。

結果をグラフ化してみる。
ssdlite_mobilenet_v2のFP32 nms_gpuの場合、突出して処理時間がかかっているため、対数目盛とした。また、ssd_inception_v2, ssd_resnet_50_fpnは除く。

もう少しわかりやすいように、ssdlite_mobilenet_v2のFP32 nms_gpuを除いたものも掲載する。

ssdlite_mobilenet_v2のFP32 nms_gpuの処理時間をを除いた


問題点・不明な事象...


今回のベンチマークを行うにあたり、2つのよくわからない事象にあたった。。。書き残しておく(これは筆者の手順ミスの可能性もあるので注意)。


SSDLite MobileNet v3 LargeのPre-trained Modelで検出ができない


これは、Jetson NanoやTF-TRTの問題ではない。
Pre-trainedのモデル(ssd_mobilenet_v3_large_coco)のチェックポイントからモデルをfreeze graph(エクスポート)するしたもを利用するとなぜかオブジェクトを検出しない。。。
ssd_mobilenet_v3_small_cocoはうまくいく(オブジェクトを検出する)。
エクスポートする手順は変わっていないはずなのだが?


MobileNet V3 LargeとSmallの違いはネットワークぐらいで、使っているOpeは同じだと思っていたので、理由がさっぱりわからない。。。

なお、Google colabで再現するnotebookを作成して試してみても再現する(TensorFlowのバージョンは1.12, 1.14, 1.15で確認)。


自分で再学習した場合は、オブジェクトを検出するようになる。
再学習したチェックポイントは、一応、公開しておく(ただし、十分学習できていない。また、都合により削除する場合あるので注意)。


TF-TRTで変換したモデルが再現できない(SSDLite MobileNet V3 Large、Small)


今回、SSDLite MobileNet V3 Large、SmallのTF-TRTモデルの変換を行っていた中で、一度だけFP16のモデルの変換に成功?したのか、推論の処理時間が早くなったモデルが生成できた。

下の表のFP16 nms_cpu(最速?)の列は、何故か推論の処理時間が早くなったモデルである。FP16 nms_cpu(ベンチ)の列は、ベンチマークで記載したものである。


Model
Input size
Inference time [ms]
FP16 nms_cpu
(最速?)
FP16 nms_cpu
(ベンチ)
ssdlite_mobilenet_v3_small320×3203347
ssdlite_mobilenet_v3_large320×3205164


  • ssdlite_mobilenet_v3_smallはmAPが同等のssd_mobilenet_v1よりも早い。
  • ssdlite_mobilenet_v3_largeはssdlite_mobilenet_v2とほぼ同じ。

その後、何度か再現(モデルの変換)を試みたが再現できなかった。
当初、モデルの変換の際に誤って違うモデルを選んだのかと思ったのだが、モデルの構造をみると、

TRTEngineOpのinput_shapeが320×320となっている。MobileNet V3以外でinputが320×320はないため間違いとは思えない。。。

TrtGraphConverterのパラメータをいじっても再現しない。もし、これが再現できるのであれば、SSDLite MobileNet V3は、推論の処理時間とmAPをみても十分使えるものになると考えられる。

こssdlite_mobilenet_v3_smallのモデルについて公開してこうと思う(筆者の都合により削除する場合あるので注意)。


最後に


TF-TRTについてObject detectionモデルの変換についてまとめてみた。とりあえず、Image classification、Object detectionのモデルについてはやってみた。今後は、新しいモデルの登場、TF-TRTのバージョンアップがあればやってみようと思う。