2021年1月17日日曜日

Raspberry PiでTensorFlow Lite Support Task Libraryをやってみる。

目的


TensorFlow Lite Support Task Library (C++)をつかってRaspberry Pi4で動くカメラキャプチャのサンプルを作ってみる。
TensorFlow LiteのAPIと比べて簡単に実装できるのかを確認してみる。


動機


2020年9月ぐらいから tensorflow / tflite-support のリポジトリに気がついたり、公式ブログでもアナウンスがあったりしたので気になっていた。
ようやく年末年始の休みにやってみることにした。




TensorFlow Lite Support Task Libraryとは?


公式のドキュメントは日本語にも翻訳されている。
自分の理解は以下。
  • TensorFlow Lite APIよりも簡単に扱うことができるAPIを用意。
  • タスク(画像分類、物体検出、自然言語処理、...etc)ごとにAPIを用意。
  • モデルのInput / Outputの形式(Float, INT, Shape...)を気にしなくてよい。
TensorFlow Lite APIと比べて扱いやすいAPIを用意することが目的と推測。

また、TensorFlow Lite Support Task Library は、他のライブラリを含めてTensorFlow Lite Supportと称している模様。

TensorFlow Lite Supportに含まれるライブラリは
  • TensorFlow Lite Support Library
  • TensorFlow Lite Model Metadata
  • TensorFlow Lite Support Codegen Tool
  • TensorFlow Lite Support Task Library ← 今回はこれ
があり、これらを使うことで特にモバイルのアプリの作成やデプロイを簡単にしようとする目的だと推測。

以下、簡単な特徴を記載。
これらは今後変更になる可能性があるので注意。


サポート言語


Native、Android、iOSの各プラットフォームで開発できる言語をサポートしている。
  • Java
  • C++ (WIP)
  • Swift (WIP)


用意されているタスク


2021.01.17時点では以下がサポートされている。

画像系

自然言語系


タスクを自作するには?


上記のタスク以外で、独自のタスクを実装することも可能。


モデル


通常のTensorFlow Lite モデルも可能だが、metadataを追加したTensorFlow Lite モデルを扱うことができる。TensorFlow Lite モデルにmetadataを追加することで
  • Task LibraryがInput, Outputの違い(型、サイズ)を吸収してくれる
    (アプリがリサイズ、型変換を意識しなくてよい)
  • Labelファイルが不要となる(モデルに埋め込める)。
    (モデルとラベルを一元管理でき、Task Libraryが結果からラベルを返してくれる)
といった利点が出てくる。

また、metadataにはモデルの説明や著作権などのライセンス情報も埋め込むことができる。これはOn-deviceな実行の場合、モデルが端末に配布(ダウンロード)される。ユーザーにはモデルが見えるので、ライセンスが明示できることは非常にありがたいと思う。Labelもモデルに埋め込めれば管理も容易になる。

TensorFlow Hubではmetadataが組み込まれたTensorFlow Liteモデルがある。例えば、ssd_mobilenet_v1を確認するとmetadetaにはモデルの説明、ライセンス、Input、Outputの詳細が確認できる。

metadataを追加したTensorFlow Lite モデルについては、次回以降にもう少し詳細化してみたい。


ラズパイ4でカメラキャプチャのサンプルを作る


今回は、画像のタスクのサンプル(CLI Demos for C++ Vision Task APIs)を参考に、ラズパイ4(64bit)とPiCameraでカメラキャプチャでタスクを実行するサンプル(C++)を作った。

Task Library(C++)の使い勝手を確認してみる。

元のリポジトリからForkしたリポジトリを作成。




用意したサンプル


画像で用意されているタスクのサンプルを作成してみた。


環境


自分の環境は以下。
  • Raspberry Pi 4 4GB
  • Raspberry Pi OS 64bit
  • Raspberry Pi Camera Module V2.1(UVCカメラでもOKなはず)


ビルド環境の準備と必要なモジュールのインストール


サンプルは元リポジトリと同様Bazelをつかってビルドしている。また、カメラキャプチャするためOpenCVを利用。ビルドはクロスコンパイルしたかったが、今回はHostのラズパイでビルドする。
クロスコンパイルするにはMediaPipeのサンプルと同じようにDokcerで行なえばいいと思う。

# Install required library
$ sudo apt install git libopencv-dev

# Install build tool.
$ wget https://github.com/bazelbuild/bazel/releases/download/3.7.2/bazel-3.7.2-linux-arm64
$ chmod +x bazel-3.7.2-linux-arm64
$ sudo mv bazel-3.7.2-linux-arm64 /usr/local/bin/bazel
$ sudo apt install openjdk-11-jdk


ビルド


リポジトリをClone後、それぞれをビルド。ビルド時間はおおよそ15~20分程度。
# Clone repository
$ git clone https://github.com/NobuoTsukamoto/tflite-support.git
$ cd tflite-support


Image Classifier


# Build Image Classifier
$ bazel build \
    --verbose_failures \
    tensorflow_lite_support/examples/task/vision/pi/image_classifier_capture


Object Detector


# Build Image Classifier
$ bazel build \
    --verbose_failures \
    tensorflow_lite_support/examples/task/vision/pi/object_detector_capture


Image Segmenter


# Build Image Classifier
$ bazel build \
    --verbose_failures \
    tensorflow_lite_support/examples/task/vision/pi/image_segmenter_capture


実行


TensorFlow Hubからmetadataが組み込まれたTensorFlow Liteモデルをダウンロードして実行する。


Image Classifier


# Download the model
$ curl \
   -L 'https://tfhub.dev/google/lite-model/aiy/vision/classifier/birds_V1/3?lite-format=tflite' \
   -o ./aiy_vision_classifier_birds_V1_3.tflite

# Run the classification tool.
$ ./bazel-bin/tensorflow_lite_support/examples/task/vision/pi/image_classifier_capture \
    --model_path=./aiy_vision_classifier_birds_V1_3.tflite \
    --num_thread=4


Object Detector


# Download the model.
$ curl \
   -L 'https://tfhub.dev/tensorflow/lite-model/ssd_mobilenet_v1/1/metadata/2?lite-format=tflite' \
   -o ./ssd_mobilenet_v1_1_metadata_2.tflite

# Run the detection tool.
$ ./bazel-bin/tensorflow_lite_support/examples/task/vision/pi/object_detector_capture \
    --model_path=./ssd_mobilenet_v1_1_metadata_2.tflite \
    --score_threshold=0.5 \
    --num_thread=4




Image Segmenter


# Download the model.
$ curl \
    -L 'https://tfhub.dev/tensorflow/lite-model/deeplabv3/1/metadata/1?lite-format=tflite'  \
    -o ./deeplabv3_1_metadata_1.tflite

# Run the segmantation tool.
$ ./bazel-bin/tensorflow_lite_support/examples/task/vision/pi/image_segmenter_capture \
    --model_path=./deeplabv3_1_metadata_1.tflite \
    --num_thread=4




ハマったこと


WORKSPACEにOpenCVのBUILDを追加したところビルドエラーが発生。
/usr/include/c++/8/cstdlib:75:15: fatal error: stdlib.h: No such file or directory
#include_next <stdlib.h>

詳細はこのIssueにある通りで、ビルドのパラメータに「build --spawn_strategy=standalone」が指定されていると、/usr/includeのインクルードパスが追加されなくなるためである模様。
ビルドパラメータを削除して対応した。


TensorFlow Lite Support Task Libraryの使いやすさ


ある程度、サンプルを実装してみた感想。画像系のタスクでの結果なので、自然言語系のタスクは触れていないので注意。


モデルのロード(タスクの生成)


モデルのロードはそのタスクのクラスを生成することで実現する。モデルパス、閾値やthread数などのパラメータもオプションとして指定する。

物体検出タスクだとこんな感じでObjectDetectorのインスタンスを生成。
  // Build ObjectDetector.
  const ObjectDetectorOptions& options = BuildOptions();
  ASSIGN_OR_RETURN(std::unique_ptr<ObjectDetector> object_detector,
                   ObjectDetector::CreateFromOptions(options));

オプションの指定(BuildOptions関数)はこんな感じ。
ObjectDetectorOptions BuildOptions() {
  ObjectDetectorOptions options;
  // モデルパスを指定
  options.mutable_model_file_with_metadata()->set_file_name(
      absl::GetFlag(FLAGS_model_path));
  // 出力の最大数
  options.set_max_results(absl::GetFlag(FLAGS_max_results));
  // 推論でのスレッドの並列数
  options.set_num_threads(absl::GetFlag(FLAGS_num_thread));
  // スコアの閾値
  if (absl::GetFlag(FLAGS_score_threshold) >
      std::numeric_limits<float>::lowest()) {
    options.set_score_threshold(absl::GetFlag(FLAGS_score_threshold));
  }
  // 出力クラスのホワイトリスト
  for (const std::string& class_name :
       absl::GetFlag(FLAGS_class_name_whitelist)) {
    options.add_class_name_whitelist(class_name);
  }
  // 出力クラスのブラックリスト
  for (const std::string& class_name :
       absl::GetFlag(FLAGS_class_name_blacklist)) {
    options.add_class_name_blacklist(class_name);
  }
  return options;
}                 

スコアの閾値や出力数の個数はアプリ側で制御すると煩雑になりがちなのでオプションで指定できるのは便利。また、画像分類、物体検出タスクでは出力のホワイトリスト、ブラックリストが指定できる。TensorFlow Hubなどで用意されたPre-trainedモデルを使う場合で、必要なクラスを制御したいときは便利だと思う。

各クラスのオプションはprotoとして記述されている。以下を参照。

画像系

自然言語系にはオプションはないように見える?


入力(画像)


TF-Liteで面倒なのは画像の入力だと思う。

モデルにあわせてリサイズや型(Float、INT)の変換、標準化が必要。必要な情報はモデルから取得できるのだが、すべての形式にあわせようとするとかなり冗長なコードとなってしまう。自分はFloat、INTモデルに限らず、モデルの入力はINT8で統一してしまうことでリサイズだけ意識するようにしている。ただ、Floatモデルの場合はINT⇒Floatへのdequantizedを挟むのでほんの少しだけもったいない気がする。

Task Libraryではモデルの入力を意識する必要がない(Libraryが吸収してくれる)。
アプリは画像データをFlatBuffer形式するだけ。各入力データの形式(Gray, RGB, RGA, YUV, Raw)からFlatBufferに変換するIFが用意されている。

OpenCVでカメラキャプチャしたデータの場合、BGR⇒RGBに変換、CreateFromRgbRawBufferを使ってFlatBufferを生成すればよい。CreateFromRgbRawBufferの引数にはcv::MatのサイズとRawデータを与える。
    cap >> frame; // capture frame.
    cv::cvtColor(frame, input_im, cv::COLOR_BGR2RGB); // BGR to RGB

    // Frame in a FrameBuffer.
    std::unique_ptr<FrameBuffer> frame_buffer;
    frame_buffer = CreateFromRgbRawBuffer(input_im.data, {input_im.cols, input_im.rows});                

2,3行で入力データが生成できるのはとてもありがたい。

また、CreateFromRgbRawBufferの引数には画像の向きも指定できる(引数のFrameBuffer::Orientation orientation)。
モバイルの場合、9軸センサーの値などから、スマホの向きを考慮して画像の向きを考える必要がある。Task Libraryではアプリで画像を回転する必要がなく、ライブラリ内部で回転してくれる。

画像のデータだけでなくて、サイズや向き、フォーマットを指定するのでFlatBufferで入力データを指定するということで理解した。


推論


推論自身は各タスクのメソッド(Classify、Detect、Segment、etc...)を呼び出し、入力データのFlatBufferを指定してあげる。
さほどTensorFlow Lite APIのinvokeと変わらない。
    // Run object detection and draw results on input image.
    ASSIGN_OR_RETURN(DetectionResult result,
                     object_detector->Detect(*frame_buffer));          

入力したデータはどのように変換されるかはここに記載がある。
  • RGBAやYUVなどの形式の場合はRGBに変換される。
  • アスペクトを維持せず、モデルの入力サイズにリサイズ。
    (アスペクト比を維持しないので要注意)
  • Orientationのパラメータによって、画像を回転して推論。


出力


画像分類、物体検出タスクの場合、それぞれ結果はClassificationResult、DetectionResult
として取得することができる。生のOutputTensorを意識する必要はない。

物体検出タスクの場合は下記のようにBoundingBoxの位置やサイズ、クラスのラベル名が取得できる。モデルにラベルが組み込まれていれば、出力クラスのindexから該当のラベルの文字列をとってくる処理も不要になる。これはほんとに便利。
DrawCaptionはOpenCVの文字列描画を行う独自の関数)
absl::Status EncodeResultToMat(const DetectionResult& result,
                               cv::Mat& image) {
  for (int index = 0; index < result.detections_size(); ++index) {
    // Get bounding box as left, top, right, bottom.
    const BoundingBox& box = result.detections(index).bounding_box();
    const Detection& detection = result.detections(index);
    const int x = box.origin_x();
    const int y = box.origin_y();
    const int width = box.width();
    const int height = box.height();

    // Draw. Boxes might have coordinates outside of [0, w( x [0, h( so clamping
    // is applied.
    cv::rectangle(image, cv::Rect(x, y, width, height), kBuleColor, kLineThickness);

    // Draw. Caption.
    std::ostringstream caption;

    if (detection.classes_size() == 0) {
      caption << "  No top-1 class available";
    } else {
      const Class& classification = detection.classes(0);

      if (classification.has_class_name()) {
        caption << classification.class_name();
      } else {
        caption << classification.index();
      }
      caption << " (" << std::fixed << std::setprecision(2) << classification.score() << ")";
      DrawCaption(image, cv::Point(x-3, y), caption.str());
    }
  }

  return absl::OkStatus();
}           

また、Image Segmenterの場合、ループ処理でOutputとのマスクをとる必要がある。OpenCVを利用している場合はcv::Mat::forEachでループしてあげれば並列化も期待できると思う。なお、このサンプルではcolored_labelsでLabelごとのcolormap(いわゆるPASCAL VOCのcolormap)で色付けしている。
std::unique_ptr<cv::Mat> EncodeMaskToMat(const SegmentationResult& result) {
  if (result.segmentation_size() != 1) {
    std::cout << "Image segmentation models with multiple output segmentations are not "
        "supported by this tool." << std::endl;
    return nullptr;
  }
  const Segmentation& segmentation = result.segmentation(0);
  // Extract raw mask data as a uint8 pointer.
  const uint8* raw_mask =
      reinterpret_cast<const uint8*>(segmentation.category_mask().data());

  // Create RgbImageData for the output mask.
  auto seg_im = std::make_unique<cv::Mat>(cv::Size(segmentation.width(), segmentation.height()), CV_8UC3);
  auto wdith = seg_im->cols;
  seg_im->forEach<cv::Vec3b>([&](cv::Vec3b &src, const int position[2]) -> void {
    size_t index = position[0] * wdith + position[1];
    Segmentation::ColoredLabel colored_label =
        segmentation.colored_labels(raw_mask[index]);
        src[0] = colored_label.b();
        src[1] = colored_label.g();
        src[2] = colored_label.r();
    });
  
  return seg_im;
}        


感想


使ってみた感想


Native C++しか使っていないが、

使いやすい点
  • TensorFlow Liteモデルの入出力の型、サイズを意識しなくてよいので実装がとても楽。
  • TensorFlow Lite APIを使た場合と比べて1/2~1/3の実装で済む。
  • OpenCVを使っても実装が楽。とくにInputをFlatBufferへの変換。
    (たぶんこれはAndroid、iOSの場合もそうかも?)

使いにくかった点
  • Bazelを使ったビルド(これは自分が慣れていないせいもある)。
    とくにOpenCVを追加したらなぜかビルドエラー。。。
  • APIのリファレンスがまだ整備されていない。
    まだ正式リリースでもない状態なので仕方がない。今後に期待。


MediaPipeとは何が違うの?


MediaPipeもTensorFlow Lite モデルを扱うことができるクロスプラットフォームなライブラリである。
MediaPipeとTask Libraryを比べた場合、
  • MediaPipeは画像系のタスクに特化、Task Libraryは画像以外のタスクも可能。
  • MediaPipeは推論部分だけでなくて、前処理、後処理も含めてのフレームワーク。
    作成したコンポーネントを再利用可能として、開発を容易とする。
    Task Libraryは推論部分の実装を容易とする。
で、それぞれ目的が異なると思う。
TF-Liteモデルを使ってお手軽にアプリを実装したい場合は、Task Libraryを使うほうが良いと思う。MediaPipeは簡単にという訳にはいかない。ある程度、MediaPipeのFrameworkとしての内容を理解していないと難しいと思う。


Coralとは何が違うの


Coral EdgeTPUのPyCoral API(Python)libcoral API(C++)もTF-Liteモデルを扱うことができる(EdgeTPU delegateだけでなくて)。どちらもTensorFlow Lite モデルをより簡単に扱うためのAPIを提供しているようにも見える。
  • Task LibraryはEdgeTPU delegateができない。
  • Pythonで扱うことができるAPIはPyCoralのみ。
で、EdgeTPUを扱う場合はPyCoral、libcoarl APIを扱う以外はないのが現状。2つのライブラリがわかれているのがもったいない気もするけど、、、


次は?


今回はTensorFlow Lite Support Task LibraryのNative C++を扱ってみた。楽に実装ができるのはいいねと思うけど、あとはBazelとかドキュメントが充実してくるといいなぁと思う。

つぎはTask Libraryとセットで必要になるTensorFlow Liteモデルへのmetadataの組み込みをやってみよう。

2020年11月21日土曜日

Fedora 33でTensorflow 2.4-rc2(CUDA11.1 cuDNN8.0.5)をビルドする

目的


Tensorflowの2.4-rc2をFedora 33でソースビルドする。
2.4の正式版リリースに向けての準備と備忘録。


環境


  • Fedora 33 x86_64
  • python 3.9.0(virtualenv)
  • GCC 10.2.1
  • CUDA 11.1 + cuDNN 8.0.5
  • CPU AMD Ryzen 7 1700
  • GPU GeForce GTX 1070


事前の準備


GCC9のビルド


前回の記事
と同様、CUDA 11.1がサポートするGCCは9で、Fedora 33のGCC10ではビルドができない。このため、まずはGCC9のビルドを行う。
GCCのビルドについては、以前の記事を参照。


GCC9.3のソースダウンロード&ビルド


ソースをダウンロードし、ビルドする。

$ wget https://ftp.gnu.org/gnu/gcc/gcc-9.3.0/gcc-9.3.0.tar.gz
$ cd gcc-9.3.0/
$ ./contrib/download_prerequisites 
$ mkdir build
$ cd build/
$ ../configure \
    --enable-bootstrap \
    --enable-languages=c,c++ \
    --prefix=/home/xxxx/gcc/9.3 
    --enable-shared \
    --enable-threads=posix \
    --enable-checking=release \
    --disable-multilib \
    --with-system-zlib \
    --enable-__cxa_atexit \
    --disable-libunwind-exceptions \
    --enable-gnu-unique-object \
    --enable-linker-build-id \
    --with-gcc-major-version-only \
    --with-linker-hash-style=gnu \
    --enable-plugin \
    --enable-initfini-array \
    --with-isl \
    --enable-libmpx \
    --enable-gnu-indirect-function \
    --build=x86_64-redhat-linux
$ make -j16
$ make install


specsファイルの作成


コンパイルしたGCC9でビルドした際に、適切な動的リンクライブラリ(libstdc++.so)がリンクされるようにSPECEファイルを修正する。

$ /home/xxxx/gcc/9.3/bin/gcc -dumpspecs > specs
$ vi specs

# before
*link_libgcc:
%D

# after
*link_libgcc:
%{!static:%{!static-libgcc:-rpath /home/xxxx/gcc/9.3/lib64/}} %D

$ mv specs /home/xxxx/gcc/9.3/lib/gcc/x86_64-redhat-linux/9/


Environment Modulesの設定


GCC8をEnvironment Modulesで切り替えられるようにする。/etc/modulefiles 配下に、gcc9xのファイルを作成する。

#%Module 1.0
#
#  gcc-9.X module for use with 'environment-modules' package:
#

conflict        gcc5x gcc7x gcc9x
prepend-path    PATH                    /home/xxxx/gcc/9.3/bin/


Bazelのビルド


Bazel をソースビルドする。最新の3.7をソースビルドした。
公式のソースビルド方法はここを参照。手順どおりであり詳細の説明は割愛。


CUDA、cuDNNのインストール


CUDA: 11.1、cuDNN: 8.0.5をインストール。CUDAはRPM Fusion Howto/ CUDA を参考にインストールを行う。Which driver Packageにもあるとおり、RPM FusionとCUDAのリポジトリの両方にnvidia driverが存在するが、バージョンの不一致を起こしてしまうことがある。手順どおりにインストールしないとCUDAとnvidia driverの不一致で使えない。。。
cuDNNはNVIDIAのダウンロードサイトからダウンロード、インストールを行う。


Tensorflowのビルド


さて、本題。TensorFlow 2.4-rc1をビルドする。


virtualenvの設定


まずはvirtualenv(virtualenvwapper)でTensorflow用の仮想Python環境を作成し、必要なモジュールをインストールする。

$ mkvirtualenv -p python3  tf2.4-rc2
$ pip install pip six numpy wheel setuptools mock 'future>=0.17.1'
$ pip install keras_applications --no-deps
$ pip install keras_preprocessing --no-deps

ビルド


Githubからソースを取得し、configureスクリプト実行し、ビルドを行う。
  • CUDAのサポートを有効とする。
  • Host compilerにGCC9のgccのパスを指定してあげる。
  • ビルドオプションには"--config=v2"と"--config=nonccl "を指定。


(tf2.4-rc1) $ wget https://github.com/tensorflow/tensorflow/archive/v2.4.0-rc1.tar.gz
(tf2.4-rc1) $ tar xf v2.4.0-rc1.tar.gz 
(tf2.4-rc1) $ cd tensorflow-2.4.0-rc1/
(tf2.4-rc1) $ ./configure 
(tf2.4-rc1) $ bazel build \
                --config=opt \
                --config=v2 \
                --cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0" \
                --config=cuda \
                --config=nonccl \
                --verbose_failures \
                //tensorflow/tools/pip_package:build_pip_package
(tf2.4-rc1) $ ./bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg
(tf2.4-rc1) $ pip install /tmp/tensorflow_pkg/tensorflow-2.4.0rc2-cp39-cp39m-linux_x86_64.whl


インストール確認


  • tf.__version__が2.4-rc1であること。
  • GPUデバイスを認識していること。

(tf2.4-rc1) $ python
    Python 3.9.0 (default, Oct  6 2020, 00:00:00) 
[GCC 10.2.1 20200826 (Red Hat 10.2.1-3)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import tensorflow as tf
2020-11-21 09:17:05.361081: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.11.0
>>> from tensorflow.python.client import device_lib
>>> device_lib.list_local_devices()
2020-11-21 09:17:17.759422: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcuda.so.1
2020-11-21 09:17:17.800045: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:17.803501: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1720] Found device 0 with properties: 
pciBusID: 0000:09:00.0 name: GeForce GTX 1070 computeCapability: 6.1
coreClock: 1.7085GHz coreCount: 15 deviceMemorySize: 7.93GiB deviceMemoryBandwidth: 238.66GiB/s
2020-11-21 09:17:17.803527: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.11.0
2020-11-21 09:17:17.805845: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcublas.so.11
2020-11-21 09:17:17.805916: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcublasLt.so.11
2020-11-21 09:17:17.806788: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcufft.so.10
2020-11-21 09:17:17.816671: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcurand.so.10
2020-11-21 09:17:17.854541: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcusolver.so.11
2020-11-21 09:17:17.862372: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcusparse.so.11
2020-11-21 09:17:17.961227: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudnn.so.7
2020-11-21 09:17:17.961410: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:17.962245: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:17.962836: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1862] Adding visible gpu devices: 0
2020-11-21 09:17:17.963571: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.11.0
2020-11-21 09:17:18.745397: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1261] Device interconnect StreamExecutor with strength 1 edge matrix:
2020-11-21 09:17:18.745448: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1267]      0 
2020-11-21 09:17:18.745463: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1280] 0:   N 
2020-11-21 09:17:18.747106: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:18.747776: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:18.748358: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:941] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-11-21 09:17:18.748909: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1406] Created TensorFlow device (/device:GPU:0 with 7120 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1070, pci bus id: 0000:09:00.0, compute capability: 6.1)
2020-11-21 09:17:18.751666: I tensorflow/compiler/jit/xla_gpu_device.cc:99] Not creating XLA devices, tf_xla_enable_xla_devices not set
[name: "/device:CPU:0"
device_type: "CPU"
memory_limit: 268435456
locality {
}
incarnation: 12838272530603917041
, name: "/device:GPU:0"
device_type: "GPU"
memory_limit: 7466015808
locality {
  bus_id: 1
  links {
  }
}
incarnation: 3621828384593309124
physical_device_desc: "device: 0, name: GeForce GTX 1070, pci bus id: 0000:09:00.0, compute capability: 6.1"
]

OK!

2020年11月3日火曜日

YoctoでTensorFlow Lite (Build pip package)

目的


YoctoでTensorFlow Liteのpip packageをビルド、インストールするレシピを作成する。
bitbakeしたイメージをラズパイ4で動かしてみる。


動機


TensorFlowのレシピはあったりするが(以前ブログにした)、TensorFlow LiteのPython interpreterのレシピがなかったこと、あとからpipでインストールするのが結構めんどくさいので、レシピを作成することにした。


meta-tensorflow-lite


meta-tensorflow-liteとしてGitHubにレシピを公開している。v2.3.1に対応。



リファレンス


TensorFlow Liteのビルドは以下が参考になる。


ビルド


リポジトリのREADMEにもある通り。
  • 確認はラズパイ4の32 / 64bitで実施。
  • イメージはcore-image-weston(他も動くはず)。
  • dunfell, zeusに対応。
    zeusは人生初めてPRをもらった!(Support also zeus version #1

必要なリポジトリをClone。
$ git clone git://git.yoctoproject.org/poky.git
$ git clone git://git.yoctoproject.org/meta-raspberrypi
$ git clone git://git.openembedded.org/meta-openembedded
$ git clone https://github.com/NobuoTsukamoto/meta-tensorflow-lite.git
$ source poky/oe-init-build-env rpi-build

レイヤーの追加
$ bitbake-layers add-layer ../meta-openembedded/meta-oe/
$ bitbake-layers add-layer ../meta-openembedded/meta-python/
$ bitbake-layers add-layer ../meta-openembedded/meta-networking/
$ bitbake-layers add-layer ../meta-openembedded/meta-multimedia/
$ bitbake-layers add-layer ../meta-raspberrypi/
$ bitbake-layers add-layer ../meta-tensorflow-lite/

conf/local.confにpython3-tensorflow-liteを追加。あと、opencvのpythonやgitも追加すると楽。
MACHINE ?= "raspberrypi4-64"
IMAGE_INSTALL_append = " python3-tensorflow-lite"

あとはBitbakeしてSDカードに書き込み。
$ bitbake core-image-weston

このリポジトリでTensorFlow Liteモデル(Float, INT8)が動くことを確認。
(注意:Edge TPUは動作しません)




その他



やったこと


tensorflow/lite/tools/pip_package/build_pip_package.shでpip packageを作成してインストールするレシピを作成。



PiCameraが有効にならなかった


meta-rasberrypiの設定でPiCameraを有効としてもまったく認識しなくて1週間悩んでいた...
Interface誌で「My オリジナルLinuxの作り方」連載されている!みつきんさん(@yusuke_mitsuki)!!にもアドバイスいただいたのだが認識せず。。。(その節はありがとうございます!)


結局はboot/config.txtのPiCameraを有効にする「start_x=1」を643行目?以降に記述するとカメラを認識できなくなるようだ...
同じような事象で困っていた人は他にもいたようだ...

(個体なのか何なのか)認識する場合もあるので、もし、ラズパイ4で同じ事象があったら、行数を意識すること。


終わりに


あとは、Edge TPUも動かしてみたいなぁ...

2020年6月6日土曜日

Jetson NanoでTF-TRTを試す(JetPack 4.4 Developer Preview)

目的


(ずいぶん前になるが)
JetPack 4.4 Developer Previewがリリースされたので、TF-TRT(TensorFlow integration with TensorRT)を使ってFP16に最適化したモデルでの処理時間を計測する。
また、TensorFlow Object Detection APIにも新しいモデル(MnasFPN、MobileDets)が追加されたので試してみる。


まとめ


  • SSDLite MobileNet V3は前回(JetPack4.3 + TF1.15)より多少の改善がみられる。
  • MnasFPNは思った効果(推論時間)は確認できなかった。
  • MobileNet V3がMobileNet V2より有効(推論時間がはやい)ことが確認できた。
  • Object detection modelではMobileDetsが良さそう。


インストール


JetPack4.4、TensorFlow、TF-TRT modelsのインストールを行う。


JetPack


JetPack SDKからJetPack 4.4 Developer PreviewのSDイメージをダウンロードして、SDカードに書き込み。


TensorFlow


TensorFlow on Jetson Platform をもとにTensorFlowをインストールする。
注意することは、バージョンは1.xをインストールすること(TensorFlow Object Detection APIは1.x系のみ対応)。
インストール時は‘tensorflow<2’をつける。
※2020.6.6時点では1.15.2がインストールされる。


TF-TRT Models


TF-TRT Models(TensorFlow/TensorRT Models on Jetson)のリポジトリをcloneし、インストールする。
なお、オリジナルのリポジトリからforkして、変更を加えている。
  • submodule(TensorFlow Model Garden)を更新。
    (MobileDetsを変換したいため)
  • 変換スクリプト、ベンチマークスクリプトを追加



まず、TensorFlow Object Detection APIに必要なパッケージをインストールする。

$ sudo apt install pyton3-tk python3-matplotlib
$ pip3 install --user tf_slim

その後、TF-TRT Modelsのリポジトリをcloneし、インストールスクリプトを実行する。

$ git clone https://github.com/NobuoTsukamoto/tf_trt_models.git
$ cd tf_trt_models/
$ ./install.sh python3

※Python3での実行のみ確認している。


モデルの変換


変換スクリプトを使用して、TF-TRT FP16モデルに変換する。


Object detection model


pre-trained modelの場合は、prefixを指定すれば変換する。
ただ、mobiledetはフォルダの構成が他と違うため手動でダウンロード&変換が必要。
今回の対象モデルは以下。
  • ssdlite_mobilenet_v2 (※)
  • ssdlite_mobilenet_v3_small
  • ssdlite_mobilenet_v3_large
  • ssd_mobilenet_v2_mnasfpn
  • ssdlite_mobiledet_cpu
  • ssdlite_mobiledet_edgetpu
  • ssdlite_mobiledet_dsp
  • ssdlite_mobilenet_edgetpu

変換は以下のように実行

cd examples/detection/
$ python3 convert.py --model=ssdlite_mobilenet_v2_coco
$ python3 convert.py --model=ssdlite_mobilenet_v3_small_coco --force_nms_cpu
$ python3 convert.py --model=ssdlite_mobilenet_v3_large_coco --force_nms_cpu
$ python3 convert.py --model=ssd_mobilenet_v2_mnasfpn_coco --force_nms_cpu

# ssdlite_mobilenet_edgetpuはwgetできないのでリンクをクリックしてダウンロード
$ cd data/
$ tar xf checkpoints_ssdlite_mobilenet_edgetpu_coco_quant.tar.gz
$ cd ../
$ python3 convert.py --path=data/ssdlite_mobilenet_edgetpu_coco_quant/ --force_nms_cpu

$ cd data/
$ wget http://download.tensorflow.org/models/object_detection/ssdlite_mobiledet_cpu_320x320_coco_2020_05_19.tar.gz
$ tar xf ssdlite_mobiledet_cpu_320x320_coco_2020_05_19.tar.gz
$ cd ssdlite_mobiledet_cpu_320x320_coco_2020_05_19
$ mv model.ckpt-400000.data-00000-of-00001  model.ckpt.data-00000-of-00001
$ mv model.ckpt-400000.index model.ckpt.index
$ mv model.ckpt-400000.meta model.ckpt.meta
$ cd ../../
$ python3 convert.py --path=data/ssdlite_mobiledet_cpu_320x320_coco_2020_05_19 --force_nms_cpu

$ cd data/
$ wget http://download.tensorflow.org/models/object_detection/ssdlite_mobiledet_edgetpu_320x320_coco_2020_05_19.tar.gz
$ tar xf ssdlite_mobiledet_edgetpu_320x320_coco_2020_05_19.tar.gz
$ cd data/ssdlite_mobiledet_edgetpu_320x320_coco_2020_05_19/fp32/
$ mv model.ckpt-400000.data-00000-of-00001  model.ckpt.data-00000-of-00001
$ mv model.ckpt-400000.index model.ckpt.index
$ mv model.ckpt-400000.meta model.ckpt.meta
$ cd ../../
$ python3 convert.py --path=data/ssdlite_mobiledet_edgetpu_320x320_coco_2020_05_19/fp32 --force_nms_cpu

$ cd data/
$ wget http://download.tensorflow.org/models/object_detection/ssdlite_mobiledet_dsp_320x320_coco_2020_05_19.tar.gz
$ tar xf ssdlite_mobiledet_dsp_320x320_coco_2020_05_19.tar.gz
$ cd data/ssdlite_mobiledet_dsp_320x320_coco_2020_05_19/fp32/
$ mv model.ckpt-400000.data-00000-of-00001  model.ckpt.data-00000-of-00001
$ mv model.ckpt-400000.index model.ckpt.index
$ mv model.ckpt-400000.meta model.ckpt.meta
$ cd ../
$ python3 convert.py --path=data/ssdlite_mobiledet_dsp_320x320_coco_2020_05_19/fp32  --force_nms_cpu

前回のブログでもふれた「NMSをCPU実行に書き換えると変換できない」が発生するため、NMSはそのまま(GPUで実行)とする。変換スクリプトに"--force_nms_cpu"を指定する。


Image classification model


こちらも変換スクリプトを用意している。
pre-trained modelのprefixを指定すれば変換する。
今回の対象モデルは以下(Inputはすべて244x244)。
  • mobilenet_v2 (depth_multiplier=0.5)
  • mobilenet_v2 (depth_multiplier=1.0)
  • mobilenet_v2 (depth_multiplier=1.4)
  • mobilenet_v3_small
  • mobilenet_v3_small

$ cd examples/classification/
$ python3 convert.py --model=mobilenet_v2_0p5_224
$ python3 convert.py --model=mobilenet_v2_1p0_224
$ python3 convert.py --model=mobilenet_v2_1p4_224
$ python3 convert.py --model=mobilenet_v3_small
$ python3 convert.py --model=mobilenet_v3_small_224


ベンチマーク


ベンチマーク実施はMax power&CUIモードで実行する。

$ sudo systemctl set-default multi-user.target
$ sudo reboot
$ sudo jetson_clocks

Object detection model


  • ssdlite_mobilenet_v3_small / largeは前回(JetPack4.3+TF1.15)と比べると推論時間が改善している。
  • ssdlite_mobilenet_v2はNMSのCPU実行ができない分、推論時間が遅い。
  • ssdlite_mobilenet_v2_mnasfpnは想定よりかなり遅い。
    CPUに最適化しているからだろうか?
  • 精度(mAP)と推論時間のトータルを考慮するとssdlite_mobiledet_edgetpuが一番。
  • ssdlite_mobilenet_v3_small or largeも使える。

初回の推論時間

Model
Input size
Inference time [ms]
TF-TRT (FP16)
ssdlite_mobilenet_v2300x30013025
ssdlite_mobilenet_v3_small320x3208375
ssdlite_mobilenet_v3_large320x3207366
ssdlite_mobilenet_v2_mnasfpn320x32017502
ssdlite_mobiledet_cpu320x3208117
ssdlite_mobiledet_edgetpu320x3207332
ssdlite_mobiledet_dsp320x3208203
ssdlite_mobilenet_edgetpu320x32023643


2回目以降の100回の推論の平均時間

Model
Input size
Inference time [ms]
TF-TRT (FP16)
ssdlite_mobilenet_v2300x30088
ssdlite_mobilenet_v3_small320x32056
ssdlite_mobilenet_v3_large320x32068
ssdlite_mobilenet_v2_mnasfpn320x320223
ssdlite_mobiledet_cpu320x32073
ssdlite_mobiledet_edgetpu320x32070
ssdlite_mobiledet_dsp320x32080
ssdlite_mobilenet_edgetpu320x320186



Image classification model


  • mobilenet_v2 (depth_multiplier=0.5)とmobilenet_v3_smallがほぼ同等の推論時間。
  • mobilenet_v2 (depth_multiplier=1.0)とmobilenet_v3_largeがほぼ同等の推論時間。
  • FP32での精度を考えるとmobilenet_v3を使うのが良いかも?


初回の推論時間

Model
Input size
Inference time [ms]
TF-TRT (FP16)
mobilenet_v2_0.5_224224x2243477
mobilenet_v2_1.0_224224x22411354
mobilenet_v2_1.4_224224x2249356
mobilenet_v3_small_224224x2244187
mobilenet_v3_large_224224x2245387


2回目以降の100回の推論の平均時間

Model
Input size
Inference time [ms]
TF-TRT (FP16)
mobilenet_v2_0.5_224224x22410
mobilenet_v2_1.0_224224x22416
mobilenet_v2_1.4_224224x22423
mobilenet_v3_small_224224x22410
mobilenet_v3_large_224224x22417



最後に


Jetson Nano(JetPack 4.4 Developer Preview)でTF-TRT モデルの推論時間をベンチマークしてみた。

結果からMobileNet V3ベースまたは、SSDLite MobileDets Edge TPUが良い結果に思える。
FP16にした場合の制度の低下がどれぐらいかは気になるところではあるが、FP32とほぼ同等(の低下)と考えれば、MobileNet V3、MobileDetsは良い選択肢になると思う。
(あとはどのぐらい学習しやすさによるか?)

Object detection modelはモバイルCPU、Edge TPU、DSPに特化したモデルが登場した(MnasFpn、MobileDets)。
これらのモデルの中にはJetson NanoのGPUではそれほど良い結果は得られなかった。
これはモデルがHWに特化していることも要因の一つに思える。

あとは、Efficientnet、EfficientDetも試してみたい。

2020年4月25日土曜日

TensorFlow Model Optimization ToolkitでQuantization aware training in Keras

変更


2020.5.16 - QATでkeras modelとTF-Lite modelの精度の差がなくなった(問題が解消した)ので修正。


目的



tf.kerasでQuantization aware training(QAT)が実行できるTensorFlow Model Optimization Toolkit(tensorflow-model-optimization)のAPIがリリースされたので試してみる。




注意


現時点(2020.4.25)ではtensorflow-model-optimizationのQATのAPIは1.0リリース前(v0.3.0)で、experimentalなAPIも含まれている。このため、将来ここに記載された内容は変更になっている可能性がある。

また、TensorFlow 2.2.0-rc3以下では、TF-Lite integet quant modelに変換できないためtf-nightlyを使うことにも注意(参考資料のissue#38285も参照) 。

詳細はリファレンスも参照。


動機


以前、TF2.0のKerasでPost-training quantizationをブログに書いた。

しかし、Post-training quantizationは扱いやすい反面、精度が落ちる場合がある。精度をなるべく落とさない場合は、QATをしたいが、tf.kerasではサポートされていなかった。

今回、tensorflow-model-optimizationでQATのAPIがリリースされた。まずはImage ClassificationのモデルでQATを行い、TF-Lite Integer quant modelやEdge TPU Modelへの変換を試してみたかったので、今回やったことをまとめてみる。


参考資料



環境・バージョン情報


Google Colaboratoryで確認できるnotebookを作成した。
ただし、TensorFlow 2.2.0-rc3以下では、TF-Lite modelに変換できないためtf-nightly-gpuを使う。
  • tf-nightly-gpu: 2.2.0.dev20200423
  • tensorflow-model-optimization: 0.3.0


KerasのMobileNet v2でQAT


今回、tf.kerasのMobilenet v2をQATし、TF-Lite modelに変換する。
MobileNet v2のモデルでtf_flower datasetを学習する。tfsdなどの使い方はTF2.0のKerasでPost-training quantizationも参照。
Google Colaboratoryで実行できるnotebookは以下。


インストール


tensorflow-model-optimizationのインストールはpipでインストール可能。
$ pip install tensorflow-model-optimization

Import


以下でtensorflow-model-optimizationをimport。
import tensorflow_model_optimization as tfmo

モデルの生成


Functional APIやSequential modelを使ってモデルを生成する。
keras.applicationsで用意されているモデルを使う場合、Functional APIで構築する(下記の注意を参照)。
def setup_mobilenet_v2_model():
  base_model = MobileNetV2(include_top=False,
                           weights='imagenet',
                           pooling='avg',
                           input_shape=(IMG_SIZE, IMG_SIZE, 3))
  x = base_model.output
  x = tf.keras.layers.Dense(info.features['label'].num_classes,
                            activation="softmax")(x)
  model = tf.keras.Model(inputs=base_model.input, outputs=x)

  return model

モデル生成時の注意


Sequential modelの場合、入れ子となるようなモデルを構築すると量子化モデルの生成時にエラーとなる。
下記のようにモデルを定義するとエラー(ValueError: Quantizing a tf.keras Model inside another tf.keras Model is not supported.)が発生してしまうので注意。
def setup_mobilenet_v2_model():
  base_model = MobileNetV2(include_top=False,
                           weights='imagenet',
                           pooling='avg',
                           input_shape=(IMG_SIZE, IMG_SIZE, 3))
  model = tf.keras.Sequential([base_model,
                               tf.keras.layers.Dense(info.features['label'].num_classes,
                                                     activation='softmax')])

  return model

サポートモデルの注意


現時点でサポートされているImage classification modelはここ(Image classification with tools)を参照。MobileNet、ResNetあたり。
DensNetは量子化モデルの生成時にエラーとなってしまう。サポート状況は改善されるので要確認。


モデルの学習(Not QAT)


QATのを行う前にまずは通常の学習を行う。
QATは通常より学習の時間がかかるため事前の学習を行っておいた方がよい。
history = model.fit(train.repeat(),
                    epochs=10,
                    steps_per_epoch=steps_per_epoch,
                    validation_data=validation.repeat(),
                    validation_steps=validation_steps)

なお、MobileNet v2で、おおよそ1Stepあたりの学習時間は
  • QATでない通常の学習時: 20秒
  • QAT時: 40秒
なので、約2倍の差があった。


モデルの学習(QAT)


学習済みのモデルを使って量子化モデルへの生成、QATを行う。

まず、quantize_model APIでモデル全体を量子化する。
なお、APIとしては一部のレイヤーのみを量子化することも可能。
量子化したあとは、compileが必要になる。
q_aware_model = tfmo.quantization.keras.quantize_model(model)
q_aware_model.compile(optimizer = tf.keras.optimizers.RMSprop(lr=base_learning_rate),
                      loss = 'sparse_categorical_crossentropy',
                      metrics = ["accuracy"])

モデルのサマリを表示してみる。
各Layerがquantize_xxxとなり、量子化モデルになっていることがわかる。
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            [(None, 224, 224, 3) 0                                            
__________________________________________________________________________________________________
quantize_layer (QuantizeLayer)  (None, 224, 224, 3)  3           input_1[0][0]                    
__________________________________________________________________________________________________
quant_Conv1_pad (QuantizeWrappe (None, 225, 225, 3)  1           quantize_layer[1][0]             
__________________________________________________________________________________________________
quant_Conv1 (QuantizeWrapper)   (None, 112, 112, 32) 929         quant_Conv1_pad[0][0]            
__________________________________________________________________________________________________
quant_bn_Conv1 (QuantizeWrapper (None, 112, 112, 32) 129         quant_Conv1[0][0]                

〜 中略 〜

__________________________________________________________________________________________________
quant_block_16_project (Quantiz (None, 7, 7, 320)    307841      quant_block_16_depthwise_relu[0][
__________________________________________________________________________________________________
quant_block_16_project_BN (Quan (None, 7, 7, 320)    1283        quant_block_16_project[0][0]     
__________________________________________________________________________________________________
quant_Conv_1 (QuantizeWrapper)  (None, 7, 7, 1280)   412161      quant_block_16_project_BN[0][0]  
__________________________________________________________________________________________________
quant_Conv_1_bn (QuantizeWrappe (None, 7, 7, 1280)   5121        quant_Conv_1[0][0]               
__________________________________________________________________________________________________
quant_out_relu (QuantizeWrapper (None, 7, 7, 1280)   3           quant_Conv_1_bn[0][0]            
__________________________________________________________________________________________________
quant_global_average_pooling2d  (None, 1280)         3           quant_out_relu[0][0]             
__________________________________________________________________________________________________
quant_dense (QuantizeWrapper)   (None, 5)            6410        quant_global_average_pooling2d[0]
==================================================================================================

QAT(Quantization aware training)を行う。
これは、生成した量子化モデルで学習を行う。
q_aware_history = q_aware_model.fit(train.repeat(),
                                    initial_epoch=10,
                                    epochs=70,
                                    steps_per_epoch=steps_per_epoch,
                                    validation_data=validation.repeat(),
                                    validation_steps=validation_steps)

学習開始直後は、loss, accuracyが悪化するが学習を進めるとQAT直前とほぼ同等になる。
事前に学習を行っていると精度がすぐに回復するようである。

学習の結果は以下。マゼンダの縦線より左が通常の学習、右がQATである。


QATの注意


(2020.5.16 変更)
Issue #368 でQATのKeras modelとTF-Lite modelで精度の差が異なる問題が修正された。これによってQATの収束とTF-Lite modelの精度は一致する。

自分があげたissueにあるとおり、QATの学習が少ないとKeras model(量子化モデル)とTF-Lite modelの精度に差が出てしまう。

作成したサンプルは10epochs程度でloss, accuracyが収束したように見え、keras modelではtestセットで0.98〜の精度がでる。しかし、TF-Lite modelに変換すると精度が0.20(tf_flowerは5クラス!)となってしまう。

原因はこちらのissueのコメントにあるとおり、量子化のMin / Maxの初期値が-6 / 6でかなり広く、Min / Maxが収束していない状態だと量子化時に損失が発生してしまうとのこと。
また、量子化の更新はMovingAverageQuantizerに定義されていて、tensorflowのmoving_averages(EMA: 移動平均)を使って値が更新される。このときのema_decay(減衰)が0.999で収束するまでにそれなりのstep数がかかってしまうとのことである。

複雑なタスクの場合、QATでloss, accuracyが回復するまでとEMAが収束するまではほぼ同じぐらいかもしれない。しかし、tf_flowerのような簡単?なタスクの場合は、loss, accuracyのほうがはやく収束してしまうため、このような結果となってしまう。

今後、このコメントのとおり、EMAの収束も監視できるようになるといいと思う。

また、モデルによってもMEAの収束は異なる模様。以下のようなモデルの場合は、10epochsでほぼ収束していた。
def setup_cnn_model():
  # extract image features by convolution and max pooling layers
  inputs = tf.keras.Input(shape = (IMG_SIZE, IMG_SIZE, 3))
  x = tf.keras.layers.Conv2D(32, kernel_size=3, padding="same", activation="relu")(inputs)
  x = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(x)
  x = tf.keras.layers.Conv2D(64, kernel_size=3, padding="same", activation="relu")(x)
  x = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(x)
  # classify the class by fully-connected layers
  x = tf.keras.layers.Flatten()(x)
  x = tf.keras.layers.Dense(512, activation="relu")(x)
  x = tf.keras.layers.Dense(info.features['label'].num_classes)(x)
  x = tf.keras.layers.Activation("softmax")(x)
  model_functional = tf.keras.Model(inputs=inputs, outputs=x)

  return model_functional

QATのパラメータは変更が可能なので試してみてもよい。


かならず、TF-Lite modelでも精度を確認したほうがよいと思う。


TF-Lite Integer quantization Modelの変換


TF-Lit modelに変換する際は、MLIRのコンバーターが必要(ここでは、experimental_new_converter フラグをセットしているが不要になるはず)。
以下のパラメータで変換ができる。
converter = tf.lite.TFLiteConverter.from_keras_model(q_aware_model)

converter.experimental_new_converter = True
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_q_aware_integer_quant_model = converter.convert()
with open(os.path.join(models_dir, 'mobilenet_v2_q_aware_integer_quant.tflite'), 'wb') as f:
    f.write(tflite_q_aware_integer_quant_model)

TF-Lite Modelのテスト


前回のPost-training quantizationで使用したコードがそのまま使用できる(ここでは省略)。
Post-training quantizationとQATで、Keras modelからの精度低下を確認。

Test accuracykeras modelTF-Lite model
Baseline0.9730.946
QAT1.0001.000

一応、Post-training quantization ではTF-Lite modelでの精度の低下があるが、QATでは低下がない。
ただ、今回のデータセット(tf_flower)だとちょっと適切でないかも。。。


Edge TPU Modelの変換


TF-Lite Integer quant modelに変換できたので、Edge TPU Modelにも変換できるはずである。
ただし、Edge TPU Compiler version 2.1.302470888では、
  • MobileNet v1は成功
  • MobileNet v2は失敗( Internal compiler error. Aborting! )
であった。。。

今後のEdge TPU Compilerのアップデートに期待!


最後に


TensorFlow Model Optimization Toolkitを使ってKerasモデルのQuantization aware trainingを行ってみた。少しハマったポイントはあるけど、簡単にQATができる。

KerasモデルでQATができるとモバイルやEdge TPUでの利用がさらに面白くなると思う。

これからもウォッチしてみたい。

2020年3月28日土曜日

Fedora 31でTensorflow 1.15.2(CUDA10.2 cuDNN7.6.5)をビルドする

目的


前回のブログにつづき、TensorFlow 1.15.2をソースビルドする。1.x系と2.x系は両方あった方がよい。Dockerを使えばよいのだが、簡単に使える方がよいのでビルドする。


環境


前回と同様である。
  • Fedora 31 x86_64
  • python 3.7.6(virtualenv)
  • CUDA 10.2 + cuDNN 7.6.5
  • CPU AMD Ryzen 7 1700
  • GPU GeForce GTX 1070


事前準備


こちらも前回のブログと同様なので割愛。
詳細は、こちらを参照。ただし、Bazelのバージョンは0.26.1を使用する。


Tensorflowのビルド


さて、本題。TensorFlow 1.15.2をビルドする。1.x系の注意事項。

virtualenvの設定




ビルド


ソースを展開後、third_party/nccl/build_defs.bzl.tpl の116行目を削除する。これでCUDA 10.2 + cuDNN 7.6.5でのビルドができる。



ビルドを行う。途中でsys_gettidの問題でエラーとなる。
なお、/home/xxx/.cache/bazel/_bazel_xxx/aca0f394050ca263374306622f61644f のパスは、bazelのキャッシュであり毎回変わるので注意。



ビルドエラーが発生後、該当の external/grpc/src/core/lib/gpr/log_linux.cc を修正する。"gettid"を "sys_gettid"に置き換えて競合しないようにする。

ビルド完了後、pipパッケージを作成して、インストールする。


インストール確認



  • tf.__version__が1.15.2であること。
  • GPUデバイスを認識していること。

  • OK!

    Fedora 31でTensorflow 2.2-rc1(CUDA10.2 cuDNN7.6.5)をビルドする

    目的


    Tensorflowの2.2-rc1をFedora 30でソースビルドする。
    2.2の正式版リリースに向けての準備と備忘録。


    環境


    • Fedora 31 x86_64
    • python 3.7.6(virtualenv)
    • CUDA 10.2 + cuDNN 7.6.5
    • CPU AMD Ryzen 7 1700
    • GPU GeForce GTX 1070


    事前の準備


    GCC8のビルド


    前回の記事と同様、CUDA 10.2がサポートするGCCは8で、Fedora 31のGCC9ではビルドができない。このため、まずはGCC8のビルドを行う。
    GCCのビルドについては、以前の記事を参照。


    GCC8.4のソースダウンロード&ビルド


    以下でビルドを行う。


    specsファイルの作成


    コンパイルしたGCC7でビルドした際に、適切な動的リンクライブラリ(libstdc++.so)がリンクされるようにSPECEファイルを修正する。


    Environment Modulesの設定


    GCC8をEnvironment Modulesで切り替えられるようにする。/etc/modulefiles 配下に、gcc8xのファイルを作成する。
    ※筆者の環境にはソースビルドしたGCC5、GCC7がある。


    Bazelのビルド


    Bazelをソースビルドする(リポジトリのバージョンだとTensorflowが期待するバージョンと一致しないことがあるため)。2.2-rc1はBazel2.0.0が必要なため、ソースビルドした。

    公式のソースビルド方法はここを参照。手順どおりであり詳細の説明は割愛。


    CUDA、cuDNNのインストール


    CUDA: 10.2、cuDNN: 7.6.5をインストール。CUDAはRPM Fusion Howto/ CUDA を参考にインストールを行う。cuDNNはNVIDIAのダウンロードサイトからダウンロード、インストールを行う。


    Tensorflowのビルド


    さて、本題。TensorFlow 2.2-rc1をビルドする。


    virtualenvの設定


    まずはvirtualenv(virtualenvwapper)でTensorflow用の仮想Python環境を作成し、必要なモジュールをインストールする。


    ビルド


    Githubからソースを取得し、configureスクリプト実行し、ビルドを行う。

    • CUDAのサポートを有効とする。
    • Host compilerにGCC8のgccのパスを指定してあげる。
    • ビルドオプションには"--config=v2"と"--config=nonccl "(NCCLのライブラリをインストールしたがビルドエラーとなったので外す)を指定。
    • 日本語環境では依存関係のダウンロードで失敗したので、ビルド前に"LANG=C"で回避。

    なお、エラーは以下であった。



    インストール確認


    • tf.__version__が2.2-rc1であること。
    • GPUデバイスを認識していること。


    OK!