文章目录
本篇博客的内容分为两部分:1、在windows10下编译GPU版本的tensorflow,并获得tensorflow.lib和tensorflow.dll文件;2、利用这两个文件,创建一个C++工程,编写inference示例程序
一、windows10下编译GPU版本的tensorflow
1、首先需要准备的环境
- x墙工具
- vs2015
- swig,官网是这里,注意下载windows版本的(含有.exe文件),解压即可
- python,建议3.6或以上版本
- CMake,官网下载安装,安装完成后添加到环境变量里(../CMake/bin)
- Git,官网下载安装,安装完成后添加到环境变量里(../Git/bin)
- cuda及cudnn,本文编译的是最新的1.6版本tf,所以cuda要求9.0,cudnn要求7,限于篇幅,配置方法自行网上搜索
- 矩阵运算库,例如BLAS、MKL、eigen这些,这里我选择的是eigen,下载最新的版本,解压后添加到环境变量里(../eigen3.3.4)
2、下载tensorflow源码,配置CMakeLists.txt
打开命令提示符,cd到专门的路径下,输入
git clone --recursive https://github.com/tensorflow/tensorflow.git
- 1
下载好tensorflow源码后,找到tensorflow/contrib/cmake/CMakeLists.txt,搜索“tensorflow_OPTIMIZE_FOR_NATIVE_ARCH”,找到后做出如下修改
if (tensorflow_OPTIMIZE_FOR_NATIVE_ARCH)
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-march=native" COMPILER_OPT_ARCH_NATIVE_SUPPORTED)
if (COMPILER_OPT_ARCH_NATIVE_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
else()
CHECK_CXX_COMPILER_FLAG("/arch:AVX" COMPILER_OPT_ARCH_AVX_SUPPORTED)
if(COMPILER_OPT_ARCH_AVX_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX")
endif()
endif()
endif()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
3、开始编译lib和dll
首先进入tensorflow/contrib/cmake目录下,新建一个build文件夹。然后打开命令提示符,输入cmake-gui,配置相关的路径,再configure(选择vs2015的那个vc14 64位编译器),即可得到下图
原本根据需要自行勾选编译选项,再configure+generate,即可打开vs2015完成编译,我最开始就是这么做的,但始终没有成功,报的错误也看不懂(后来我在知乎上看到有一篇文章,作者说tensorflow各工程之间依赖十分复杂,直接用ALL_BUILD要改一些配置)。所以这里我采用的是vs2015自带的编译工具,如下图
右键-更多-以管理员身份运行,先cd到tensorflow/contrib/cmake/build目录下,再输入以下内容进行configure
cmake .. -A x64 -DCMAKE_BUILD_TYPE=Release -DSWIG_EXECUTABLE=D:/3rd_party/swigwin-3.0.12/swig.exe -DPYTHON_EXECUTABLE=D:/python.exe -DPYTHON_LIBRARIES=D:/libs/python36.lib -Dtensorflow_ENABLE_GPU=ON -Dtensorflow_ENABLE_GRPC_SUPPORT=OFF -Dtensorflow_BUILD_SHARED_LIB=ON
- 1
说明一下需要自行修改的参数,SWIG_EXECUTABLE是swig.exe所在路径,PYTHON_EXECUTABLE和PYTHON_LIBRARIES分别是python的exe和lib所在路径,这些都需要自己配,而且路径不能含有空格或者中文字符。后面tensorflow_ENABLE_GRPC_SUPPORT涉及到tensorflow线上部署,默认是ON的状态,如果要编译,则需要下载一堆文件,这里我不需要,就关闭了。另外要想指定编译其它内容,可以参考前面cmake-gui的那张图,配置的格式就是”-D+xxx=ON/OFF”。修改完这段话后,即可执行,等待configure done。
configure完成之后,就要开始正式编译动态库了。此时需要打开你的x墙工具,因为编译的过程中,会从网上下载几个文件,虽然都不大,但它们是存储在含有google的网址内的。接下来,在刚才的命令行窗口继续输入
MSBuild /p:Configuration=Release ALL_BUILD.vcxproj
- 1
注意如果想编debug版本的动态库,就把Release改成Debug。接下来就是漫长的等待了,在我的i7电脑下,编了大概三小时才完成,期间电脑会特别卡(vs默认是多线程编译的)。注意最后不能有错误,否则无法顺利生成tensorflow.lib和tensorflow.dll。最终得到的lib和dll是在tensorflow/contrib/cmake/build/Release目录下,这里提供我编译的一个release版本,还包含整理好的头文件。
二、基于tensorflow C++ api的inference示例程序
前面编译的方法其实很多网址都能搜到,这里写出来是为了做个记录。接下来写的才是本文的重点:如何利用已训练好的tensorflow模型,在windows下编写inference程序,打包生成exe文件,供线下部署。
1、在vs2015中新建项目,配置环境
头文件的配置如下
说明:第一个eigen是前面所说的矩阵运算库,因为c++没有类似numpy的东西(不过c++14有个xtensor),所以矩阵运算可以利用eigen的api。E:\Git\tensorflow是我的tensorflow源码根目录,此外还需要包含一些build里面的头文件,因为部分.h和.cc文件是编译的时候生成的。
lib文件配置即配置tensorflow.lib所在目录,此外注意配置tensorflow.dll
2、新建main.cpp。
本程序的主要功能是输入一张图片,读取tensorflow模型,给出运算结果。下面一步步介绍其实现
2.1 宏定义和头文件
先上代码
#define COMPILER_MSVC
#define NOMINMAX
#define PLATFORM_WINDOWS // 指定使用tensorflow/core/platform/windows/cpu_info.h
#include<iostream>
#include<opencv2/opencv.hpp>
#include"tensorflow/core/public/session.h"
#include "tensorflow/core/platform/env.h"
using namespace tensorflow;
using std::cout;
using std::endl;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
这里需要将宏定义放在最前面,因为涉及到平台、编译器这些,否则就会报一些未定义的错误。另外这里使用了tensorflow的命名空间,是为了使后面的代码更简洁一些。
2.2 读取图片
有两种方式:一是用tensorflow自带的api,参考这里的讨论;二是利用opencv读取,再转化为tensorflow可识别的数据格式,这种方法的好处就是,可以很方便地利用opencv库做一些预处理。下面介绍第二种方法
// 设置输入图像
cv::Mat img = cv::imread(image_path);
cv::cvtColor(img, img, cv::COLOR_BGR2GRAY);
int height = img.rows;
int width = img.cols;
int depth = img.channels();
// 图像预处理
img = (img - 128) / 128.0;
img.convertTo(img, CV_32F);
// 取图像数据,赋给tensorflow支持的Tensor变量中
const float* source_data = (float*)img.data;
tensorflow::Tensor input_tensor(DT_FLOAT, TensorShape({1, height, width, depth })); //这里只输入一张图片,参考tensorflow的数据格式NHWC
auto input_tensor_mapped = input_tensor.tensor<float, 4>(); // input_tensor_mapped相当于input_tensor的数据接口,“4”表示数据是4维的。后面取出最终结果时也能看到这种用法
// 把数据复制到input_tensor_mapped中,实际上就是遍历opencv的Mat数据
for (int i = 0; i < height; i++) {
const float* source_row = source_data + (i * width * depth);
for (int j = 0; j < width; j++) {
const float* source_pixel = source_row + (j * depth);
for (int c = 0; c < depth; c++) {
const float* source_value = source_pixel + c;
input_tensor_mapped(0, i, j, c) = *source_value;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
需要说明的都放在了代码注释当中,这里用到了auto,包括后面的代码也用到了不少,主要原因是我也还未弄清部分数据的格式(后来看到了tf官方的示例,他们也建议用auto,这样代码更简洁一些)。
2.3 建立会话,加载模型文件
模型来自我前段时间训练的验证码识别,注意要保存为.pb格式,这样inference的时候就不用重新搭建网络结构了。
// 初始化tensorflow session
Session* session;
Status status = NewSession(SessionOptions(), &session);
if (!status.ok()){
std::cerr << status.ToString() << endl;
return -1;
}
else {
cout << "Session created successfully" << endl;
}
// 读取二进制的模型文件到graph中
GraphDef graph_def;
status = ReadBinaryProto(Env::Default(), model_path, &graph_def);
if (!status.ok()) {
std::cerr << status.ToString() << endl;
return -1;
}
else {
cout << "Load graph protobuf successfully" << endl;
}
// 将graph加载到session
status = session->Create(graph_def);
if (!status.ok()) {
std::cerr << status.ToString() << endl;
return -1;
}
else {
cout << "Add graph to session successfully" << endl;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
2.4 定义输入输出数据格式,run inference,输出数据可视化
// 输入inputs,“ x_input”是我在模型中定义的输入数据名称,此外模型用到了dropout,所以这里有个“keep_prob”
tensorflow::Tensor keep_prob(DT_FLOAT, TensorShape());
keep_prob.scalar<float>()() = 1.0;
std::vector<std::pair<std::string, tensorflow::Tensor>> inputs = {
{ "x_input", input_tensor },
{ "keep_prob", keep_prob },
};
// 输出outputs
std::vector<tensorflow::Tensor> outputs;
// 运行会话,计算输出"x_predict",即我在模型中定义的输出数据名称,最终结果保存在outputs中
status = session->Run(inputs, { "x_predict" }, {}, &outputs);
if (!status.ok()) {
std::cerr << status.ToString() << endl;
return -1;
}
else {
cout << "Run session successfully" << endl;
}
// 下面进行输出结果的可视化
tensorflow::Tensor output = std::move(outputs.at(0)); // 模型只输出一个结果,这里首先把结果移出来(也为了更好的展示)
auto out_shape = output.shape(); // 这里的输出结果为1x4x16
auto out_val = output.tensor<float, 3>(); // 与开头的用法对应,3代表结果的维度
// cout << out_val.argmax(2) << " "; // 预测结果,与python一致,但具体数值有差异,猜测是计算精度不同造成的
// 输出这个1x4x16的矩阵(实际就是4x16)
for (int i = 0; i < out_shape.dim_size(1); i++) {
for (int j = 0; j < out_shape.dim_size(2); j++) {
cout << out_val(0, i, j) << " ";
}
cout << endl;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
输出结果如下图(懒得打码了,请无视我的显卡。。)
至此,模型输出数据的格式也清楚了,完整流程就是这样,想进一步研究Tensor用法的话可以参考这里,完整的代码参考我的GitHub。
PS
本次完成所有工程,在网上参考了大量资料,时间关系,这里不分先后,列举一下:tensorflow编译、TensorFlow C++ API案例一、TensorFlow C++ API案例二、TensorFlow C++ API案例三(gist貌似也要x墙?)
距离上一次更新博客已经半年了,主要原因还是自己比较懒,这次总算是做了一点综合性的工作,希望以后能继续。另外吐槽一点,百度的搜索太差劲了,我先前的博客,即使输入原标题也在百度里面搜不到,但是在谷歌里面第一条就是。
PPS
更新一个tensorflow官方的c++ api inference例子
Windows10下源码编译基于GPU的tensorflow.dll
时间:2017-09-05 10:06 来源:清屏网 作者:那一抹忧伤 点击:1737次
笔者因为想尝试一些机器学习方面的idea,所以于TensorFlow产生了交集, 笔者搞计算机图形学,所以更多地与windows和visual studio打交道, 于是想在windows和visual studio环境下编译出tensorflow的gpu版本。 但是整个互联网对于在windows和vs2015下编译tensorflow的信息少的可怜, 甚至在tensorflow的官方git hub页面, 也宣称没有在windows+vs环境下成功build出tensorflow GPU。 所以, 似乎, 为了搞点儿机器学习的算法,我们就不得不放弃可爱的windows屈从于Linux了? -- 就因为那些不搞游戏编程的人喜欢用Linux?不可能的。 在开始这篇文章前, 我首先要讲一下为什么要编译tf源码。(从此以后tf就是tensorflow的简称)。 为此, 我首先要声明, 如果读者不想使用tensorflow的高级功能的话, 在windows上安装它的python接口就可以了, 很简单, 也很方便, 同样是带了gpu支持的,是tensorflow开发者对于windows python预编好的接口, 对此, 有以下几点需要注意:
那么, 为什么要编译tensorflow的源代码呢???? -- 因为我想使用tensorflow的高级功能, 其中包括编写tensorflow的插件, 这个工作流程最简化如下所示: 所以, 如果不能在自己的机器上编译出tensorflow的话, 那么tensorflow的真正功能, 相当于只用了很小的一部分, 因为它提供的interface就算再好再全也是有限的, 而扩展的那部分能做的是, 理论上是无限的. 比如完全有可能利用Unreal Engine的插件把UE的内部数据直接导入到tensorflow里去, 也有可能在unity里直接造出一个基于tensorflow的插件. 到此为止总结一下我们面临的问题: 为了使用tf的高级功能, 想在windows上编译tf-gpu, 但是网上存在的信息实在少之又少, 所以笔者开始了漫长的征程. 终于成功的build出了tensorflow.dll以及tensorflow.lib. 下面是我详细的编译过程:
此处有几点必须注意的!!!visual studio一般是默认多线程编译的, 由于tensorflow这个项目文件的dependency过于复杂, 在编译到某些项目的时候会出现: Faltal Error "compiler is out of heap space", 看到这个问题, 不要慌张, 因为你的背后有这篇强大的攻略!! 去你的tools --> options: 在弹出的对话框里如下设置: 然后再去build, 此时, 笔者很不幸的告诉你, 尽管已经这样设置, 还是有可能build不成功的!!!!!有一个很特殊的项目, 由于dependency过于复杂, 仍然会 Faltal Error "compiler is out of heap space" 这个该死的项目就是 "tf_core_kernels" , 请对它特殊照顾!!! 在你的 ALL BUILD编译完不管报了什么错以后, 请独立对这个项目再编译一次, 如图所示: 这样做可以把之前很多没有编译出来的 *.obj 代码给编译出来. , 为了保险起见, 请去重新单独编译(project only build)tensorflow_static 这个项目, 这个项目会编译出tensorflow_static.lib. 在编译这个项目的过程中, 如果有遇到缺少任何xxx.obj, 请按照那个名字搜索 http:// xxx.cc 文件, locate到相应的项目, 并project only build 那个项目. 如果你完成了build tensorflow_static 这个项目, 在你的tensorflow 这个项目(目标是dll), 下会成功的多出tensorflow.def这个source, 这是你tensorflow_static的编译所得!!! 如图: 此时你可以右键, project only, build tensorflow, 然而, 愚昧的你真的以为这次就终于可以了么????? 错!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 由于写那个cmakeList的人没有考虑周全, 所以此处你会有一个unresolved symbol 照理说这个symbol应该是属于tf_c 或者tf_core_cpu那两个项目的, 而且我们已经针对项目only重新build过了, 应该有才对啊.........到底发生了什么????? 原来, tensorflow这个最终的boss项目, 在生成的时候, 忘记把编译好的 *.obj给包含进去了!!!!!!! 然后, 再 右键, project only, build tensorflow, 这次你终于可以成功的build了!!!!!!!!!!!!!!!!!!!!!!!!!!! Creating library C:/Users/zxx_1/tensorflow/tensorflow/contrib/cmake/build/Release/tensorflow.lib and object C:/Users/zxx_1/tensorflow/tensorflow/contrib/cmake/build/Release/tensorflow.exp tensorflow.vcxproj ->C:\Users\zxx_1\tensorflow\tensorflow\contrib\cmake\build\Release \tensorflow.dll 看到这两行字, 心情还是无比激动的. 这意味着接下来你终于可以自由得飞翔了. 在笔者尝试编译的过程中, 这篇文章的帮助很大, 虽然他只build了无gpu的版本, 但还是起到了很多参考作用的, 虽然在看了我的build大法后完全不用去看原来那个文章, 但还是在此提及以作感谢. Building a static Tensorflow C++ library on Windows – Joe Antognini |