2019.5.31 更新

最近在研究如何用tensorflow的python库训练模型,生成pb文件,再在c++中调用这个模型,完成前向的预测。

具体步骤如下:

①在python中,用tensorflow的python库,训练模型,并生成pb文件;

②准备好tensorflow的c++库;

③在c++中用tensorflow的API来调用这个pb文件,完成前向计算过程。

 

下面分别说每一步:

① 训练就不多说了,不过要留意整个计算图中的输入输出的tensor的名称,例如我的网络有两个输入,叫'input1'和'input2',有两个输出,叫'output1'和'output2',这些名字后面要用到。

生成pb文件可以添加如下代码:

constant_graph = tf.graph_util.convert_variables_to_constants(sess, sess.graph_def,['output1','output2'])
with tf.gfile.FastGFile('weight.pb', mode='wb') as f:
    f.write(constant_graph.SerializeToString())

请注意上述代码第一行中需要写上输出tensor的名称列表(输入tensor不用写)。

 

② 我在github上发现了一个好资源,tensorflow的很多编译好的版本都能直接下。。。不用自己费劲编译了。。

https://github.com/fo40225/tensorflow-windows-wheel

给这个人点赞!

不过我看了一下,这里面只有tensorflow1.7~1.10的版本有编译好的cpp库,其他的版本只有编译好的安装tf的python库的whl文件,所以强烈推荐大家用1.7~1.10的tensorflow。

例如我需要tensorflow1.10的、cpu支持AVX2指令集的版本,就下载tensorflow-windows-wheel/1.10.0/cpp/libtensorflow-cpu-windows-x86_64-1.10.0-avx2.7z

例如我需要tensorflow1.10的、cpu支持SSE2指令集、且gpu支持CUDA9.2和CUDNN7.2的版本,就下载tensorflow-windows-wheel/1.10.0/cpp/libtensorflow-gpu-windows-x86_64-1.10.0-sse2cuda92cudnn72.7z

然后把其中的include路径包含进去,把lib文件添加到工程中,再在执行exe时把dll文件复制进去就好了,参考下面的【三】和【四】。

 

如果这个github仓里没有你需要的版本,你就只能自己编译了,贼麻烦,往下看。

根据官网给出的编译器对应关系(详见我的另一篇博客),最新版(1.12版)的tensorflow需要用bazel去编译,官网的编译教程也给的是用bazel编译,但是我弄了半天然后失败了。。 所以我就用cmake去编译旧版(以下以1.4版为例)。

以下我会给出报错信息和解决方法。

注意:编译用 ,其他的没试过。

 

具体步骤:

一、环境准备

  1. 操作系统:windows
  2. 安装git,git官方下载地址
  3. 安装visual studio 2015(即vs14)
  4. 安装python3.5或3.6(或直接安装anaconda,下载地址详见我的另一篇博客)并加入环境变量
  5. 下载swig,swig官方下载地址
  6. 安装cmake,版本号需要>=3.6.3,cmake官方下载地址
  7. 科学上网(最好有,否则一些依赖的文件可能下载不下来导致编译失败,错误信息为:error downloading xxx failed

 

二、操作步骤

  1. 用git下载源码(或者直接去tensorflow的github地址下载其源码的zip包
  2. git clone -b v1.4.0 https://github.com/tensorflow/tensorflow.git

    用cmake预编译。 在SWIG_EXECUTABLE选项中输入swig.exe的绝对路径;底下打勾的可以视情况选择。 然后点Configure,再点Generate。如果出错了可能是环境变量没设置好。

  3. 用vs打开G:\tensorflow-1.4.0\tensorflow\contrib\cmake\build中的tensorflow.sln,分别将前面这几个以下划线开头的项目的属性修改一下,方法:右击项目→属性(R)→配置属性→链接器→常规→附加库目录,添加$(SolutionDir)$(Configuration);  如图所示。 不这样做的话,编译这几个项目时会报错,错误信息: fatal error LNK1181: 无法打开输入文件“\pywrap_tensorflow_internal.lib”                                                                                                                          

  4. 设置最多并行生成几个项目,方法:菜单栏中的工具(T)→选项(O)→项目和解决方案→生成并运行,本来是4,可以改小一点,我改成了2。  不这样做的话,编译时会导致堆内存被耗尽,错误信息: fatal error C1060: 编译器的堆空间不足

  5. 翻好墙,关掉360,关掉防火墙,关掉其他程序给编译腾出内存空间,现在可以开始生成ALL_BUILD了。大约要等3个小时左右。

  6. 如果显示re2报错,错误信息: error C2001: 常量中有换行符,那就用vs打开G:\tensorflow-1.4.0\tensorflow\contrib\cmake\build\re2\src\re2中的RE2.sln,分别将re2_test.cc和search_test.cc的编码方式修改掉,方法:双击打开cc文件→单击菜单栏中的文件(F)→高级保存选项(文件下拉菜单中没有高级保存选项的请自行百度一下,设置一下就有了),改成UTF-8带签名,如图所示。然后生成RE2的ALL_BUILD。           

 

三、编译成功后我们可以用这些头文件以及tensorflow.lib/dll来编写自己的测试工程了。

  1. 新建工程,头文件为TestTensorFlow.h
    #pragma once  //这一句防止重复include头文件
    
    #define COMPILER_MSVC
    #define NOMINMAX  //这一句防止max/min函数命名冲突

     

  2. 源文件为TestTensorFlow.cpp

    #include "TestTensorFlow.h"
    
    #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;
    }

     

  3. 右击项目→属性→配置属性→C/C++→常规→附加包含目录,输入如图所示的路径。  不要把third_party中的路径加进去,否则可能会出现 fatal error C1014: 包含文件太多 : 深度=1024

  4. 右击项目,添加现有项,选择G:\tensorflow-1.4.0\tensorflow\contrib\cmake\build\Release中的tensorflow.lib

  5. 将G:\tensorflow-1.4.0\tensorflow\contrib\cmake\build\Release中的tensorflow.dll复制到此工程的exe同级目录下(即x64/Release下)

  6. Ctrl+F5执行

四、调用pb文件

假设我们的网络输入是一个四维张量,shape为(1,299,299,3),代码如下:

using namespace tensorflow;

//建立会话
Session *session;
Status status = NewSession(SessionOptions(), &session);

//加载tensorflow模型pb文件
GraphDef graph_def;
status = ReadBinaryProto(Env::Default(), pbpath, &graph_def);
status = session->Create(graph_def);

Tensor x(DT_FLOAT, TensorShape({ 1, 299, 299, 3 }));
auto tensormap = x.tensor<float, 4>();
//然后对这个tensormap赋值,例如写循环,tensormap(0, i, j, k)=某数字

//构造输入输出的vector<Tensor>
std::vector<std::pair<string, Tensor>> inputs = { { "input_X", x } };
std::vector<Tensor> outputs;
status = session->Run(inputs, { "out" }, {}, &outputs);
//输入输出的tensor的名字要跟建立该pb文件的name相同。
//outputs里可以取出网络的输出。 单输出就是outputs[0],多输出就是outputs[0]、outputs[1]、outputs[2]...等等