由于在生产环境,很多情况下没有python的环境,只能用c/c++的环境,而且很多情况下是用python训练好模型后,用c++加载模型运行即可,那么如何才能用c++加载模型并运行呢?这里做一些说明。
主要参考两个网址,tensorflow的c++ API官网(https://www.tensorflow.org/api_guides/cc/guide)和Loading a TensorFlow graph with the C++ API(https://medium.com/jim-fleming/loading-a-tensorflow-graph-with-the-c-api-4caaff88463f),按道理,应该只需要参考后者,即可,但我在实践的时候,碰到了一些问题。
1、用python创建graph,并保存成pb文件
执行代码后,会得到model文件夹,在model文件夹下有一个graph.pb文件。
2、用c++ 构建session,读取pb文件,执行
在tensorflow的源码目录中,创建文件夹 /tensorflow-master/tensorflow/user/loader/
在该文件夹下创建loader.cc文件,粘贴如下的代码:
3、创建BUILD文件 (输入命令, touch BUILD)
粘贴如下的代码:
这里的代码和那边博客里的代码有区别,按照博客里的代码,会出错,不能编译成功,用这个代码后,编译成功了。这里的代码时从官网C++ API介绍从copy出来的。
这里出现的问题如下,
最后,在/user/loader目录下,有两个文件
BUILD
loader.cc
4、编译运行
a)、配置tensorflow-master中的./configure
b)、在loader目录下,执行 bazel build :loader
c)、进入到tensorflow-master/bazel-bin/tensorflow/heke/loader,并把前面用python生成的model/graph.pb 复制到loader目录下
d)、运行./loader
这里运行的时候,也可以直接用bazel run :loader来运行,结合了编译和运行两个阶段
注:这里用的c++代码的编译方式和前面的博客用c++API加载python训练好的tensorflow模型 里用的编译不一致,那么相当于提供了两种c++代码的编译方式。都可以尝试。这里也试过用libtensorflow.so库的方式来编译,但有一些bug,看下能否解决吧。
用 gcc gcc -I /usr/local/include/tf -L /usr/local/lib train.cc -ltensorflow 报需要用-std=c++11,
以及/usr/local/include/tf/third_party/eigen3/unsupported/Eigen/CXX11/Tensor:1:42: fatal error: unsupported/Eigen/CXX11/Tensor: No such file or directory 的错误
用gcc -std=c++11 -I /usr/local/include/tf -L /usr/local/lib train.cc -ltensorflow 就不会报-std=c++11的错误,但是下面的错误继续会报。
这个错误,目前先解决不了,后续再看吧
其实上面的问题,不是关键,这个应该可以通过安装eigen,并在编译时,指定eigen的目录可以解决。
更重要的错误在于,用gcc编译时,目前只有tensorflow/c/c_api.h支持的最好,其它的都需要导入各种各样的头文件。会提醒缺少各种各样的头文件。这个问题目前好像还比较难解决,可以参见https://github.com/tensorflow/tensorflow/issues/2412 中asimshankar的回复。
主要参考两个网址,tensorflow的c++ API官网(https://www.tensorflow.org/api_guides/cc/guide)和Loading a TensorFlow graph with the C++ API(https://medium.com/jim-fleming/loading-a-tensorflow-graph-with-the-c-api-4caaff88463f),按道理,应该只需要参考后者,即可,但我在实践的时候,碰到了一些问题。
1、用python创建graph,并保存成pb文件
import tensorflow as tf
import numpy as np
with tf.Session() as sess:
a = tf.Variable(5.0, name='a')
b = tf.Variable(6.0, name='b')
c = tf.multiply(a, b, name="c")
sess.run(tf.global_variables_initializer())
print a.eval() # 5.0
print b.eval() # 6.0
print c.eval() # 30.0
tf.train.write_graph(sess.graph_def, 'models/', 'graph.pb', as_text=False)
执行代码后,会得到model文件夹,在model文件夹下有一个graph.pb文件。
2、用c++ 构建session,读取pb文件,执行
在tensorflow的源码目录中,创建文件夹 /tensorflow-master/tensorflow/user/loader/
在该文件夹下创建loader.cc文件,粘贴如下的代码:
#include "tensorflow/core/public/session.h"
#include "tensorflow/core/platform/env.h"
using namespace tensorflow;
int main(int argc, char* argv[]) {
// Initialize a tensorflow session
Session* session;
Status status = NewSession(SessionOptions(), &session);
if (!status.ok()) {
std::cout << status.ToString() << "\n";
return 1;
}
// Read in the protobuf graph we exported
// (The path seems to be relative to the cwd. Keep this in mind
// when using `bazel run` since the cwd isn't where you call
// `bazel run` but from inside a temp folder.)
GraphDef graph_def;
status = ReadBinaryProto(Env::Default(), "models/graph.pb", &graph_def);
if (!status.ok()) {
std::cout << status.ToString() << "\n";
return 1;
}
// Add the graph to the session
status = session->Create(graph_def);
if (!status.ok()) {
std::cout << status.ToString() << "\n";
return 1;
}
// Setup inputs and outputs:
// Our graph doesn't require any inputs, since it specifies default values,
// but we'll change an input to demonstrate.
Tensor a(DT_FLOAT, TensorShape());
a.scalar<float>()() = 3.0;
Tensor b(DT_FLOAT, TensorShape());
b.scalar<float>()() = 2.0;
std::vector<std::pair<string, tensorflow::Tensor>> inputs = {
{ "a", a },
{ "b", b },
};
// The session will initialize the outputs
std::vector<tensorflow::Tensor> outputs;
// Run the session, evaluating our "c" operation from the graph
status = session->Run(inputs, {"c"}, {}, &outputs);
if (!status.ok()) {
std::cout << status.ToString() << "\n";
return 1;
}
// Grab the first output (we only evaluated one graph node: "c")
// and convert the node to a scalar representation.
auto output_c = outputs[0].scalar<float>();
// (There are similar methods for vectors and matrices here:
// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/public/tensor.h)
// Print the results
std::cout << outputs[0].DebugString() << "\n"; // Tensor<type: float shape: [] values: 30>
std::cout << output_c() << "\n"; // 30
// Free any resources used by the session
session->Close();
return 0;
}
3、创建BUILD文件 (输入命令, touch BUILD)
粘贴如下的代码:
load("//tensorflow:tensorflow.bzl", "tf_cc_binary")
tf_cc_binary(
name = "loader",
srcs = ["loader.cc"],
deps = [
"//tensorflow/cc:cc_ops",
"//tensorflow/cc:client_session",
"//tensorflow/core:tensorflow",
],
)
这里的代码和那边博客里的代码有区别,按照博客里的代码,会出错,不能编译成功,用这个代码后,编译成功了。这里的代码时从官网C++ API介绍从copy出来的。
这里出现的问题如下,
用bazel build :loader 时
在bazel-bin/tensorflow/loader下不能找到可执行文件./loader
在build时,也是会报错的,提示如下:
collect2: error: ld returned 1 exit status
Target //tensorflow/heke/loader:loader failed to build
用bazel run :loader会报错,同上
collect2: error: ld returned 1 exit status
Target //tensorflow/heke/loader:loader failed to build
最后,在/user/loader目录下,有两个文件
BUILD
loader.cc
4、编译运行
a)、配置tensorflow-master中的./configure
b)、在loader目录下,执行 bazel build :loader
c)、进入到tensorflow-master/bazel-bin/tensorflow/heke/loader,并把前面用python生成的model/graph.pb 复制到loader目录下
d)、运行./loader
这里运行的时候,也可以直接用bazel run :loader来运行,结合了编译和运行两个阶段
注:这里用的c++代码的编译方式和前面的博客用c++API加载python训练好的tensorflow模型 里用的编译不一致,那么相当于提供了两种c++代码的编译方式。都可以尝试。这里也试过用libtensorflow.so库的方式来编译,但有一些bug,看下能否解决吧。
用 gcc gcc -I /usr/local/include/tf -L /usr/local/lib train.cc -ltensorflow 报需要用-std=c++11,
以及/usr/local/include/tf/third_party/eigen3/unsupported/Eigen/CXX11/Tensor:1:42: fatal error: unsupported/Eigen/CXX11/Tensor: No such file or directory 的错误
用gcc -std=c++11 -I /usr/local/include/tf -L /usr/local/lib train.cc -ltensorflow 就不会报-std=c++11的错误,但是下面的错误继续会报。
这个错误,目前先解决不了,后续再看吧
其实上面的问题,不是关键,这个应该可以通过安装eigen,并在编译时,指定eigen的目录可以解决。
更重要的错误在于,用gcc编译时,目前只有tensorflow/c/c_api.h支持的最好,其它的都需要导入各种各样的头文件。会提醒缺少各种各样的头文件。这个问题目前好像还比较难解决,可以参见https://github.com/tensorflow/tensorflow/issues/2412 中asimshankar的回复。