この記事について
機械学習、Deep Learningの専門家ではない人が、Deep Learningを応用したアプリケーションを作れるようになるのが目的です。MNIST数字識別する簡単なアプリケーションを、色々な方法で作ってみます。特に、組み込み向けアプリケーション(Edge AI)を意識しています。
モデルそのものには言及しません。数学的な話も出てきません。Deep Learningモデルをどうやって使うか(エッジ推論)、ということに重点を置いています。
- Kerasで簡単にMNIST数字識別モデルを作り、Pythonで確認
- TensorFlowモデルに変換してPythonで使用してみる (Windows, Linux)
- TensorFlowモデルに変換してCで使用してみる (Windows, Linux)
- TensorFlow Liteモデルに変換してPythonで使用してみる (Windows, Linux)
- TensorFlow Liteモデルに変換してCで使用してみる (Linux)
- TensorFlow Liteモデルに変換してC++で使用してみる (Raspberry Pi) <--- 今回の内容
- TensorFlow LiteモデルをEdge TPU上で動かしてみる (Raspberry Pi)
今回の内容
- TensorFlow Lite for C++のライブラリ(RaspberryPi用)を作る
- TensorFlow Lite用モデルを使って、入力画像から数字識別するC++アプリケーションを作る
TensorFLow Lite用モデルは前回作成したconv_mnist.tflite
を使います
ソースコードとサンプル入力画像: https://github.com/take-iwiw/CNN_NumberDetector/tree/master/05_TensorflowLite_CPP
環境
- OS: Windows 10 (64-bt)
- CPU = Intel Core i7-6700@3.4GHz (物理コア=4、論理プロセッサ数=8)
- OS(on VirtualBox): Ubuntu 16.04
- 開発環境: bazel 0.23.1, cmake, gcc
- Raspberry Pi 2 Model B (2018-11-13-raspbian-stretch)
全体の方針
まず、PC Linux(Ubuntu)上で、ラズパイ用のTensorFlow Lite for C++ ライブラリをクロスビルドします。
そして、作成したTensorFlowLiteライブラリを使用するアプリケーションを作成します。ビルドは、クロスビルドとネイティブビルドの両方を試してみます。
TensorFlow Lite for C++のライブラリ(RaspberryPi用)を作る
ビルドする
基本的には、https://www.tensorflow.org/lite/guide/build_rpi に書かれている通りにやればOKです。Bazelのインストールをしておく必要があるかもしれません。
PC Linux(Ubuntu)上で、以下コマンドを実行します。
が、2点注意があるので先に↓の注意事項を確認してください。
sudo apt-get update
sudo apt-get install crossbuild-essential-armhf
cd ~/
git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow
git checkout 5d43d943153836f6c3bafd97d6bedbc124b99e37
./tensorflow/lite/tools/make/download_dependencies.sh
nano tensorflow/lite/tools/make/Makefile
./tensorflow/lite/tools/make/build_rpi_lib.sh
ls tensorflow/lite/tools/make/gen/rpi_armv7l/lib/libtensorflow-lite.a
生成物は tensorflow/lite/tools/make/gen/rpi_armv7l/lib/libtensorflow-lite.a
になります。
※上記コマンドでは念のため、僕の方で動作確認できたバージョンを指定しています。本当はタグが打たれているv1.13.1あたりを指定すべきなのだと思いますが、後述のMakefileの問題を直すのが少し面倒になるため、中途半端ですがこのバージョンを指定することにしました。
注意事項1
VirtualBox上の共有フォルダで作業は行わないようにしてください。
download_dependencies.sh実行中にシンボリックリンク作成エラーが出てしまいます。
困ったことに、ライブラリ自体は作成されてしまいます。が、これを使用したアプリケーションをビルドしようとすると、以下のようなエラーが出てリンクに失敗します。先のコマンドではホームディレクトリにcdしてから作業しています。
./libtensorflow-lite.a(spectrogram.o): In function `tflite::internal::Spectrogram::ProcessCoreFFT()':
spectrogram.cc:(.text+0x1d2): undefined reference to `rdft'
注意事項2
これは、TensorFlow側の問題だと思うのですが、現時点(2019年3月10日)では、Android用のNNAPIを使用する設定になってしまっています。そのため、これもライブラリ自体の作成はできるのですが、アプリケーションをビルドしようとすると、以下のようなエラーが出てリンクに失敗します。
undefined reference to `NnApiImplementation()'
tensorflow/lite/tools/make/Makefile
を以下のように編集します。これは、単純にMakefileの条件設定のif文の問題だと思うので、そのうち修正されると思います。
original)
BUILD_WITH_NNAPI=true
change)
BUILD_WITH_NNAPI=false
参考: https://github.com/tensorflow/tensorflow/issues/25120
TensorFlow Lite用モデルを使って、入力画像から数字識別するC++アプリケーションを作る
PC上でクロスビルドしてみる (コマンドライン)
最初にgit cloneした、tensorflowディレクトリで作業します。
まず、先ほど作成したlibtensorflow-lite.a
を作業ディレクトリにコピーしておきます。また、TensorFlowLiteだけを使用するサンプルコード(OpenCV未使用)を用意しておきます(https://github.com/take-iwiw/CNN_NumberDetector/blob/master/05_TensorflowLite_CPP/main_without_opencv.cpp )。ソースコードは、前回作成したPC Linux用のコードと全く同じものが使用できます。
以下のようなコマンドでコンパイルが出来ます。pthreadが必要みたいでした。
cd ~/tensorflow
cp tensorflow/lite/tools/make/gen/rpi_armv7l/lib/libtensorflow-lite.a .
# サンプルコードをダウンロード
wget https://raw.githubusercontent.com/take-iwiw/CNN_NumberDetector/master/05_TensorflowLite_CPP/main_without_opencv.cpp
# ビルド
arm-linux-gnueabihf-g++ main_without_opencv.cpp -I. -I./tensorflow -I./tensorflow/lite/tools/make/downloads -I./tensorflow/lite/tools/make/downloads/eigen -I./tensorflow/lite/tools/make/downloads/absl -I./tensorflow/lite/tools/make/downloads/gemmlowp -I./tensorlow/lite/tools/make/downloads/neon_2_sse -I./tensorflow/lite/tools/make/downloads/farmhash/src -I./tensorflow/lite/tools/make/downloads/flatbuffers/include -std=c++11 -lstdc++ -lm -lpthread -ltensorflow-lite -L.
# ラズパイにアップロード (IPアドレスは適宜置き換えてください)
scp ./a.out pi@192.168.1.88:/home/pi/a_cross.out
wget https://github.com/take-iwiw/CNN_NumberDetector/raw/master/05_TensorflowLite_CPP/resource/conv_mnist.tflite
scp conv_mnist.tflite pi@192.168.1.88:/home/pi/.
ラズパイ上で、./a.out
で実行できます。結果は次のネイティブビルド版と同様なので省略します。
ラズパイ上でネイティブビルドしてみる (コマンドライン)
OpenCV等を使用したプロジェクトの場合、クロスビルドだと色々と設定が面倒なので、ラズパイ上でネイティブビルドしてみます。とはいえ、使用するコンパイラが変わっただけです。
ラズパイ上でも、tensorflowリポジトリをcloneして、依存ファイルをダウンロードしておきます。これはビルド時にヘッダファイルが必要なためです。また、先ほど作成したlibtensorflow-lite.a
をコピーしておきます。
scp tensorflow/lite/tools/make/gen/rpi_armv7l/lib/libtensorflow-lite.a pi@192.168.1.88:/home/pi/.
# ラズパイのIPアドレスは適宜置き換えてください
# ビルドに必要なヘッダ取得
git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow
git checkout 5d43d943153836f6c3bafd97d6bedbc124b99e37
./tensorflow/lite/tools/make/download_dependencies.sh
# PCからアップロードしたライブラリをコピーしておく
cp ~/libtensorflow-lite.a .
# サンプルコードとモデルをダウンロード
wget https://raw.githubusercontent.com/take-iwiw/CNN_NumberDetector/master/05_TensorflowLite_CPP/main_without_opencv.cpp
wget https://github.com/take-iwiw/CNN_NumberDetector/raw/master/05_TensorflowLite_CPP/resource/conv_mnist.tflite
# ビルド
g++ main_without_opencv.cpp -I. -I./tensorflow -I./tensorflow/lite/tools/make/downloads -I./tensorflow/lite/tools/make/downloads/eigen -I./tensorflow/lite/tools/make/downloads/absl -I./tensorflow/lite/tools/make/downloads/gemmlowp -I./tensorlow/lite/tools/make/downloads/neon_2_sse -I./tensorflow/lite/tools/make/downloads/farmhash/src -I./tensorflow/lite/tools/make/downloads/flatbuffers/include -std=c++11 -lstdc++ -lm -lpthread -ltensorflow-lite -L.
# 実行
./a.out
pi@raspberrypi:~/tensorflow $ ./a.out
=== Pre-invoke Interpreter State ===
~略~
=== Post-invoke Interpreter State ===
Interpreter has 16 tensors and 5 nodes
Inputs: 10
Outputs: 8
Tensor 0 conv2d_1/Conv2D_bias kTfLiteFloat32 kTfLiteMmapRo 32 bytes ( 0.0 MB) 8
Tensor 1 conv2d_1/Relu kTfLiteFloat32 kTfLiteArenaRw 25088 bytes ( 0.0 MB) 1 28 28 8
Tensor 2 conv2d_1/kernel kTfLiteFloat32 kTfLiteMmapRo 288 bytes ( 0.0 MB) 1 3 3 8
Tensor 3 conv2d_2/Conv2D_bias kTfLiteFloat32 kTfLiteMmapRo 16 bytes ( 0.0 MB) 4
Tensor 4 conv2d_2/Relu kTfLiteFloat32 kTfLiteArenaRw 3136 bytes ( 0.0 MB) 1 14 14 4
Tensor 5 conv2d_2/kernel kTfLiteFloat32 kTfLiteMmapRo 1152 bytes ( 0.0 MB) 4 3 3 8
Tensor 6 dense_1/BiasAdd kTfLiteFloat32 kTfLiteArenaRw 40 bytes ( 0.0 MB) 1 10
Tensor 7 dense_1/MatMul_bias kTfLiteFloat32 kTfLiteMmapRo 40 bytes ( 0.0 MB) 10
Tensor 8 dense_1/Softmax kTfLiteFloat32 kTfLiteArenaRw 40 bytes ( 0.0 MB) 1 10
Tensor 9 dense_1/kernel/transpose kTfLiteFloat32 kTfLiteMmapRo 31360 bytes ( 0.0 MB) 10 784
Tensor 10 input_1 kTfLiteFloat32 kTfLiteArenaRw 3136 bytes ( 0.0 MB) 1 28 28 1
Tensor 11 max_pooling2d_1/MaxPool kTfLiteFloat32 kTfLiteArenaRw 6272 bytes ( 0.0 MB) 1 14 14 8
Tensor 12 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 13 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 14 (null) kTfLiteFloat32 kTfLiteArenaRw 56448 bytes ( 0.1 MB) 1 14 14 72
Tensor 15 (null) kTfLiteFloat32 kTfLiteArenaRwPersistent 1152 bytes ( 0.0 MB) 72 4
Node 0 Operator Builtin Code 4
Inputs: 10 2 0
Outputs: 1
Node 1 Operator Builtin Code 17
Inputs: 1
Outputs: 11
Node 2 Operator Builtin Code 3
Inputs: 11 5 3
Outputs: 4
Node 3 Operator Builtin Code 9
Inputs: 4 9 7
Outputs: 6
Node 4 Operator Builtin Code 25
Inputs: 6
Outputs: 8
prob of 0: 0.020
prob of 1: 0.504
prob of 2: 0.140
prob of 3: 0.020
prob of 4: 0.006
prob of 5: 0.026
prob of 6: 0.021
prob of 7: 0.220
prob of 8: 0.012
prob of 9: 0.033
入力がオール0の値なので、結果に意味はありません。
クロスプラットフォーム対応なプロジェクトにする
今回作成したアプリケーション用プログラムでは、ソースコードは、PC用とラズパイ用で全く同じものが使えました。
また、ラズパイ用ビルドにおいて、クロスビルドとネイティブビルドの違いはtoolchainの違いだけです(arm-linux-gnueabihf-g++ or g++)。
これらを簡単に切り替えられるようなcmakeプロジェクトを作ってみました。PC Linux(Ubuntu)、ラズパイ用ネイティブビルド、ラズパイ用クロスビルドに対応しています。
https://github.com/take-iwiw/CNN_NumberDetector/tree/master/05_TensorflowLite_CPP
トップのCMakeLists.txtはこんな感じです。
cmake_minimum_required(VERSION 2.8)
project(NumberDetector)
# Switch build target
set(BUILD_TARGET PC CACHE STRING "Build target?")
set(LINK_ONLY OFF CACHE BOOL "Use pre-built library?")
# Common build settings
include(cmakes/common.cmake)
# Build settings for each target
if(${BUILD_TARGET} STREQUAL PC)
if(WIN32)
message(FATAL_ERROR "[BUILD] PC Windows is not supported")
else()
message("[BUILD] PC Linux")
include(cmakes/PC_LINUX.cmake)
endif()
elseif(${BUILD_TARGET} STREQUAL RASPI_NATIVE)
message("[BUILD] Raspberry Pi Native")
include(cmakes/RASPI_NATIVE.cmake)
elseif(${BUILD_TARGET} STREQUAL RASPI_CROSS)
message("[BUILD] Raspberry Pi Cross")
include(cmakes/RASPI_CROSS.cmake)
else()
message(FATAL_ERROR "[BUILD] Invalid target")
endif()
# Create Main project
add_executable(NumberDetector
main.cpp
# main_without_opencv.cpp
)
# For OpenCV
find_package(OpenCV REQUIRED)
if(OpenCV_FOUND)
target_include_directories(NumberDetector PUBLIC ${OpenCV_INCLUDE_DIRS})
target_link_libraries(NumberDetector ${OpenCV_LIBS})
endif()
# For Tensorflow Lite
if((${BUILD_TARGET} STREQUAL PC) AND UNIX)
# use shared library
# target_link_libraries(NumberDetector ${CMAKE_SOURCE_DIR}/external_libs/tensorflow_prebuilt/linux-cpu-x86_64/libtensorflowlite.so)
# file(COPY ${CMAKE_SOURCE_DIR}/external_libs/tensorflow_prebuilt/linux-cpu-x86_64/libtensorflowlite.so DESTINATION ${PROJECT_BINARY_DIR}/)
# use static library
target_link_libraries(NumberDetector ${CMAKE_SOURCE_DIR}/external_libs/tensorflow_prebuilt_static/linux-cpu-x86_64/libtensorflow-lite.a)
elseif((${BUILD_TARGET} STREQUAL RASPI_NATIVE) OR (${BUILD_TARGET} STREQUAL RASPI_CROSS))
target_link_libraries(NumberDetector ${CMAKE_SOURCE_DIR}/external_libs/tensorflow_prebuilt_static/rpi_armv7l/libtensorflow-lite.a)
else()
message(FATAL_ERROR "[BUILD] Invalid target")
endif()
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/eigen )
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/absl)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/gemmlowp)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorlow/lite/tools/make/downloads/neon_2_sse)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/farmhash/src)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/flatbuffers/include)
# Copy resouce
file(COPY ${CMAKE_SOURCE_DIR}/resource/ DESTINATION ${PROJECT_BINARY_DIR}/resource/)
add_definitions(-DRESOURCE_DIR="${PROJECT_BINARY_DIR}/resource/")
cd 05_TensorflowLite_CPP/
mkdir build && cd build
cmake .. # PC Linux native
# cmake .. -DBUILD_TARGET=RASPI_CROSS # Cross compile for Raspberry Pi on PC Linux
# cmake .. -DBUILD_TARGET=RASPI_NATIVE # Native compile on Raspberry Pi
make