この記事について
機械学習、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++のライブラリを作る
- 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)
- OS(on VirtualBox): Ubuntu 16.04
- CPU = Intel Core i7-6700@3.4GHz (物理コア=4、論理プロセッサ数=8)
- GPU = NVIDIA GeForce GTX 1070 (← GPUは無くても大丈夫です)
- 開発環境: bazel 0.23.1, cmake, gcc
今回は、Linux(Ubuntu)のみで動作確認しました(理由は後述)。僕の環境では、Windows10上のVirtual Boxで動かしています。
TensorFlow Lite for C++のライブラリを作る
ビルドターゲット
TensorFlow Liteに関する情報は https://www.tensorflow.org/lite/guide/get_started にあります。
基本的には、AndroidやiOSといったモバイル向けのライブラリです。また、ラズパイや組み込みLinuxボードでも使えるようです。
これら向けのビルドスクリプトが、https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/make に用意されています。
Windows, Linux向けのビルドも、ここにあるスクリプトをいじれば行けるかなと思ったのですが、うまくできませんでした。 (2019/3/11追記: できるようになりました。この記事の一番最後に追記します)
代わりに、 https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/BUILD 内のターゲットに、libtensorflowlite.so
というものを見つけました。今回は共有ライブラリをビルドしてみようと思います。(正しい方法かは不明。。)
ビルドツールの用意
TensorFlowのビルドには、Bazelというビルドツールが使われています。makeみたいなもののようですが、色々な言語に対応しているらしいです。
Windows
Windowsでは、https://docs.bazel.build/versions/master/install-windows.html に記載されているようにバイナリ配布されています。
Bazel自体はいいのですが、TensorFlowをビルドするにはVisual Studio 2015が必要みたいです。(2019年3月9日現在)。
僕のPCにはVS2017しか入っておらず、わざわざVS2015もインストールするのが面倒なので、今回はWindows上での検証は行わないことにします。
Linux
Linuxでは、https://docs.bazel.build/versions/master/install-ubuntu.html の手順通りにインストールするだけです。 インストーラには https://github.com/bazelbuild/bazel/releases/download/0.23.1/bazel-0.23.1-installer-linux-x86_64.sh を使用しました。
ライブラリをビルドする
以下コマンドで、TensorFlowのレポジトリをcloneします。念のため、現時点(2019年3月9日)で動作確認したバージョンにcheckoutしておきます。必要に応じてmasterのlatestにしたり、所定のバージョンにcheckoutしてください。
その後、ターゲットを指定してbazelでビルドします。
コメントアウトしていますが、label_imageというサンプルプロジェクトもありました。モデルなど、必要なファイルはdownload_models.sh
でダウンロードできました。
git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow # 以後、cloneしたtensorflowディレクトリ内で全ての作業を行います。この下にさらにtensorflowというディレクトリがあるので注意(そこではないです)
git checkout 5d43d943153836f6c3bafd97d6bedbc124b99e37
bazel build '//tensorflow/lite:libtensorflowlite.so'
# bazel build '//tensorflow/lite/examples/label_image'
# tensorflow/lite/examples/ios/download_models.sh
Target //tensorflow/lite:libtensorflowlite.so up-to-date:
bazel-bin/tensorflow/lite/libtensorflowlite.so
INFO: Elapsed time: 125.840s, Critical Path: 24.11s
INFO: 151 processes: 151 local.
INFO: Build completed successfully, 158 total actions
~/tensorflow$ ls bazel-bin/tensorflow/lite/libtensorflowlite.so
bazel-bin/tensorflow/lite/libtensorflowlite.so
TensorFlow Lite用アプリケーションプログラムを手動でビルドする
方針
本当は、アプリケーションプログラムもライブラリと同様にbazelを使用してビルドすべきなのだと思います。あるいは、TensorFlow Liteと一緒にビルドしてしまうのが楽なのかもしれません。
しかし、1時間くらいBazelをいじったのですが、あまり友達になれそうにありませんでした。
ということで、先ほど作成したlibtensorflowlite.so をリンクする形で、いつも通りの方法でアプリケーションプログラムをビルドしてみようと思います。まずは普通にコマンドラインから手動でビルドしてみます。
オプション設定などは、 https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tools/make/Makefile が参考になりました。
ヘッダファイルを集める
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tools/make/Makefile を見ると分かるのですが、必要なヘッダファイルが点在しています。さらに、外部のライブラリのヘッダファイルも必要になります。(ライブラリ自体はlibtensorflowlite.soビルド時に取り込まれているみたいなのですが、アプリケーションプログラムビルド時に、ヘッダファイル間の依存関係があるため、必要になります)
以下コマンドで、外部ライブラリヘッダファイルを取得します。./tensorflow/lite/tools/make/downloads/
に保存されます。
./tensorflow/lite/tools/make/download_dependencies.sh
ls ./tensorflow/lite/tools/make/downloads/
absl eigen farmhash fft2d flatbuffers gemmlowp googletest neon_2_sse
僕の環境(VirtualBox内のWindowsとの共有フォルダで作業)だけかもしれませんが、以下のようなエラーが出ました。特に問題はありませんでした。
tar: docs/source/CONTRIBUTING.md: Cannot create symlink to ‘../../CONTRIBUTING.md’: Protocol error
tar: Exiting with failure status due to previous errors
ビルドする
先ほど作成したbazel-bin/tensorflow/lite/libtensorflowlite.so
をカレントディレクトリにコピーしておきます。また、中身は後で見ますが、main.cppがアプリケーションプログラムのソースコードとして、配置しておきます。
その後、ビルドします。gccには必要なincludeパス設定(多い。。。)と、libtensorflowlite.soのリンク設定をします。
実行時には、ライブラリパスとしてカレントディレクトリを指定しておきます。
# 必要なファイルを確認して、
cp bazel-bin/tensorflow/lite/libtensorflowlite.so .
ls libtensorflowlite.so main.cpp
libtensorflowlite.so main.cpp
# ビルド
gcc main.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++ -ltensorflowlite -L./
# 実行
LD_LIBRARY_PATH=. ./a.out
TensorFlow Lite用アプリケーションプログラムのcmakeプロジェクトを作る
手動でビルドすることで原理は分かったのですが、毎回面倒なのでcmakeプロジェクトで作ります。以下のようなディレクトリ構造を用意します。ソースコードなどは後から作ります。
https://github.com/take-iwiw/CNN_NumberDetector/tree/master/05_TensorflowLite_CPP も参考にしてください。
05_TensorflowLite_CPP
├─external_libs/
│ ├─tensorflow/ <- https://github.com/tensorflow/tensorflow
│ └─tensorflow_prebuilt/
│ └─linux-cpu-x86_64/libtensorflowlite.so
├─resource/
│ └─0~9.jpg(サンプル画像)、conv_mnist.tflite
├─CMakeLists.txt
└─main.cpp
external_libs/tensorflow にhttps://github.com/tensorflow/tensorflow をcloneします。gitで管理する場合は、submoduleを使うと楽だと思います(git submodule add https://github.com/tensorflow/tensorflow external_libs/tensorflow
)。
一度ビルドしたら、ライブラリをexternal_libs/tensorflow_prebuilt/linux-cpu-x86_64/libtensorflowlite.so に保存しておくと便利だと思います。自分のアプリケーションプログラムビルド時にはここに配置したライブラリをリンクします。
作成済みプロジェクトをcloneする場合
https://github.com/take-iwiw/CNN_NumberDetector/ からcloneした場合は、clone後に以下のコマンドが必要になります。また、ここではとりあえずビルドが通って使えたバージョン5d43d943153836f6c3bafd97d6bedbc124b99e37 を指定しています。もしも動かなかったらバージョンを変えたり、latestにcheckoutしてみてください。
git clone git@github.com:take-iwiw/CNN_NumberDetector.git
cd CNN_NumberDetector
git submodule init
git submodule update
# cd 05_TensorflowLite_C/external_libs/tensorflow
# git checkout xxx
プロジェクト内で必要なファイルを用意する
新しい環境でビルド、実行する場合には、libtensorflowlite.soのビルドが必要になります。ビルドしたらprebuiltディレクトリにコピーしておきます。
cloneして最初の状態では外部ライブラリヘッダのダウンロードが必要になります。
以下コマンドで必要なファイルを用意します。毎回実行する必要はありません。
ちなみに、ライブラリのビルドですが、こちらもおそらくVirtualBoxの共有フォルダ内で作業すると、変なところ(ホームディレクトリ下の.cache)に出来てしまいました。シンボリックリンクを作成できないことが原因のようです。どこに生成されたかは、ビルド終了時のメッセージを確認してください。
# ライブラリビルドする必要があるときだけ必要
cd external_libs/tensorflow/
bazel build '//tensorflow/lite:libtensorflowlite.so'
cp /home/tak/.cache/bazel/_bazel_tak/bf687bbce172de5fd22329b67398b71f/execroot/org_tensorflow/bazel-out/k8-opt/bin/tensorflow/lite/libtensorflowlite.so ../tensorflow_prebuilt/linux-cpu-x86_64/.
cd ../../
# clone後1回だけ必要
cd external_libs/tensorflow/
./tensorflow/lite/tools/make/download_dependencies.sh
cd ../../
ソースコードなど
CMakeLists.txt
CMakeLists.txtは以下のようになります。
OpenCVの設定と、先ほど手動で指定したTensorFlow Lite用のインクルードパス設定をしています。
また、以前と同様に必要なリソースファイル(入力画像とモデル)のコピーもしています。
cmake_minimum_required(VERSION 2.8)
project(NumberDetector)
# Create Main project
add_executable(NumberDetector
main.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
target_link_libraries(NumberDetector ${PROJECT_SOURCE_DIR}/external_libs/tensorflow_prebuilt/linux-cpu-x86_64/libtensorflowlite.so)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/eigen )
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/absl)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/gemmlowp)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/neon_2_sse)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/farmhash/src)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/flatbuffers/include)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -lstdc++")
# Copy resouce
file(COPY ${CMAKE_SOURCE_DIR}/resource/ DESTINATION ${PROJECT_BINARY_DIR}/resource/)
add_definitions(-DRESOURCE_DIR="${PROJECT_BINARY_DIR}/resource/")
main.cpp
main.cppは以下の通りです。
https://www.tensorflow.org/lite/guide/inference#loading_a_model や https://www.tensorflow.org/api_docs/python/tf/lite/Interpreter や https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/examples/minimal/minimal.cc を参考に、実装しました。
かなり簡単に実装できています。基本的には、TensorFlow for Cと同じ流れです(https://qiita.com/take-iwiw/items/d7cb2bd10a8bbb81cd8b )。
なお、サンプルコードでは他にも大量のincludeファイルがありました。僕の環境ではなくても問題がなかったのですが、エラーが出た場合には上記のサンプルコードを参考にincludeを追加してみてください。
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/model.h"
#include "tensorflow/lite/optional_debug_tools.h"
#define MODEL_FILENAME RESOURCE_DIR"conv_mnist.tflite"
#define TFLITE_MINIMAL_CHECK(x) \
if (!(x)) { \
fprintf(stderr, "Error at %s:%d\n", __FILE__, __LINE__); \
exit(1); \
}
int main()
{
/* read input image data */
cv::Mat image = cv::imread(RESOURCE_DIR"4.jpg");
cv::imshow("InputImage", image);
/* convert to 28 x 28 grayscale image (normalized: 0 ~ 1.0) */
cv::cvtColor(image, image, CV_BGR2GRAY);
cv::resize(image, image, cv::Size(28, 28));
image = ~image;
cv::imshow("InputImage for CNN", image);
image.convertTo(image, CV_32FC1, 1.0 / 255);
/* tflite */
std::unique_ptr<tflite::FlatBufferModel> model = tflite::FlatBufferModel::BuildFromFile(MODEL_FILENAME);
TFLITE_MINIMAL_CHECK(model != nullptr);
// Build the interpreter
tflite::ops::builtin::BuiltinOpResolver resolver;
tflite::InterpreterBuilder builder(*model, resolver);
std::unique_ptr<tflite::Interpreter> interpreter;
builder(&interpreter);
TFLITE_MINIMAL_CHECK(interpreter != nullptr);
// Allocate tensor buffers.
TFLITE_MINIMAL_CHECK(interpreter->AllocateTensors() == kTfLiteOk);
printf("=== Pre-invoke Interpreter State ===\n");
tflite::PrintInterpreterState(interpreter.get());
// Set data to input tensor
float* input = interpreter->typed_input_tensor<float>(0);
memcpy(input, image.reshape(0, 1).data, sizeof(float) * 1 * 28 * 28 * 1);
// Run inference
TFLITE_MINIMAL_CHECK(interpreter->Invoke() == kTfLiteOk);
printf("\n\n=== Post-invoke Interpreter State ===\n");
tflite::PrintInterpreterState(interpreter.get());
// Get data from output tensor
float* probs = interpreter->typed_output_tensor<float>(0);
for (int i = 0; i < 10; i++) {
printf("prob of %d: %.3f\n", i, probs[i]);
}
cv::waitKey(0);
return 0;
}
mkdir build && cd build
cmake ..
make
=== Pre-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.000
prob of 1: 0.001
prob of 2: 0.013
prob of 3: 0.043
prob of 4: 0.929
prob of 5: 0.003
prob of 6: 0.001
prob of 7: 0.006
prob of 8: 0.001
prob of 9: 0.003
(追記:2019/03/11)スタティックライブラリの作成
上述 した通り、最初はスタティックライブラリをうまく作れませんでした。が、ラズパイ用のライブラリを作るために色々と調べていたら解決策が見つかりました。
ビルド用スクリプトを用意する
tensorflow/lite/tools/make/
に各「モバイル」プラットフォーム向けのビルドスクリプトが配置されています。適当な対象をベースに、PC native向けのシェルスクリプト(tensorflow/lite/tools/make/build_linux.sh) を作ります。
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR/../../../.."
make -j 3 -f tensorflow/lite/tools/make/Makefile TARGET=linux
Makefileを修正する
これは、現時点(2019/03/11)でのTensorFlowリポジトリ上でのミスだと思うのですが、tensorflow/lite/tools/make/Makefileを以下のように修正します
original)
BUILD_WITH_NNAPI=true
change)
BUILD_WITH_NNAPI=false
参考: https://github.com/tensorflow/tensorflow/issues/25120
ビルドする
./tensorflow/lite/tools/make/build_linux.sh
アプリケーションビルド例
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.