OpenNH

日常のひとこま(自分用のメモとかあれこれ)

tensorflow2.0 c++ apiをcmakeで利用する(Ubuntu18.04)

1. 目次


2. はじめに

tensorflowをpythonで利用するのは情報もたくさんあるしpipで簡単に導入できるのに、C++で利用したいとなったときに全然まとまった記事がなかったので苦労しました。知識ある人はササッとできてしまうのかもしれませんが、私みたいな素人がtensorflow c++ apiを導入しようとすると一苦労です。というか実際なかなかうまく入らずめちゃ大変でした。

Ubuntu18.04でtensorflow2.0 c++ apiを導入するためのメモを残しておきます。
新たにtensorflowをc++で利用したいって人の助けになればと思います。


3. 環境

OS Ubuntu 18.04
GPU Geforce GTX 960
CUDA ver.10.2
cuDNN ver.7.6
bazel ver. 0.26.1
tensorflow ver. 2.0.0


4. bazel version 0.26.1 のインストール

まず、tensorflowをソースからビルドするためにGoogle独自のビルドツールである「bazel」をインストールします。

環境に合わせて今回は、bazel 0.26.1をインストールします。以下のリンクから.shファイルをダウンロードできます。
tensorflow, CUDA, cuDNNのバージョンによって対応するbazelのバージョンも異なってくるので注意してください。tensorflowの公式に対応表が載っているのですが、古くて使い物になりませんでした。

上記リンクから、Ubuntuの場合は次の.shファイルをダウンロード

bazel-0.26.1-installer-linux-x86_64.sh

それではインストールしていきます。

# cd ~/Download/
$ chmod +x bazel-0.26.1-installer-linux-x86_64.sh

$ ./bazel-0.26.1-installer-linux-x86_64.sh --user

$ source ~/.bashrc

# インストールされたか確認
$ bazel version
Starting local Bazel server and connecting to it...
Build label: 0.26.1
Build target: bazel-out/k8-opt/bin/src/main/java/com/google/devtools/build/lib/bazel/BazelServer_deploy.jar
Build time: Thu Jun 6 11:05:05 2019 (1559819105)
Build timestamp: 1559819105
Build timestamp as int: 1559819105

Build label: 0.26.1がインストールしたいバージョンと一致しているので大丈夫ですね。
もし間違っていたら、ダウンロードしてくる.shファイルを間違えている可能性があるので、再度ダウンロード・インストールを行ってください。


5. tensorflow 2.0.0 をソースからビルド

今回は、現在(2020/1)時点でstable版の最新であるtensorflow2.0.0を導入していきます。
C++で利用するためにはソースからビルドする必要があるのでちょっと面倒です。pipインストールが楽すぎる…。しかもビルドにはGoogleしかおそらく使ってないであろうbazelを利用しなければなりません。

< ここで一点注意事項 >
bazelでビルドする際に、他のサイトでは

$ bazel build -c opt --config=cuda --copt=-march=native tensorflow:libtensorflow.so

と書かれていることが多いですが、以下のように生成する共有ライブラリはtensorflow:libtensorflow_cc.soとしてください。こちらの記事に同じ事が書かれています。

$ bazel build -c opt --config=cuda --copt=-march=native tensorflow:libtensorflow_cc.so


それではtensorflowをビルドしていきましょう。

$ git clone https://github.com/tensorflow/tensorflow.git

$ cd tensorflow

# 新たにブランチを作成しcheckout
$ git checkout -b work_v2.0.0 v2.0.0

# CUDA設定以外は基本的にNoかDefault
./configure 

# third_party/nccl/build_defs.bzl.tpl 内の以下の行を削除する
# "--bin2c-path=%s" % bin2c.dirname 
$ vim third_party/nccl/build_defs.bzl.tpl
# Delete>> "--bin2c-path=%s" % bin2c.dirname 

# bazelでビルドする
$ bazel build -c opt --config=cuda --copt=-march=native tensorflow:libtensorflow_cc.so
<---->
Target //tensorflow:libtensorflow_cc.so up-to-date:
  bazel-bin/tensorflow/libtensorflow_cc.so
INFO: Elapsed time: 3246.956s, Critical Path: 207.09s
INFO: 7346 processes: 7346 local.
INFO: Build completed successfully, 12925 total actions

Build completed successfullyとでていれば、これでビルド完了。

ちゃんとビルドできているか確認します。

$ ls bazel-bin/tensorflow/
c/                           libtensorflow_cc.so.2.0.0
cc/                          libtensorflow_cc.so.2.0.0-2.params
compiler/                    libtensorflow_framework.so.2
core/                        libtensorflow_framework.so.2.0.0
libtensorflow_cc.so          libtensorflow_framework.so.2.0.0-2.params
libtensorflow_cc.so.2        stream_executor/

以下の2つが生成されていればビルド成功です。

  • "libtensorflow_cc.so.2.0.0"
  • ”libtensorflow_framework.so.2.0.0”


6. パスを通していく

以下のサイトを参考


6.1. 共有ライブラリのコピー

共有ライブラリ名は以下のようにしてください。 - libtensorflow_cc.so.2 - libtensorflow_framework.so.2

他のサイトで見られるlibtensorflow_cc.solibtensorflow_framework.soだとリンカーエラーになります。tensorflow2.0だからかな?

$ sudo cp bazel-bin/tensorflow/libtensorflow_cc.so.2.0.0 /usr/local/lib/libtensorflow_cc.so.2

$ sudo cp bazel-bin/tensorflow/libtensorflow_framework.so.2.0.0 /usr/local/lib/libtensorflow_framework.so.2


6.2. ヘッダファイルの抽出

以下のcopy_tensorflow_headers.shファイルを~/tensorflow/直下において実行。こちらのコードを修正して利用します。必要以上に抽出してる気がするけど細かい部分確認していないのでとりあえずこのままで大丈夫です。

copy_tensorflow_headers.sh

#!/bin/bash -eu
# -*- coding: utf-8 -*-

# Referece:
# [copy_tensorflow_headers.sh](https://gist.github.com/saitodev/3cde48806a32272962899693700d9669)

HEADER_DIR=/usr/local/include/tensorflow

if [ ! -e $HEADER_DIR ];
then
    mkdir -p $HEADER_DIR
fi

find tensorflow/core -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
find tensorflow/cc   -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
find tensorflow/c    -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;

find third_party/eigen3 -follow -type f -exec cp --parents {} $HEADER_DIR \;

pushd bazel-genfiles
find tensorflow  -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
popd

pushd bazel-tensorflow/external/eigen_archive
find Eigen       -follow -type f -exec cp --parents {} $HEADER_DIR \;
find unsupported -follow -type f -exec cp --parents {} $HEADER_DIR \;
popd

pushd bazel-tensorflow/external/com_google_protobuf/src
find google -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
find google -follow -type f -name "*.inc" -exec cp --parents {} $HEADER_DIR \;
popd

pushd bazel-tensorflow/external/com_google_absl
find absl -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
find absl -follow -type f -name "*.inc" -exec cp --parents {} $HEADER_DIR \;
popd

pushd ~/.cache/bazel/_bazel_fsato/ce9d2ac105b59fe4ccec7c9da3d0efcf/external
find eigen_archive       -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
find com_google_protobuf -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
popd

下記コマンドで実行すると、必要なヘッダーファイルがHEADER_DIRで指定した/usr/local/include/tensorflow内にコピーされます。

# ~/tensorflow
$ sudo bash copy_tensorflow_headers.sh 


6.3. FindTensorflow.cmakeの作成

cmakeでパッケージを探せるように設定しておきます。 こちらのリンク先)のファイルFindTensorflow.cmakeを、他のFindFoo.cmakeがある場所におきます。

$ cp FindTensorflow.cmake /usr/share/cmake-3.10/Modules/


7. サンプルをcmakeで動かす

サンプルが用意されているので、その中でもよくデモとして利用されるtensorflow/examples/label_image/を試しに動かしてみます。これは一枚の画像から物体認識を行うデモになっています。

扱いやすいようにlabel_image/以下を好きなディレクトリにコピーしましょう。例として~/myws/にコピーしました。

# ~/tensorflow
cp tensorflow/examples/label_image ~/myws

7.1. モデルのダウンロード

サンプルを動かすためにはモデルをダウンロードしてくる必要があります。
以下のコマンドでダウンロードできます。

$ curl -L "https://storage.googleapis.com/download.tensorflow.org/models/inception_v3_2016_08_28_frozen.pb.tar.gz" | tar -C ~/myws/label_image/data -xz

7.2. 下準備

それではlabel_imageデモを動かしてみます。
まず、main.ccでモデルを読み込んでいる部分のパスを変更します(main文の中)。

- string image = "tensorflow/examples/label_image/data/grace_hopper.jpg";
+tring image = "../data/grace_hopper.jpg";

- string graph = "tensorflow/examples/label_image/data/inception_v3_2016_08_28_frozen.pb";
+string graph = "../data/inception_v3_2016_08_28_frozen.pb";

-string labels = "tensorflow/examples/label_image/data/imagenet_slim_labels.txt";
+string labels = "../data/imagenet_slim_labels.txt";

次にCMakeLists.txtを作成し、~/myws/label_imageにおきます。
「FindTensorflow.cmakeの作成」でCmakeで簡単に呼び出せるように設定したのでそれを利用します。ただ共有ライブラリに関してはなぜかフルパスを指定してあげないとリンカーエラーとなってしまいました。ここは調べとかないとですね。

# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

find_package(Tensorflow REQUIRED)
include_directories(${Tensorflow_INCLUDE_DIRS})

#add_executable(hello_tensorflow hello_tensorflow.cpp)
add_executable(label_image main.cc)

# Link the static Tensorflow library.
target_link_libraries(label_image "/usr/local/lib/libtensorflow_cc.so.2")
target_link_libraries(label_image "/usr/local/lib/libtensorflow_framework.so.2")

7.3. サンプルの実行

# ~/myws/label_image
$ mkdir build && cd build
$ cmake ..  
$ make
$ ./label_image

実行結果

2020-01-16 22:40:18.908806: I /label_image/main.cc:251] military uniform (653): 0.834305
2020-01-16 22:40:18.908820: I /label_image/main.cc:251] mortarboard (668): 0.0218697
2020-01-16 22:40:18.908826: I /label_image/main.cc:251] academic gown (401): 0.0103582
2020-01-16 22:40:18.908831: I /label_image/main.cc:251] pickelhaube (716): 0.00800826
2020-01-16 22:40:18.908836: I /label_image/main.cc:251] bulletproof vest (466): 0.00535093

ちゃんと認識できていることが確認できました!