windows10+vs2015下编译GPU版本将Tensorflow封装成SDK

文章目录

本篇博客的内容分为两部分: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预编好的接口, 对此, 有以下几点需要注意:

  1. 必须先安装好cuda8.0, 其它版本不建议, cuda8.0可从nvidia的官网获得, 使用默认安装路径即可
  2. 之后安装cudnn5.0, 任何其它版本不适用。 因为nvidia提供的cudnn只是一个 .zip文件, 所以解压后把里头的 \bin; \include; \lib; 文件夹合并到cuda的安装目录下, 比如我的为: C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\
  3. 之后安装python, 必须是 python3.5 , 任何其它版本不可。安装python时,请勾选“添加系统Path”。
  4. 接下来, 打开你的cmd, 输入pip install update pip; 这个指令会自动更新python的pip功能,帮助它识别tensorflow软件包。
  5. 接下来, pip install update numpy。
  6. 最后,pip install update tensorflow-gpu。
  7. 此安装简单易懂, 很容易完成, 安装好的tensorflow绝对能跑(在python环境下), 比如此时, 你可以: 
    >python 
    >import tensorflow as tf 
    >tf.Session() 
    这时候tensorflow就会输出你的显卡型号等的信息.

那么, 为什么要编译tensorflow的源代码呢????

-- 因为我想使用tensorflow的高级功能, 其中包括编写tensorflow的插件, 这个工作流程最简化如下所示:

所以, 如果不能在自己的机器上编译出tensorflow的话, 那么tensorflow的真正功能, 相当于只用了很小的一部分, 因为它提供的interface就算再好再全也是有限的, 而扩展的那部分能做的是, 理论上是无限的. 比如完全有可能利用Unreal Engine的插件把UE的内部数据直接导入到tensorflow里去, 也有可能在unity里直接造出一个基于tensorflow的插件.

到此为止总结一下我们面临的问题: 为了使用tf的高级功能, 想在windows上编译tf-gpu, 但是网上存在的信息实在少之又少, 所以笔者开始了漫长的征程. 终于成功的build出了tensorflow.dll以及tensorflow.lib. 下面是我详细的编译过程:

  • 让我们开始一个相对来说还算干净的windows 10. 比如我正好因为一些原因重装过系统.
  • 安装visual studio 2015. 注意, 请安装这个版本.
  • 下载官方的cuda8.0, 默认安装路径.
  • 下载cudnn5.0 for CUDA8.0, exactly. 其它版本皆不可. 讲解压后的文件合并到cuda的安装目录下.
  • 下载并安装git bash.
  • 下载并安装cmake 3.9
  • 下载并安装swig, Windows users should download swigwin-3.0.12 which includes a prebuilt executable.
  • 进入你的git bash, 输入git clone https:// github.com/tensorflow/t ensorflow.git 
  • 回车, 此时git会自动下载tensorflow 源代码, 成功的话, 你的users\xxx\ 目录下会多出tensorflow的文件夹.
  • 打开你的cmake gui, 因为有很多选项, 所以我不建议在command line 里头调用. 
    在打开的界面, 给予tensorflow的cmakeList的位置和你想要把VC项目放到哪里去的文件夹, 典型的如下所示, 请勾选grouped 和advanced 两个tag
  • 接下来, 点击cmake的config,选择visual studio2015作为你的build 目标, 点击finish 。
  • 此时你一定会看见一些error, 不要慌张, 大多是因为swig没找到或者python没找到或者cuda没找到之类的信息, 因为我们用的是gui版Cmake, 这些问题很好解决!!!

    比如:

  • 此时只需要手动输入所相关的路径, 如图(我这种懒鬼会把.exe直接装到桌面上的.....)
  • 此时你再点config, 就不会出错了, 此时你的设置大概是这样:
  • 展开tensorflow 这个tag, 并做如下勾选
  • 此时如果再点config那个按钮, 你的界面应该变成这样, 多出了有关CUDA的tag!!
  • 对于CUDA, 我们并不需要什么特别的设置, 此时点Generate, Cmake就会自动生成相应的tensorflow.sln, 这个可爱的, 我们可以用visual studio 2015打开的好东西!!!!!!!
  • Generate完后点击Open Project.
  • 理论上在ALL BUILD 那里右键BUILD就可以, 但是....................如果这样就能够build成功了, 我还tmd需要写这篇文章吗?????? 因为tensorflow的作者根本就没考虑windows的缘故, 就这样build一般是build不成功的!!!!!

此处有几点必须注意的!!!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

猜你喜欢

转载自blog.csdn.net/maweifei/article/details/82218842
今日推荐