2019年12月29日日曜日

2019年の振り返り

今年はいろいろとやった気がするので、振り返ってみようと思う。やっぱり、Edge TPUとJetson Nanoを中心にとても楽しい一年だったと思う。


エッジ向けAIデバイス


今年はエッジ向けAIデバイスがブームになったと思う。個人では3つ購入して遊んでみた。


個人でこのような素晴らしいデバイスを購入できるようになったのはとても良いと思う。また、AIデバイスによってOn-DeviceでDeep Learningができるようになることは、とても大きい可能性があると思う。


Edge TPU


3月に発売を知って、真っ先に買ってみた。
画像認識、物体検出だけでなくて、セグメンテーションモデルやポーズ推定も動作するのは素晴らしい。まだまだ、使い方はたくさんあると思うので、来年も継続してやってみたい。

こんなことをやってみた。

ブログ(いくつか)


コード

また、TensorFlow Liteについても、ちょっとだけわかった気がする(あくまで気がする)。ラズパイなどのARM CPUでそれなりに動作できるのはとても素晴らしいと思う。モバイルも含めていろいろとできることが増えるなぁと感じる。
TensorFlow Lite for MicrocontrollersやAndoridも気になる分野なので来年は絶対、チャレンジする。

インフルエンサーのかたがつぶやくとものすごい広まることも知った(数日で1,000ビュー超えるなんて初めて...)。


Jetson Nano


最初は我慢していたけど、物欲に負けて7月に購入。
TF-TRT(TensorFlow integration with TensorRT)を中心にやってみた。また、もっと推論は早くなるはず、と指摘いただいて試してみた結果、原因が判明したこともよかった。

ブログ
コード

CUDAはいろいろと使い方がたくさんある。まだ、TensorRTも試せていないので、来年は手を出したいと思う。


M5STICKV


RISC-V CPUとKPUプロセッサで\3,000ちょっとはとても安い!これも物欲に負けて購入。まだ十分使えていないので、来年はやってみる!これを使ってやってみたいことはたくさんある。


MediaPipe


気になっていて、ようやく試せた。MLのアプリケーション作るのは面倒!だけど、フレームワークにして楽にしちゃおう!という感じだと思うけど、これからこういったアプローチは増えてくるのだろうか?来年もウォッチしようと思う。

ブログ
コード



コンペ


今年の前半は、いくつか画像のコンペをチャレンジしてみた。まあ、メダルは取れたけど、やっぱり金じゃないのはくやしい。上位を目指したい。時間が取れなくてチャレンジだけで終わってしまったのもいくつかる。来年はどうしよう?もう一回チャレンジしてみようかな?


釣り


まぁ、今年も行ける回数は少なかったよね(しょうがない)。しかも数年ぶりにボウズもあった(涙)来年も、数回は行きたいね。







ラーメン


一応、長野はラーメン激戦区。お店は14回訪問。月1回ぐらいのペース?今度はブログにラーメンを書いてみようかな。旨い!しかないけど。。。

久々の阿吽。塩。健康診断の後の一杯は最高

土鍋や竹さん。いつもおいしい!

県外でのラーメンは1回だけ。東京ラーメンストリートの斑鳩


最後に


書かなかったけど、Yoctoもっと詳しくなりたいし、カメラもうまくなりたい。jax colab tpuもある、ラズパイ4も使い倒したい... スノーボードも復活したいし...
やりたいことはたくさんある。

ただ、今年は楽しい一年だったと思う。
一番は、Twitterでいろいろとアドバイスやいいねをいただくフォロワーさんが増えたことがうれしかった(ほんと、ありがとうございます)。いろいろとリプライをもらったり、ここに記載できないものがたくさんありすぎて、感謝。やっぱり、色々もらえるとモチベーションもちがうんだなぁとわかった。

来年も、面白いことをやってきたいと思う。

2019年12月27日金曜日

MediaPipeでEdge TPU


目的


ここ最近、MediaPipeのサンプルをEdge TPUで動作させてみて、オリジナルから変えてみたこと、ハマったこと、感じたことをまとめてみる。


動機


前のブログでも書いたとおり、MediaPipeは気になっていた。

そこにGoogle Coral DevBoard向けのサンプルがでてきた。
DevBoardを持っておらず購入しようか迷ったが、USB Acceleratorでもできるのは?と思いやって見た結果、Jetson Nano + Edge TPU、Raspberry Pi + Edge TPUで動かすことができた。





メリットとデメリット


思いつくままにメリットとデメリットを記載したい。ただし、所感であり誤りも含んでいると思うので注意。

論文、ドキュメントは以下を参照。


メリット


モジュールの再利用性と非同期処理のサポートが特徴と感じる。

これはMediaPipeが目的でもあるMLアプリケーションのプロトタイプモデルをすばやく構築するために再利用性を高めることと、MLアプリケーションには必要になる非同期処理(重い推論と他の処理を分離)を意識していることからだと思われる。

  • モジュールの再利用
  • 非同期処理のサポート
  • フロー制御のサポート

モジュールの再利用がしやすい


Calculatorとしてコンポーネントを分割することで、再利用しやすい作りとなっている。このコンポーネントをCalculatorと呼んでいる。特に推論を行う前処理、後処理は同じものを再利用したい。AnnotationOverlayCalculator(bonding boxの描画)やPacketResamplerCalculator(フレームの間引き)などがそれに当たる。あと、CPU⇔GPU間のメモリ割当もサポートしているため、AndroidのGPU delegateも簡単に構築できる。


非同期処理のサポート


一般に、推論部分は一番重い(処理時間がかかる)。

このため、GPU(もしくはAccelerator、CPUなど)で推論している間に他の処理をCPUで行うことで、トータルの処理時間を短縮させたい。一般的には、カメラ入力、推論、描画は非同期に行って描画を高速にしたい(見せたい)ということもある。しかし、一般的に非同期処理を実装するのは難易度が高い。

MediaPipeにはtimestampと呼ばれる概念で同期を取ることができる。timestampはデータ(packetと呼ばれる)が生成されるたびにインクリメントされていく。Calculatorは分散で処理していて、データ(packetと呼ばれる)に付属するtimestampによって、データの到達順が管理され、Calculator間のデータの同期が取られる。


フロー制御のサポート


推論など重い処理を担当するCalculatorは、データのボトルネックとなってしまう。適切な制御を行わないとオーバーフローやデッドロックの原因になってしまう。このため、MediaPipeは2つのフロー制御を持っている。
  • The back-pressure mechanism
    ストリームでバッファリングされたPacketが制限に達すると、上位ノードの実行を抑制する。
  • 専用ノードの挿入
    PacketResamplerCalculatorを使ったノードの場合は、Packetを間引きする(たとえば、フレームレートを1/10に間引くなど)。
    FlowLimiterCalculatorを使ったノードの場合は、処理が終わるまでの間のPacketを受け捨てる。

デメリット


まだ、ドキュメントやツール類が追いついていないところにデメリットを感じてしまう。このあたりは今後改善されるかもしれない。
  • Bazelによるビルド
  • どんなCalculatorがあるのか?
  • デバッグツールがない?
  • 実装がC++

Bazelによるビルド


Bazelを多少は理解していないといけないところは辛い。また、graphとBUILDの対応は手動で行う必要がある(graphからビルドするCalculatorのパス・ファイル名を割り出さないといけない)。もう少しスマートな方法がないのだろうか?


どんなCalculatorがあるのか?


これは、ドキュメントが整備されていないのが原因。今は、ソースから何があるのかを拾い出さないといけない。ドキュメントが整備されれば解消されるとは思う。


デバッグツールがない?


Calculatorが非同期で動作するということは、デバッグが大変である。論文を見る限りツールがありそうだがまだすべてが公開されていないようである。ブレークポイントを設定してのデバッグは大変かもしれない...


実装がC++


現状はアプリケーション、CalculatorともC++での実装が必要。C++は敷居が高いと思われる。少なくともアプリケーションはPythonで書きたいのでは?


Edge TPU向けのサンプルを作成したポイント


ここでは、Coral Dev Board Setup (experimental)からEdge TPU USB Acceleratorを使ったサンプルを作成したのポイントを残す。
作成したサンプルは2つ。

Jetson Nano + Edge TPU


こちらは、ほとんどCoral Dev Board Setup (experimental)から変更はない。
Jetson Nano側にEdge TPUのRuntimeをインストールするぐらいである。OpenCVもJetpackにインストール済みのライブラリを使用すればよかった。

Raspberry Pi + Edge TPU


Raspbianは32bit OSであるため、クロスコンパイル時の指定を"--cpu=armv7a"にするぐらいである。ほぼ変更なく動作できるのは、MediaPipeの強みの一つだと思う。


Object Detection and Tracking


AndroidのデモをRaspberry Pi + Edge TPUで動かそうと思ったが、ハマってしまった... 最初の検出は動作するのだが、2回目以降の検出がされない... 原因は、Raspberry Pi(もとはCoarl)のメイン処理とPacketResamplerCalculatorにあった。

もともと、Object Detection and TrackingのGraphは、入力ストリーム(カメラ or ビデオ)を間引いて推論を行っている(トラッキングや表示は間引かない)。これは、時間のかかる・リソース消費が大きい推論を全フレームで行わず、トラッキングで補完することで、リアルタイム・リソース消費を軽くしている。このフレームの間引きをPacketResamplerCalculatorが担当し、デフォルトでは0.5フレームにしている(フロー制御の一つ)。

PacketResamplerCalculatorはデータのtimestampをチェックしてtimestampが指定したフレームレートの時間に達したら、後続のCalculatorに流すようになっている。このため、timestampは経過時間(実時間)を期待している。

timestampはデータの生成の際(メイン処理)に付与され、増加している。Raspberry Pi(もとはCoarl)のメイン処理では、timestampが+1しかされていなかった。このため、timestampの時間の流れが非常にゆっくりになってしまったのが原因だった...(timestampはμs単位なので、非常に長ーく待っていれば、2回目の推論がくる...)

  while (grab_frames) {
    // Capture opencv camera or video frame.
    cv::Mat camera_frame_raw;
    capture >> camera_frame_raw;
    if (camera_frame_raw.empty()) break;  // End of video.
    cv::Mat camera_frame;
    cv::cvtColor(camera_frame_raw, camera_frame, cv::COLOR_BGR2RGB);
    if (!load_video) {
      cv::flip(camera_frame, camera_frame, /*flipcode=HORIZONTAL*/ 1);
    }

    // Wrap Mat into an ImageFrame.
    auto input_frame = absl::make_unique(
        mediapipe::ImageFormat::SRGB, camera_frame.cols, camera_frame.rows,
        mediapipe::ImageFrame::kDefaultAlignmentBoundary);
    cv::Mat input_frame_mat = mediapipe::formats::MatView(input_frame.get());
    camera_frame.copyTo(input_frame_mat);

    // Send image packet into the graph.
    MP_RETURN_IF_ERROR(graph.AddPacketToInputStream(
        kInputStream, mediapipe::Adopt(input_frame.release())
                          .At(mediapipe::Timestamp(frame_timestamp++))));
    // ★↑ここでframe_timestampが+1されていることが原因...

    // Get the graph result packet, or stop if that fails.
    mediapipe::Packet packet;
    if (!poller.Next(&packet)) break;
    auto& output_frame = packet.Get();

    // Convert back to opencv for display or saving.
    cv::Mat output_frame_mat = mediapipe::formats::MatView(&output_frame);
    cv::cvtColor(output_frame_mat, output_frame_mat, cv::COLOR_RGB2BGR);
    if (save_video) {
      writer.write(output_frame_mat);
    } else {
      cv::imshow(kWindowName, output_frame_mat);
      // Press any key to exit.
      const int pressed_key = cv::waitKey(5);
      if (pressed_key >= 0 && pressed_key != 255) grab_frames = false;
    }
  }


このため、実時間(前回データ生成時との時間の差)を与えることで、問題が解消できたのである。修正のコードは下記。githubにもアップしている。std::chronoで前回からの時間差をとっている(ただ、この修正だと、ビデオファイル入力ではまずい... ビデオファイル入力の場合は、フレームレートから算出しないといけない)。

  std::chrono::system_clock::time_point start;
  start = std::chrono::system_clock::now();
  while (grab_frames) {
    // Capture opencv camera or video frame.
    cv::Mat camera_frame_raw;
    capture >> camera_frame_raw;
    if (camera_frame_raw.empty()) break;  // End of video.
    cv::Mat camera_frame;
    cv::cvtColor(camera_frame_raw, camera_frame, cv::COLOR_BGR2RGB);
    if (!load_video) {
      cv::flip(camera_frame, camera_frame, /*flipcode=HORIZONTAL*/ 1);
    }

    // Wrap Mat into an ImageFrame.
    auto input_frame = absl::make_unique(
        mediapipe::ImageFormat::SRGB, camera_frame.cols, camera_frame.rows,
        mediapipe::ImageFrame::kDefaultAlignmentBoundary);
    cv::Mat input_frame_mat = mediapipe::formats::MatView(input_frame.get());
    camera_frame.copyTo(input_frame_mat);

    // Get timestamp.
    // ★ここで時間差(μs)を取得する。
    auto end = std::chrono::system_clock::now();
    frame_timestamp = std::chrono::duration_cast(end - start).count();
    
    // Send image packet into the graph.
    MP_RETURN_IF_ERROR(graph.AddPacketToInputStream(
        kInputStream, mediapipe::Adopt(input_frame.release())
                          .At(mediapipe::Timestamp(frame_timestamp))));

    // Get the graph result packet, or stop if that fails.
    mediapipe::Packet packet;
    if (!poller.Next(&packet)) break;
    auto& output_frame = packet.Get();

    // Convert back to opencv for display or saving.
    cv::Mat output_frame_mat = mediapipe::formats::MatView(&output_frame);
    cv::cvtColor(output_frame_mat, output_frame_mat, cv::COLOR_RGB2BGR);
    if (save_video) {
      writer.write(output_frame_mat);
    } else {
      cv::imshow(kWindowName, output_frame_mat);
      // Press any key to exit.
      const int pressed_key = cv::waitKey(5);
      if (pressed_key >= 0 && pressed_key != 255) grab_frames = false;
    }
  }


最後に


MediaPipeを使うとMLアプリケーションの開発がとても簡単になるように思える。
ただ、Calculator(コンポーネント)の実装やデバッグまで考えると難易度が高いようにも思える。
もう少し使われるようになるのか?2020年もウォッチしてみたいと思う。


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のバージョンアップがあればやってみようと思う。


2019年10月27日日曜日

TensorFlow Lite GPU delegateのAndroid用ライブラリをビルドする

目的


TensorFlow LiteのAndroid用バイナリをビルドする手順を記載する。
特に、GPU delegate用のライブラリは手順がなかったのでメモ程度に残す。


動機


前回のブログで作成したTF-LiteモデルをAndroidスマホで動かしてみようとした。
公式のサンプルを改造して動かしてみていたが、FP16 Quant modelのGPU delegateがどうしても動作できなかった。
公式ではリポジトリから0.0.0-nightlyをダウンロードして組み込んでいる。これは、2019年5月のビルドで少し古いため、新しいバージョンでビルドしてみて確認してみたかった。
(ただ、結果はFP16 Quant modelのGPU delegateは動作しなかった...)


参考


手順


Bazelのインストール


TensorFlowをビルドするときと同じ。2.0rcをビルドしたときと同じものを使用する。
バージョンは0.25.3。


Android NDKのインストール


公式の手順では、"The current recommended version is 17c"とバージョンが指定されている。リンクから17cのLinux 64 ビット(x86) をダウンロードする。
ダウンロード後は適当なフォルダに展開しておく(パスを通す必要は特に無い)。

※v2.0.0および、master(8b036e0cfb7e9bb1c016860cd7fe79752b97443c)では、最新のr20でも問題なくビルドはできた。


Android SDKのインストール


Android Studioの最新版をインストールする。
ここから3.5.1 for Linux 64-bit をダウンロードし、android-stuio/studio.sh を実行する。
インストール後、Welcome to Android StuidoのConfigure > SDK ManagerからSDKにName: Android 10.0(Q)、API Level: 29にチェックが入っていることを確認する。 


TensorFlowのClone


GithubからTensorFlowのリポジトリをCloneする(ここではmasterを想定)。
$ git clone https://github.com/tensorflow/tensorflow.git


Python仮想環境の準備


不要とは思うが念の為、Pythonの仮想環境を作成して作業する。
$ mkvirtualenv  --python=python3 tflite-gpu2.0
$ pip install pip six numpy wheel setuptools mock 'future>=0.17.1'
$ pip install keras_applications==1.0.6 --no-deps
$ pip install keras_preprocessing==1.0.5 --no-deps


ビルド


configureを行い、tensorflow-lite、tensorflow-lite-gpuの順にバイナリを作成する。
tensorflow-lite-gpuはGPU delegateの部分しか無いため、tensorflow-liteも必要。

configureのポイント

  • CUDAはNo。
  • Android builds?はYes。
  • path of the Android NDK to useには、配置したNDKのパスを入力。
  • path of the Android SDK to useは、デフォルトであれば/home/xxxx//Android/Sdkで問題ないはず。
  • NDK API level、SDK API levelはデフォルトのまま。

Configureを実行
$ ./configure 
WARNING: --batch mode is deprecated. Please instead explicitly shut down your Bazel server using the command "bazel shutdown".
You have bazel 0.25.3- (@non-git) installed.
Please specify the location of python. [Default is /home/xxxxx/.virtualenvs/tflite-gpu2.0/bin/python]: 


Traceback (most recent call last):
  File "", line 1, in 
AttributeError: module 'site' has no attribute 'getsitepackages'
Found possible Python library paths:
  /home/xxxxx/.virtualenvs/tflite-gpu2.0/lib/python3.7/site-packages
Please input the desired Python library path to use.  Default is [/home/xxxxx/.virtualenvs/tflite-gpu2.0/lib/python3.7/site-packages]

Do you wish to build TensorFlow with XLA JIT support? [Y/n]: 
XLA JIT support will be enabled for TensorFlow.

Do you wish to build TensorFlow with OpenCL SYCL support? [y/N]: 
No OpenCL SYCL support will be enabled for TensorFlow.

Do you wish to build TensorFlow with ROCm support? [y/N]: 
No ROCm support will be enabled for TensorFlow.

Do you wish to build TensorFlow with CUDA support? [y/N]: 
No CUDA support will be enabled for TensorFlow.

Do you wish to download a fresh release of clang? (Experimental) [y/N]: 
Clang will not be downloaded.

Do you wish to build TensorFlow with MPI support? [y/N]: 
No MPI support will be enabled for TensorFlow.

Please specify optimization flags to use during compilation when bazel option "--config=opt" is specified [Default is -march=native -Wno-sign-compare]: 


Would you like to interactively configure ./WORKSPACE for Android builds? [y/N]: Y
Searching for NDK and SDK installations.

Please specify the home path of the Android NDK to use. [Default is /home/xxxxx/Android/Sdk/ndk-bundle]: /home/xxxxx/WorkSpace/android/android-ndk-r17c


Please specify the (min) Android NDK API level to use. [Available levels: ['14', '15', '16', '17', '18', '19', '21', '22', '23', '24', '26', '27', '28']] [Default is 18]: 


Please specify the home path of the Android SDK to use. [Default is /home/xxxxx/Android/Sdk]: 


Please specify the Android SDK API level to use. [Available levels: ['29']] [Default is 29]:  


Please specify an Android build tools version to use. [Available versions: ['29.0.2']] [Default is 29.0.2]: 


Preconfigured Bazel build configs. You can use any of the below by adding "--config=<>" to your build command. See .bazelrc for more details.
--config=mkl          # Build with MKL support.
--config=monolithic   # Config for mostly static monolithic build.
--config=gdr          # Build with GDR support.
--config=verbs        # Build with libverbs support.
--config=ngraph       # Build with Intel nGraph support.
--config=numa         # Build with NUMA support.
--config=dynamic_kernels # (Experimental) Build kernels into separate shared objects.
--config=v2           # Build TensorFlow 2.x instead of 1.x.
Preconfigured Bazel build configs to DISABLE default on features:
--config=noaws        # Disable AWS S3 filesystem support.
--config=nogcp        # Disable GCP support.
--config=nohdfs       # Disable HDFS support.
--config=noignite     # Disable Apache Ignite support.
--config=nokafka      # Disable Apache Kafka support.
--config=nonccl       # Disable NVIDIA NCCL support.
Configuration finished

ビルド、tensorflow-lite、tensorflow-lite-gpuの2つをビルドする。
まずは、tensorflow-lite。
bazel build \
          --cxxopt='-std=c++11' -c opt \
          --fat_apk_cpu=x86,x86_64,arm64-v8a,armeabi-v7a \
          //tensorflow/lite/java:tensorflow-lite

つぎは、tensorflow-lite-gpu。
tensorflow-lite-gpuのビルド方法はドキュメントが無いのだけど、BUILDファイルを参照するとターゲットが存在する。
$ bazel build \
          --cxxopt='-std=c++11' -c opt \
          --fat_apk_cpu=x86,x86_64,arm64-v8a,armeabi-v7a \
          //tensorflow/lite/java:tensorflow-lite-gpu

ビルドできたライブラリ(*.aar)はbazel-genfiles/tensorflow/lite/java配下にできる。
tensorflow-lite.aar、tensorflow-lite-gpu.aarを用いる。
$ ls bazel-genfiles/tensorflow/lite/java/*.aar
bazel-genfiles/tensorflow/lite/java/tensorflow-lite-gpu.aar
bazel-genfiles/tensorflow/lite/java/tensorflowlite.aar
bazel-genfiles/tensorflow/lite/java/tensorflow-lite.aar
bazel-genfiles/tensorflow/lite/java/tensorflowlite_gpu.aar


アプリへの組込


公式の手順が古くなっているため、少し変更が必要。
compileフラグは古いバージョンでしか使えない。implementationフラグに置き換えればOK。

2019年10月16日水曜日

TF2.0のKerasでPost-training quantization

修正


(2019.11.25)@PINTO03091 さんから指摘いただいたFull Integer quantのrepresentative_data_genのコード、説明の誤りを修正(ありがとうございます)。


目的


TensorFlow2.0がリリースされたので、
  • Keras modelから Post-training quantizationでTF-Lite modelへの変換を試してみる。
  • いろいろなTF-Lite quantization modelへの変換を試してみる。
  • それぞれのTF-Lite quantization modelの特性を確認してみる。

動機


以前、TF-2.0rc1でtf.kerasのMobileNet v2をfine-tuinginし、Post-training quantizationするノートブックを作った。
TF2.0がリリースされたので、このノートブックをもとにモデルを変換して、いろいろなTF-Lite model を比較してみようと思った。


バージョン情報

  • TensorFlow: 2.0.0
  • Edge TPU Compiler version 2.0.267685300
  • Edge TPU runtime and Python API library: 2.12.1 (September 2019)
  • Raspbian: 10.1
  • Jetson Nano: JetPack 4.2.2

環境

  • モデルの生成、学習はGoogle Colab上で行う。
  • 推論の実行は、Google Colab(CPU, GPU)、Raspberry Pi 3 B+、Jetson Nano。
  • Raspberry Pi 3 B+、Jetson Nanoにインストールする TF Lite 2.0 は @PINTO03091さんのTensorflowLite-binを利用。
    なお、Jetson NanoのPython3のバージョン3.6であるため、pyenvをつかって3.7を用意する。


Keras Modelの作成・学習


ソースについては、keras-post-training-quantization.ipynbをベースとする。
学習データは、tensorflow_datasets(tfsd)のtf_flower データセットを使用する。


試したKeras モデル


今回、試したモデルはImage Classificationの以下の4つ。チョイスについてなにか深い意味はない。
  • 独自のCNN 
  • MobileNet v2 1.0
  • Inception v3
  • DenseNet121
(ResNetが無いのは、Fine-tuningしてもval_accuracyがほぼランダムになってしまったため諦めた... なんでだろう?データが少ない?) 


独自のCNNモデル


Sequentialで単純にモデルを構築(Dropoutを入れてもFull Integer quant modelや Edge TPU modelを生成できる)。IMG_SIZEは112。
(サイズを224にすると、Edge TPU Modelに変換できなくなった... Edge TPU Compilerがモデルのサイズを意識しているのか?)
model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, (3, 3), padding='same',
                           input_shape=(IMG_SIZE, IMG_SIZE, 3), activation='relu'),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2), padding='same'),
    tf.keras.layers.Dropout(0.25),

    tf.keras.layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2), padding='same'),
    tf.keras.layers.Dropout(0.25),

    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(512, activation='relu'),
    tf.keras.layers.Dropout(0.4),
    tf.keras.layers.Dense(info.features['label'].num_classes, activation='softmax')
])

MobileNet v2


Pre-trained modelをFine-tuningする。IMG_SIZEは224。
base_model = MobileNetV2(include_top=False,
                         weights='imagenet',
                         input_shape=(IMG_SIZE, IMG_SIZE, 3))

model = tf.keras.Sequential([
    base_model,
    tf.keras.layers.GlobalAveragePooling2D(),
    tf.keras.layers.Dense(info.features['label'].num_classes, activation='softmax')
])

Inception v3


こちらも同様。IMG_SIZEは229。
base_model = InceptionV3(include_top=False,
                         weights='imagenet',
                         input_shape=(IMG_SIZE, IMG_SIZE, 3))

model = tf.keras.Sequential([
    base_model,
    tf.keras.layers.GlobalAveragePooling2D(),
    tf.keras.layers.Dense(info.features['label'].num_classes, activation='softmax')
])

DenseNet121


DenseNetはEdge TPUで動かしたことがなかったのでチョイス。IMG_SIZEは224。
base_model = DenseNet121(include_top=False,
                         weights='imagenet',
                         input_shape=(IMG_SIZE, IMG_SIZE, 3))

model = tf.keras.Sequential([
    base_model,
    tf.keras.layers.GlobalAveragePooling2D(),
    tf.keras.layers.Dense(info.features['label'].num_classes, activation='softmax')
])

モデルの保存


学習したモデルはh5形式で保存する。
# Save keras model
model.save(os.path.join(models_dir, 'xxx.h5'))



Keras ModelからTF-Lite Modelへの変換


TF-Lite model には、
  • 量子化されていないTF-Lite model 
  • 量子化された Quantization model
の2種類が存在する。
量子化することでモデルのサイズを TF-Lite model よりさらに削減することができる。

Quantization modelはPost-training quantizationもしくは、Quantization-aware trainingで作成することができる。TF2.0では、Post-training quantizationのみサポートされているため、今回は、Post-training quantizationを扱う。

さらに Post-training quantizationで量子化できるモデルは、以下の4種類が存在する。
  • Weight quantization model
  • Float16 quantization model
  • Integer quantization model
  • Full integer quantization model

また、Full integer quant model(Integer quant model) から Edge TPUで動作可能なEdge TPU modelを作成することができる。


Python API を使った TF-Lite modelへの変換


TensorFlow Lite converter Python APIを使って保存した Keras modelをTF-Lite model に変換する。


TF-Lite Model


保存したKeras modelをload_modelでロード、from_keras_modelでconverterを取得、convert で変換する。
特に変換する際のパラメータは必要ない。
loaded_model = tf.keras.models.load_model(os.path.join(models_dir, 'mobilenet_v2.h5'))
converter = tf.lite.TFLiteConverter.from_keras_model(loaded_model)

tflite_model = converter.convert()

tflite_file = models_dir/'mobilenet_v2.tflite'
tflite_file.write_bytes(tflite_model)

Weight quantization Model


"hybrid" quantizationとも呼ばれる方法。
重みのみを量子化して、推論時は浮動小数点演算で行われる。
  • 量子化することで、モデルサイズが1/4程度になる。
  • 推論の精度の低下は少なく、元のモデルとほぼ同等。
  • 推論の処理時間も早くなる。
  • モバイル(Android、iOS)やサーバー(クラウド)での実行を想定?

モデルの変換時、optimizationsフラグにOPTIMIZE_FOR_SIZEを指定する。
loaded_model = tf.keras.models.load_model(os.path.join(models_dir, 'mobilenet_v2.h5'))
converter = tf.lite.TFLiteConverter.from_keras_model(loaded_model)

converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]
tflite_weight_quant_model = converter.convert()

tflite_weight_model_quant_file = models_dir/'mobilenet_v2_weight_quant.tflite'
tflite_weight_model_quant_file.write_bytes(tflite_weight_quant_model)

Float16 quantization Model


重みをFloat16に量子化する方法。
  • モデルサイズは1/2程度になる。
  • GPU delegateが可能。
    Andorid、iOSなどGPUで推論の処理時間が早くなる。
  • CPUでも実行可能。
  • モバイルでの実行を想定。

モデルの変換時、supported_typesフラグにtf.float16を指定する。

※TF1.xでは、supported_typesにtf.lite.constants.FLOAT16を指定する。tf.lite.constants.FLOAT16はTF2.0では削除されたので、tf.float16を指定すればよいはず?
loaded_model = tf.keras.models.load_model(os.path.join(models_dir, 'mobilenet_v2.h5'))
converter = tf.lite.TFLiteConverter.from_keras_model(loaded_model)

converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]

tflite_fp16_quant_model = converter.convert()

tflite_fp16_model_quant_file = models_dir/'mobilenet_v2_fp16_quant.tflite'
tflite_fp16_model_quant_file.write_bytes(tflite_fp16_quant_model)

Integer quantization Model


重みとアクティベーションの完全な整数量子化。
  • モデルサイズが1/4程度になる。
  • メモリ使用量の削減、推論時間の高速化。
  • 入力と出力は Float となる。
    (TF-Lite、Weight quant、Float16 quant modelと同じインターフェースで実行できる)
  • モバイルやエッジ(ARM CPU)での実行を想定。

モデルの変換時、optimizationsフラグにDEFAULT 指定する。
入力のキャリブレーション(入力データがどの範囲を取りうるのかを調整)が必要。RepresentativeDataset に入力データを返すジェネレーターを指定する。

以下では、representative_data_genが入力データを返すジェネレーター。
ここでは、学習時に1/255としているため、元の画素である0-255に戻していることに注意。学習時のデータの範囲(0.0-1.0)を入力する。UINT8(0-255)に戻したときに取りうる範囲を決める。
def representative_data_gen():
  for batch in test.take(255):
    yield [batch[0]]

loaded_model = tf.keras.models.load_model(os.path.join(models_dir, 'mobilenet_v2.h5'))
converter = tf.lite.TFLiteConverter.from_keras_model(loaded_model)

converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_data_gen

tflite_full_integer_quant_model = converter.convert()

tflite_full_integer_model_quant_file = models_dir/'mobilenet_v2_integer_quant.tflite'
tflite_full_integer_model_quant_file.write_bytes(tflite_full_integer_quant_model)

Full integer quantization Model


Integer quant modelと同様だが、完全な整数量子化を行う。
モデルの入力・出力とも整数となる。このため、推論時は他のモデルと異なるインターフェースとなる。

モデルの変換時、基本的にはInteger quant modelと同様で、それ以外にinference_input_type、inference_output_typeにtf.uint8を指定することで、Full integer quant modelとなる。

なお、inference_input_type、inference_output_typeは、TF2.0では削除されているため、TF2.0のインターフェースでは、Full integer quant modelとならない(Integer quantization modelになってしまう。)このため、tf.compat.v1.lite.TFLiteConverter.from_keras_model_fileをつかってモデルをロードして変換する。

最初、インターフェースが削除されていたことに気がつかず、この件がわからず悩んでいた(バグなんて思っていた)。
def representative_data_gen():
  for batch in test.take(255):
    yield [batch[0]]

loaded_model = tf.keras.models.load_model(os.path.join(models_dir, 'mobilenet_v2.h5'))
converter = tf.lite.TFLiteConverter.from_keras_model(loaded_model)

converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8
converter.representative_dataset = representative_data_gen

tflite_full_integer_quant_model = converter.convert()

tflite_full_integer_model_quant_file = models_dir/'mobilenet_v2_full_integer_quant.tflite'
tflite_full_integer_model_quant_file.write_bytes(tflite_full_integer_quant_model)

Edge TPU モデル


Full Integer quant modelからEdge TPU modelに変換が可能。
September 2019 Updatesからは、Integer  quant modelからの変換も可能になった。
Keras modelのpost-training quant modelからの変換を強化したとあるので、これもその一つなのだろうと推測。

"We've released a minor update to the Edge TPU Compiler (version 2.0.2xx) with improved support for post-training quantization—especially those built with Keras"
(Coral の September 2019 Updates から抜粋)

Edge TPU Compiler version 2.0.267685300であれば、Integer  quant modelからの変換ができる。ただし、edgetpu.classification.engineのインターフェイスは利用できない。このため、今回はFull Integer quant modelから変換したモデルのみ使用している。


Integer quant modelを変換すると、2つのOpeがCPU側にオフロードされている。
これは、入力・出力のFloatをIntに変換するためのOpeと思われる。
  • QUANTIZE
  • DEQUANTIZE
edgetpu_compiler -s --out_dir /content/models /content/models/mobilenet_v2_integer_quant.tflite
Edge TPU Compiler version 2.0.267685300

Model compiled successfully in 307 ms.

Input model: /content/models/mobilenet_v2_integer_quant.tflite
Input size: 2.58MiB
Output model: /content/models/mobilenet_v2_integer_quant_edgetpu.tflite
Output size: 2.77MiB
On-chip memory available for caching model parameters: 6.91MiB
On-chip memory used for caching model parameters: 2.71MiB
Off-chip memory used for streaming uncached model parameters: 0.00B
Number of Edge TPU subgraphs: 1
Total number of operations: 72
Operation log: /content/models/mobilenet_v2_integer_quant_edgetpu.log

Model successfully compiled but not all operations are supported by the Edge TPU. A percentage of the model will instead run on the CPU, which is slower. If possible, consider updating your model to use only operations supported by the Edge TPU. For details, visit g.co/coral/model-reqs.
Number of operations that will run on Edge TPU: 70
Number of operations that will run on CPU: 2

Operator                       Count      Status

ADD                            10         Mapped to Edge TPU
QUANTIZE                       1          Operation is otherwise supported, but not mapped due to some unspecified limitation
PAD                            5          Mapped to Edge TPU
CONV_2D                        35         Mapped to Edge TPU
DEPTHWISE_CONV_2D              17         Mapped to Edge TPU
DEQUANTIZE                     1          Operation is working on an unsupported data type
MEAN                           1          Mapped to Edge TPU
FULLY_CONNECTED                1          Mapped to Edge TPU
SOFTMAX                        1          Mapped to Edge TPU


なお、Full Integer quant modelを変換すると、CPU側にオフロードされるOpeはない。

edgetpu_compiler -s --out_dir /content/models /content/models/mobilenet_v2_full_integer_quant1.tflite
Edge TPU Compiler version 2.0.267685300

Model compiled successfully in 300 ms.

Input model: /content/models/mobilenet_v2_full_integer_quant1.tflite
Input size: 2.58MiB
Output model: /content/models/mobilenet_v2_full_integer_quant1_edgetpu.tflite
Output size: 2.77MiB
On-chip memory available for caching model parameters: 6.91MiB
On-chip memory used for caching model parameters: 2.71MiB
Off-chip memory used for streaming uncached model parameters: 0.00B
Number of Edge TPU subgraphs: 1
Total number of operations: 72
Operation log: /content/models/mobilenet_v2_full_integer_quant1_edgetpu.log

Operator                       Count      Status

MEAN                           1          Mapped to Edge TPU
SOFTMAX                        1          Mapped to Edge TPU
FULLY_CONNECTED                1          Mapped to Edge TPU
ADD                            10         Mapped to Edge TPU
QUANTIZE                       2          Mapped to Edge TPU
PAD                            5          Mapped to Edge TPU
CONV_2D                        35         Mapped to Edge TPU
DEPTHWISE_CONV_2D              17         Mapped to Edge TPU


推論


Pythonでの推論コードは以下。
# モデルをロードしてinterpreterを取得
interpreter = tf.lite.Interpreter(model_path=path_to_mode_file)
interpreter.allocate_tensors()

# set_tensorで入力画像を設定、invokeで推論を実行する
interpreter.set_tensor(interpreter.get_input_details()[0]["index"], image)
interpreter.invoke()

# 推論結果の取り出し。predictions に推論結果(各ラベルの予測値)となる
predictions = interpreter.get_tensor(interpreter.get_output_details()[0]["index"])

TF-Lite、Weight、Float16、Integer quant modelは、入力は学習時と同じサイズで正規化して入力する。
MobileNet v2の場合、入力は[1, 224, 224, 3]で0.0 - 1.0で正規化したFloat32で入力する。
im = Image.open(path_to_image_file)
im = im.resize((224, 224))
im = np.array(im, np.float32)
im = im / 255.0
im = image[np.newaxis, :, :] # 1, 224, 224, 3にするため

Full Integer quant modelは、正規化せずUINT8(0 - 255)で入力する。
im = Image.open(path_to_image_file)
im = im.resize((224, 224))
im = np.array(im, np.uint8)
im = image[np.newaxis, :, :] # 1, 224, 224, 3にするため


それぞれのモデルの比較


作成したそれぞれのモデルについて、
  • ファイルサイズ
  • 精度
  • 推論時間(Google Colab、Raspberry Pi3 B+、Jetson Nano、Edge TPU)
を比較してみる。

なお、DenseNetのEdge TPU modelは作成できなかった。
※September 2019 Updates によって、コンパイルできなくなった模様。古いコンパイラ使えとある...

モデルのファイルサイズを比較


ファイルサイズを比較すると、
TF-Lite model > Float16 quant model > Weight、Integer、Full integer quant model

TF-Lite modelから比較すると、
  • Weight、Integer、Full Integer quantization modelは、1/2〜1/4程度。
  • Float16 Integer quantization modelは、同じもしくは、1/2程度。
  • モデルによって小さくなるサイズが異なる(が傾向は同じ)。

Model
File size [MB]
Keras model
TF-Lite model
Post quantization model
Edge TPU model
Weight quant Float16 quantInteger quantFull integer quant
Original CNN197502550252525
MobileNet v2 1.016924333
Inception v3127842142222223
DenseNet121362771477

各モデルのサイズを比較


精度の比較


各モデルの精度を比較する。
実行は Google Colab のCPU上での実行。
データセットを分割し、学習、バリデーションに使ってない残りのテストデータで評価した。
ここでは、精度の良さは関係なく、Keras model に対してどの程度、低下があるかを確認する。Edge TPU model は Integer、Full Integer quant modelと同等であるので省略。
  • TF-Lite、Float16 quant model は Keras modelとほぼ同等で精度の低下なし
    Float16 quant model はGPU delegateの場合については未確認。
  • Weight quant model は本来精度の低下は少ないはずだが、MobileNet v2,、Inception v3で精度が大幅に低下した。
    独自のCNNや DenseNetは精度の低下がないことから、モデルの構造が影響していると推測。
  • Integer quant、Full Integer quant model は、若干の精度低下が確認できる。
    ただし、(これは対象にもよるだろうが)低下は0.01 〜 0.02程度。
  • 実際にモデルを導入する際は、必ずテストデータで精度を確認する必要がある。


Model
Top-1 Accuracy
Keras model
TF-Lite Model model
Post quantization model
Weight quant Float16 quantInteger quantFull integer quant
Original CNN0.56390.56390.56940.56110.55560.5556
MobileNet v2 1.00.75830.75830.45830.75560.73890.7417
Inception v30.85000.85000.82780.85000.84170.8444
DenseNet1210.85280.85280.85560.85000.87780.8778

各モデルの精度を比較


処理時間の比較


各モデルの推論の処理時間を比較する。
  • Keras modelの場合は、predictの前後で計測。
  • 各TF-Lite modelの場合は、set_tensor、invoke、get_tensorのまでの区間で計測。
  • Raspberry Pi、Jetson Nanoでは set_num_threadによるマルチスレッドの効果を確認する。

Google Colab 上での実行


Keras modelは Tesla K80、各TF-Lite modelはCPU(Intel(R) Xeon(R) CPU @ 2.20GHz)での実行となる。
  • TF-Lite modelとFloat16 quant modelは同じ処理時間となる。
  • Weight quant modelはTF-Lite modelより処理時間がかかる。
  • Integer、Full Integer quant modelはかなり処理時間がかかる。
    これは、x86_64に最適化されていないためである。
  • サーバー(クラウド)での実行は、TF-Liteもしくは Weight quant model が適している。


Model
Time per inference, in milliseconds (iterations = 20)
Keras model
TF-Lite model
Post quantization model
Weight quant Float16 quant modelInteger quant modelFull integer quant model
Original CNN5452838612011199
MobileNet v2 1.07840864212281229
Inception v312737912283762231311450
DenseNet1211312446772441150311293

推論の処理時間(Google Corab CPU, GPU)

Raspberry Pi 3 B+ での実行


  • Integer、Full Integer quant modelは、整数量子化による効果が確認できる。
  • Weight quant modelは、モデルによってTF-Lite modelとの差がある。
    独自のCNNモデルのみ処理時間が早くなっているが、それ以外のモデルでは遅くなっている。おそらく、モデルの構造に影響すると推測。
  • また、Weight quant model はマルチスレッドの効果がほとんど無いことも確認できる。
    これは、マルチスレッドをサポートしていないためである。
  • TF-Lite、Float16 quant、Integer、Full integer quant model でマルチスレッドの効果が確認できる。ただし、Jetson Nanoもだが、3スレッド以上は効果が小さくなる。このため、モバイルなどのメニーコアのCPUなどでむやみにスレッド数を指定しても効果はない可能性がある。

Model
set_num_threads
Time per inference, in milliseconds (iterations = 20)
TF-Lite model
Post quantization model
Weight quantFloat16 quantInteger quantFull integer quant
Original CNN
1216942145860
2181831823636
3172811693131
4169801692928
MobileNet v2 1.0
1366452361272274
2239418240154153
3251422240118116
42184152179998
Inception v3
1212924622169841845
2117524581168456458
38242457824327329
47042452702263266
DenseNet121
125842670253411651187
2148126531493715719
3116426531176571568
4113026531156509498


推論の処理時間(Raspberry Pi 3 B+ Original CNN)

推論の処理時間(Raspberry Pi 3 B+ MobileNet v2 1.0

推論の処理時間(Raspberry Pi 3 B+ Inception v3

推論の処理時間(Raspberry Pi 3 B+ DenseNet121

Jetson Nanoでの実行


  • こちらも、Raspberry Pi と同じ特性が見られる。
  • Raspberry Pi 3 B+と比較すると、おおよそ1/2〜1/3の処理時間である。
    これは、CPU、メモリ性能とOS(32 or 64 bit)が影響している。
    また、最適化も影響している可能性もある(TF2.0以降にARM 32bitの最適化がかなり入っていそうで、差が縮む可能性あり)
  • エッジ(ARM CPU)では、Integer quant、Full integer quant modelが適していそう(精度とのトレードオフ)。
    精度を重視するのであれば、TF-Lite modelが適している。

Model
set_num_threads
Time per inference, in milliseconds (iterations = 20)
TF-Lite model
Post quantization
Weight quantFloat16 quantInteger quantFull integer quant
Original CNN
16250783030
24543601616
34142551212
43941541010
MobileNet v2 1.0
11522331519898
289214896060
378213784747
483212824141
Inception v3
146434390461519061887
226784381271110341031
3200543642030754760
4182043601836608683
DenseNet121
1128615211301566574
27301525729360362
35641520570295293
45201523517262261

推論の処理時間(Jetson Nano Original CNN

推論の処理時間(Jetson Nano MobileNet v2 1.0

推論の処理時間(Jetson Nano Inception v3

推論の処理時間(Jetson Nano DenseNet121

Edge TPU(USB Accelerator)での実行


Raspberry Pi、Jetson Nanoでの実行を比較
  • USB2.0、3.0の効果が確認できる。

Time per inference, in milliseconds (iterations = 20)
ModelDeviceEdge TPU model
Original CNN
Raspberry Pi 3 B+706
Jetson Nano63
MobileNet v2 1.0
Raspberry Pi 3 B+13
Jetson Nano3
Inception v3
Raspberry Pi 3 B+488
Jetson Nano46




その他

  • Float16 quant modelはAndroidの PU delegateで確認したい。
    (持っているスマホでできるかな?)
  • DenseNetのEdge TPU modelの実行を確認したい。
    (次のアップデートで対応されるかな?)
  • Weight quant modelの精度を落ちる理由は?
    (Issueで報告してみる?)
  • Raspberry Pi 3 B+の64bitってどうなの?
  • Object detection modelを試してみたい。
  • tflite_convertはどうなった?

最後に

Post training quantizationについて、学習から推論まで一通りをまとめてみた。
精度と処理時間(とファイルサイズ)はトレードオフの関係にあるため、H/Wや目的にあわせてチョイスする必要がある。
TensorFlow Lite 2019 Roadmapにもあるとおり、まだ大幅な機能追加があるため、将来この内容は役に立たない可能性があることに注意(ただし、大きな流れは変更ないはず?)。