RT

背景

Tensorflow 是google开源的目前最流行的深度学习框架。
TensorFlow 源码基于c++ 开发,提供了java, c, c++, python等主流编程语言支持。目前业界主流的应用方式是使用Tensorflow python构建以及训练模型,模型训练完成后使用c++ 或者java将模型应用到生产环境。
本文主要介绍如何将Tensorflow编译成动态库以便于其他工程使用头文件以及动态库的方式引入Tensorflow进行模型训练或者应用训练好的模型
本文的测试环境基于MacOS,Linux环境也可以按照相同步骤安装依赖,编译TensorFlow 动态库。

依赖

java

java --vesrion
java 9.0.1

bazel

bazel 是google 开源的工程构建工具。

brew install bazel

eigen

eigen 是一个C++矩阵运算库

brew install eigen

automake

brew install automake

libtool

brew install libtool

protobuf

brew install protobuf

编译

首先我们将整个Tensorflow clone到本地

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

编译tensorflow 动态链接库

bazel build //tensorflow:libtensorflow_cc.so
bazel build //tensorflow:libtensorflow_framework.so

编译的过程耗时约30mins
编译完成后,可以看到tensorflow路径下多了bazel-out, bazel-tensorflow,bazel-bin, bazel-testlogs,bazel-genfiles 这几个路径

整理头文件及库文件

这一步,我们将编译好动态库以及Tensorflow 头文件拷贝到系统路径以方便其他c++工程引用。
头文件

sudo mkdir /usr/local/include/tf/tensorflow
# 拷贝tensorflow项目本身生成的头文件
sudo cp -r tensorflow/core /usr/local/include/tf/tensorflow/
sudo cp -r tensorflow/cc /usr/local/include/tf/tensorflow/
# 拷贝bazel 编译的文件
sudo cp -r bazel-genfiles/ /usr/local/include/tf/
# 拷贝tensorflow依赖的第三方文件
sudo cp -r third_party /usr/local/include/tf/

动态链接库文件

sudo cp -r bazel-bin/tensorflow/libtensorflow_cc.so /usr/local/lib/
sudo cp -r bazel-bin/tensorflow/libtensorflow_framework.so /usr/local/lib/

示例以及编译

我们使用Tensorflow 官网https://www.tensorflow.org/api_guides/cc/guide提供的示例使用动态链接库编译测试。
示例代码如下

// tensorflow/cc/example/example.cc

#include "tensorflow/cc/client/client_session.h"
#include "tensorflow/cc/ops/standard_ops.h"
#include "tensorflow/core/framework/tensor.h"

int main() {
  using namespace tensorflow;
  using namespace tensorflow::ops;
  Scope root = Scope::NewRootScope();
  // Matrix A = [3 2; -1 0]
  auto A = Const(root, { {3.f, 2.f}, {-1.f, 0.f} });
  // Vector b = [3 5]
  auto b = Const(root, { {3.f, 5.f} });
  // v = Ab^T
  auto v = MatMul(root.WithOpName("v"), A, b, MatMul::TransposeB(true));
  std::vector<Tensor> outputs;
  ClientSession session(root);
  // Run and fetch v
  TF_CHECK_OK(session.Run({v}, &outputs));
  // Expect outputs[0] == [19; -3]
  LOG(INFO) << outputs[0].matrix<float>();
  return 0;
}

使用CMake 编译,CMakeLists.txt 如下

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

SET(TENSORFLOW_INCLUDE_PATH /usr/local/include/tf)
SET(TENSORFLOW_LIBARY /usr/local/lib/libtensorflow_cc.so)
SET(TENSORFLOW_FRAMEWORK_LIBARY /usr/local/lib/libtensorflow_framework.so)
MESSAGE(STATUS "TENSORFLOW_INCLUDE_PATH ${TENSORFLOW_INCLUDE_PATH}")
MESSAGE(STATUS "TENSORFLOW_LIBARY ${TENSORFLOW_LIBARY}")
MESSAGE(STATUS "TENSORFLOW_FRAMEWORK_LIBARY ${TENSORFLOW_FRAMEWORK_LIBARY}")
SET(EIGEN_INCLUDE_PATH /usr/local/include/eigen3)
SET(ABSL_INCLUDE_PATH /usr/local/include/tf/absl)
MESSAGE(STATUS "ABSL_INCLUDE_PATH" ${ABSL_INCLUDE_PATH})

INCLUDE(FindProtobuf)
FIND_PACKAGE(Protobuf REQUIRED)

INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR})
INCLUDE_DIRECTORIES(${TENSORFLOW_INCLUDE_PATH})
INCLUDE_DIRECTORIES(${EIGEN_INCLUDE_PATH})
INCLUDE_DIRECTORIES(${ABSL_INCLUDE_PATH})

SET(EXAMPLE_LIBRARIES
    ${PROTOBUF_LIBRARY}
    ${TENSORFLOW_FRAMEWORK_LIBARY}
    ${TENSORFLOW_LIBARY})

ADD_EXECUTABLE(example example.cpp)

SET(LDFLAGS "-std=c++11 -O3 -ggdb -Wall")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}${LDFLAGS}")

MESSAGE(STATUS "CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}")
MESSAGE(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
TARGET_LINK_LIBRARIES(example ${EXAMPLE_LIBRARIES} ${CMAKE_CXX_FLAGS})

需要注意的是,除了以上提到的头文件之外,CMakeLists.txt中引入了ABSL_INCLUDE_PATH, 引入的原因是在编译过程中报以下错误

/usr/local/include/tf/tensorflow/core/lib/gtl/array_slice.h:19:10: fatal error: 'absl/types/span.h' file not found

回到TensorFlow工程下,查找span.h

find . -name span.h
# 输出如下
./tensorflow/contrib/makefile/downloads/absl/absl/types/span.h

我们将absl 头文件拷贝到系统目录下,然后在CMakeLists.txt 文件引入该路径。

cd ./tensorflow/contrib/makefile/downloads/
cp -r absl /usr/local/include/tf/

编译

mkdir -p build && cd build
cmake ..
make

执行

pic
可以看到执行结果同预期一致。

后记

编译及应用Tensorflow 动态库的思路是安装好Tensorflow所需依赖,然后使用google开源bazel进行编译。
MacOS 依赖于强大的包管理工具brew可以很方便的安装各种依赖。到Linux环境下需要使用各发行版提供的包管理工具安装,如果不能安装成功需要自己进行手动编译。
在编译好Tensorflow 动态库后,使用Tensorflow的时候仍然有可能出现有头文件没找到或者依赖库缺少的情况,这时候我们可以首先执行Tensorflow 工程提供的 tensorflow/contrib/makefile/download_dependencies.sh 脚本下载Tensorflow 依赖,然后到 downloads 路径下查找缺少的头文件或者库文件,然后将缺少的头文件或库文件拷贝到系统路径。
Tensorflow 工程下 tensorflow/contrib/makefile/ 提供了分别在Andorid, iOS, linux, docker环境下部署应用Tensorflow 的脚本,运行相应脚本后,我们可以得到编译好的Tensorflow静态库文件以及所需的头文件。使用编译好的静态库以及头文件部署到相应平台可以运行训练好的模型。需要注意的是,这些静态库以及头文件只包括应用训练好的模型的相关功能,不具备训练模型的相关功能(毕竟我们不想也不可能到Andorid, iOS平台下去训练模型。) 。下一篇博客会详细介绍编译Tensorflow静态库以及应用Tensorflow静态库的整体过程。