参考:
参考:
GitHub地址:
这玩意折腾了几天了,bazel编译好了,但是c++调用一直报错,今天中午发现是我的protobuf版本过低,下面看我的版本情况,防止后面的人采坑,
In [1]: import tensorflow as tf
In [2]: tf.__version__
Out[2]: '1.7.0'
zhoumeixudeMacBook-Pro:~ zhoumeixu$ protoc --version
libprotoc 3.5.1
要调用tensorflow c++接口,首先要编译tensorflow,要装bazel,要装protobuf,要装Eigen,整体过程还是比较麻烦,bazel安装过程就不说了,下面慢慢来说明下安装步骤。
Eigen(矩阵库下载):
wget http://bitbucket.org/eigen/eigen/get/3.3.4.tar.bz2
下载之后解压放在重新命名为eigen3,我存放的路径是,/Users/zhoumeixu/Downloads/eigen3
protobuf下载及安装:
下载地址:https://github.com/google/protobuf/releases ,我下载的是3.5.1版本,如果你是下载新版的tensorflow,请确保protobuf版本也是最新的,安装步骤:
cd /protobuf
./configure
make
sudo make install
安装之后查看protobuf版本:
protocx --version
tensorflow下载以及编译:
1下载TensorFlow ,使用 git clone --recursive
https://github.com/tensorflow/tensorflow
2.下载bazel工具(mac下载installer-darwin、linux用
installer-linux)
3. 进入tensorflow的根目录
3.1 执行./configure 根据提示配置一下环境变量这个官网上有类似的 应该能看到 \
要GPU的话要下载nvidia驱动的 尽量装最新版的驱动吧 还有cudnn version为5以上的 这些在官网都有提及的
3.2 有显卡的执行 " bazel build --config=opt --config=cuda //tensorflow:libtensorflow_cc.so "
3.1 执行./configure 根据提示配置一下环境变量这个官网上有类似的 应该能看到 \
要GPU的话要下载nvidia驱动的 尽量装最新版的驱动吧 还有cudnn version为5以上的 这些在官网都有提及的
3.2 有显卡的执行 " bazel build --config=opt --config=cuda //tensorflow:libtensorflow_cc.so "
没显卡的 " --config=cuda " 就不要加了
如果是c版本的tensorflow, 把" libtensorflow_cc " 改成 " libtensorflow "
这句命令其实是bazel的用法 具体要生成哪个可以 " vim $(TF_ROOT_PATH)/tensorflow/BUILD " 查看
编译需要挺久的 大概大半个小时吧我记得,这里编译要有sudo权限,否则编译过程会报错。
如果是c版本的tensorflow, 把" libtensorflow_cc " 改成 " libtensorflow "
这句命令其实是bazel的用法 具体要生成哪个可以 " vim $(TF_ROOT_PATH)/tensorflow/BUILD " 查看
编译需要挺久的 大概大半个小时吧我记得,这里编译要有sudo权限,否则编译过程会报错。
sudo bazel build --config=opt //tensorflow:libtensorflow_cc.so
4. 一般是不报错的 如果报错要么是依赖项没有 到时候一个个装就好了 也有个错误是说 protoc 版本太低 这时候更新一下protoc就好了
5. bazel build成功后会有提示的 然后拷贝一下头文件 (这里应该没落下, 如果有找不到的话还得再找找吧- -)
我tensorflow clone路径是, /Users/zhoumeixu/Documents,后面在cmakelist.txt文件中注意路径。
下面开始测试在c++中调用tensorflow模型的pb模型,我做了个简单的线性回归模型及生成pb格式模型代码:
import tensorflow as tf
import numpy as np
import os
tf.app.flags.DEFINE_integer('training_iteration', 1000,
'number of training iterations.')
tf.app.flags.DEFINE_integer('model_version', 1, 'version number of the model.')
tf.app.flags.DEFINE_string('work_dir', 'model/', 'Working directory.')
FLAGS = tf.app.flags.FLAGS
sess = tf.InteractiveSession()
x = tf.placeholder('float', shape=[None, 5],name="inputs")
y_ = tf.placeholder('float', shape=[None, 1])
w = tf.get_variable('w', shape=[5, 1], initializer=tf.truncated_normal_initializer)
b = tf.get_variable('b', shape=[1], initializer=tf.zeros_initializer)
sess.run(tf.global_variables_initializer())
y = tf.add(tf.matmul(x, w) , b,name="outputs")
ms_loss = tf.reduce_mean((y - y_) ** 2)
train_step = tf.train.GradientDescentOptimizer(0.005).minimize(ms_loss)
train_x = np.random.randn(1000, 5)
# let the model learn the equation of y = x1 * 1 + x2 * 2 + x3 * 3
train_y = np.sum(train_x * np.array([1, 2, 3,4,5]) + np.random.randn(1000, 5) / 100, axis=1).reshape(-1, 1)
for i in range(FLAGS.training_iteration):
loss, _ = sess.run([ms_loss, train_step], feed_dict={x: train_x, y_: train_y})
if i%100==0:
print("loss is:",loss)
graph = tf.graph_util.convert_variables_to_constants(sess, sess.graph_def,
["inputs", "outputs"])
tf.train.write_graph(graph, ".", FLAGS.work_dir + "liner.pb",
as_text=False)
print('Done exporting!')
print('Done training!')
下面是c++项目代码:
model_loader_base.h:
#ifndef CPPTENSORFLOW_MODEL_LOADER_BASE_H
#define CPPTENSORFLOW_MODEL_LOADER_BASE_H
#include <iostream>
#include <vector>
#include "tensorflow/core/public/session.h"
#include "tensorflow/core/platform/env.h"
using namespace tensorflow;
namespace tf_model {
/**
* Base Class for feature adapter, common interface convert input format to tensors
* */
class FeatureAdapterBase{
public:
FeatureAdapterBase() {};
virtual ~FeatureAdapterBase() {};
virtual void assign(std::string, std::vector<double>*) = 0; // tensor_name, tensor_double_vector
std::vector<std::pair<std::string, tensorflow::Tensor> > input;
};
class ModelLoaderBase {
public:
ModelLoaderBase() {};
virtual ~ModelLoaderBase() {};
virtual int load(tensorflow::Session*, const std::string) = 0; //pure virutal function load method
virtual int predict(tensorflow::Session*, const FeatureAdapterBase&, const std::string, double*) = 0;
tensorflow::GraphDef graphdef; //Graph Definition for current model
};
}
#endif //CPPTENSORFLOW_MODEL_LOADER_BASE_H
ann_model_loader.h:
#ifndef CPPTENSORFLOW_ANN_MODEL_LOADER_H
#define CPPTENSORFLOW_ANN_MODEL_LOADER_H
#include "model_loader_base.h"
#include "tensorflow/core/public/session.h"
#include "tensorflow/core/platform/env.h"
using namespace tensorflow;
namespace tf_model {
/**
* @brief: Model Loader for Feed Forward Neural Network
* */
class ANNFeatureAdapter: public FeatureAdapterBase {
public:
ANNFeatureAdapter();
~ANNFeatureAdapter();
void assign(std::string tname, std::vector<double>*) override; // (tensor_name, tensor)
};
class ANNModelLoader: public ModelLoaderBase {
public:
ANNModelLoader();
~ANNModelLoader();
int load(tensorflow::Session*, const std::string) override; //Load graph file and new session
int predict(tensorflow::Session*, const FeatureAdapterBase&, const std::string, double*) override;
};
}
#endif //CPPTENSORFLOW_ANN_MODEL_LOADER_H
ann_model_loader.cpp:
#include <iostream>
#include <vector>
#include <map>
#include "ann_model_loader.h"
//#include <tensor_shape.h>
using namespace tensorflow;
namespace tf_model {
/**
* ANNFeatureAdapter Implementation
* */
ANNFeatureAdapter::ANNFeatureAdapter() {
}
ANNFeatureAdapter::~ANNFeatureAdapter() {
}
/*
* @brief: Feature Adapter: convert 1-D double vector to Tensor, shape [1, ndim]
* @param: std::string tname, tensor name;
* @parma: std::vector<double>*, input vector;
* */
void ANNFeatureAdapter::assign(std::string tname, std::vector<double>* vec) {
//Convert input 1-D double vector to Tensor
int ndim = vec->size();
if (ndim == 0) {
std::cout << "WARNING: Input Vec size is 0 ..." << std::endl;
return;
}
// Create New tensor and set value
Tensor x(tensorflow::DT_FLOAT, tensorflow::TensorShape({1, ndim})); // New Tensor shape [1, ndim]
auto x_map = x.tensor<float, 2>();
for (int j = 0; j < ndim; j++) {
x_map(0, j) = (*vec)[j];
}
// Append <tname, Tensor> to input
input.push_back(std::pair<std::string, tensorflow::Tensor>(tname, x));
}
/**
* ANN Model Loader Implementation
* */
ANNModelLoader::ANNModelLoader() {
}
ANNModelLoader::~ANNModelLoader() {
}
/**
* @brief: load the graph and add to Session
* @param: Session* session, add the graph to the session
* @param: model_path absolute path to exported protobuf file *.pb
* */
int ANNModelLoader::load(tensorflow::Session* session, const std::string model_path) {
//Read the pb file into the grapgdef member
tensorflow::Status status_load = ReadBinaryProto(Env::Default(), model_path, &graphdef);
if (!status_load.ok()) {
std::cout << "ERROR: Loading model failed..." << model_path << std::endl;
std::cout << status_load.ToString() << "\n";
return -1;
}
// Add the graph to the session
tensorflow::Status status_create = session->Create(graphdef);
if (!status_create.ok()) {
std::cout << "ERROR: Creating graph in session failed..." << status_create.ToString() << std::endl;
return -1;
}
return 0;
}
/**
* @brief: Making new prediction
* @param: Session* session
* @param: FeatureAdapterBase, common interface of input feature
* @param: std::string, output_node, tensorname of output node
* @param: double, prediction values
* */
int ANNModelLoader::predict(tensorflow::Session* session, const FeatureAdapterBase& input_feature,
const std::string output_node, double* prediction) {
// The session will initialize the outputs
std::vector<tensorflow::Tensor> outputs; //shape [batch_size]
// @input: vector<pair<string, tensor> >, feed_dict
// @output_node: std::string, name of the output node op, defined in the protobuf file
tensorflow::Status status = session->Run(input_feature.input, {output_node}, {}, &outputs);
if (!status.ok()) {
std::cout << "ERROR: prediction failed..." << status.ToString() << std::endl;
return -1;
}
//Fetch output value
std::cout << "Output tensor size:" << outputs.size() << std::endl;
for (std::size_t i = 0; i < outputs.size(); i++) {
std::cout << outputs[i].DebugString();
}
std::cout << std::endl;
Tensor t = outputs[0]; // Fetch the first tensor
int ndim = t.shape().dims(); // Get the dimension of the tensor
auto tmap = t.tensor<float, 2>(); // Tensor Shape: [batch_size, target_class_num]
int output_dim = t.shape().dim_size(1); // Get the target_class_num from 1st dimension
std::vector<double> tout;
// Argmax: Get Final Prediction Label and Probability
int output_class_id = -1;
double output_prob = 0.0;
for (int j = 0; j < output_dim; j++) {
std::cout << "Class " << j << " prob:" << tmap(0, j) << "," << std::endl;
if (tmap(0, j) >= output_prob) {
output_class_id = j;
output_prob = tmap(0, j);
}
}
// Log
std::cout << "Final class id: " << output_class_id << std::endl;
std::cout << "Final value is: " << output_prob << std::endl;
(*prediction) = output_prob; // Assign the probability to prediction
return 0;
}
}
main.cpp
#include <iostream>
#include "tensorflow/core/public/session.h"
#include "tensorflow/core/platform/env.h"
#include "ann_model_loader.h"
using namespace tensorflow;
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cout << "WARNING: Input Args missing" << std::endl;
return 0;
}
std::string model_path = argv[1]; // Model_path *.pb file
// TensorName pre-defined in python file, Need to extract values from tensors
std::string input_tensor_name = "inputs";
std::string output_tensor_name = "outputs";
// Create New Session
Session* session;
Status status = NewSession(SessionOptions(), &session);
if (!status.ok()) {
std::cout << status.ToString() << "\n";
return 0;
}
// Create prediction demo
tf_model::ANNModelLoader model; //Create demo for prediction
if (0 != model.load(session, model_path)) {
std::cout << "Error: Model Loading failed..." << std::endl;
return 0;
}
// Define Input tensor and Feature Adapter
// Demo example: [1.0, 1.0, 1.0, 1.0, 1.0] for Iris Example, including bias
int ndim = 5;
std::vector<double> input;
for (int i = 0; i < ndim; i++) {
input.push_back(1.0);
}
// New Feature Adapter to convert vector to tensors dictionary
tf_model::ANNFeatureAdapter input_feat;
input_feat.assign(input_tensor_name, &input); //Assign vec<double> to tensor
// Make New Prediction
double prediction = 0.0;
if (0 != model.predict(session, input_feat, output_tensor_name, &prediction)) {
std::cout << "WARNING: Prediction failed..." << std::endl;
}
std::cout << "Output Prediction Value:" << prediction << std::endl;
return 0;
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(cpptensorflow)
set(CMAKE_CXX_STANDARD 11)
link_directories(/Users/zhoumeixu/Documents/tensorflow/bazel-bin/tensorflow)
include_directories(
/Users/zhoumeixu/Documents/tensorflow
/Users/zhoumeixu/Documents/tensorflow/bazel-genfiles
/Users/zhoumeixu/Documents/tensorflow/bazel-bin/tensorflow
/Users/zhoumeixu/Downloads/eigen3
)
add_executable(cpptensorflow main.cpp ann_model_loader.h model_loader_base.h ann_model_loader.cpp)
target_link_libraries(cpptensorflow tensorflow_cc tensorflow_framework)
主要路径,我在前面提到过来,eige3是解压eigen重命名的路径。
编译项目:
mkdir build
cd build
cmake ..
make
生成cpptensorflow执行文件:
./cpptensorflow /Users/zhoumeixu/Documents/python/credit-nlp-ner/model/liner.pb
Final value is: 14.9985
Output Prediction Value:14.9985
python加载pb模型预测:
import tensorflow as tf
import numpy as np
logdir = '/Users/zhoumeixu/Documents/python/credit-nlp-ner/model/'
output_graph_path = logdir+'liner.pb'
with tf.Graph().as_default():
output_graph_def = tf.GraphDef()
with open(output_graph_path, "rb") as f:
output_graph_def.ParseFromString(f.read())
_ = tf.import_graph_def(output_graph_def,name="")
with tf.Session() as sess:
input = sess.graph.get_tensor_by_name("inputs:0")
output = sess.graph.get_tensor_by_name("outputs:0")
result = sess.run(output, feed_dict={input: np.reshape([1.0,1.0,1.0,1.0,1.0],[-1,5])})
print(result)
运行结果:
[[14.998546]]
顺便提一下java 的tensorflow api接口对tensor的输入用的是array,而c++用的确实vector,在这里有点不不一样,下面有时间写个thrif服务玩玩,tensorflow 的 python 、java、c++的接口全部测通了,只能说到目前为止tensorflow是我用过最好用的机器学习框架,在这里不得不感谢Jeff Dean,工程师出身,设计的机器学习框架简直是完美。