热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

tensorflow源码编译教程_【填坑】基于TensorFlowC++API的gRPC服务

之前实习的时候训练一个给ASR文本添加大小写和标点的模型,框架用的是tensorflowr1.2(本文其实和tensorflow版本无关)。模型训好后mentor说要
0a81a4ce7945e2209c5283db5938161c.png

之前实习的时候训练一个给ASR文本添加大小写和标点的模型,框架用的是tensorflow r1.2(本文其实和tensorflow版本无关)。模型训好后mentor说要转成C++上线,当时差点崩溃,由于太懒,不想换框架重写就只好试试tensorflow的C++ API了,由于公司服务器的权限问题也是躺了不少的坑,这里简单总结一下TF模型转C++ API以及转gRPC服务的基本步骤和遇到的一些很迷的Errors。
关于Tensorflow模型到gRPC服务,tensorflow有个神奇API叫Tensorflow Serving,大家可以试一试。不过本文不是采用这种方式,而是先转C++接口,再用gRPC写接口服务,其实原理是一样的。

Tensorflow C++ API

Tensorflow提供的C++API能够恢复python训练好的模型计算图和参数到C++环境中;通过向Placeholder传入数据便可以得到Eigen::Tensor类型的返回。python下的Tensorflow依赖numpy矩阵运算库,而在C++下依赖Eigen::Tensor库,所以在使用C++ API之前需要先安装好对应版本的Eigen库;
因为模型最后是一层CRF,所以还需要用Eigen重写Viterbi解码,还好只是简单的DP问题;这里简单介绍一下提到的模型的结构:525通道CNN + HighWayNet + bi-LSTM + CRF;

6819ea0b7a6bec6e367dff27475ced79.png

下载Tensorflow源码

下载最新的Tensorflow源码,这个和你使用什么版本Tensorflow训练模型没有关系。之后就需要把Tensorflow编译成我们需要的动态链接库;

$ git clone https://github.com/tensorflow/tensorflow.git

安装Bazel

这里需要注意一下,版本太新和太旧的Bazel在编译Tensorflow的时候都会报错,这里举例我用过的版本组合:Bazel-0.10.0(Tensorflow-r1.7);Bazel-0.8.0(Tensorflow-r1.5);Bazel-0.4.5(Tensorflow-r1.2)。以上组合并不固定,经供参考(本文是Tensorflow1.7)。由于在公司服务器上工作,所有的third-party都需要安装在自己的目录。
1 . 非root安装JDK8:jdk-8u161-linux-x64.tar.gz。Bazel依赖JDK8,wget下载后解压,把jdk添加到环境变量,把以下代码添加到$HOME/.bashrc:

export JAVA_HOME="$HOME/tools/java/jdk1.8.0_161"export JAVA_BIN=$JAVA_HOME/binexport JAVA_LIB=$JAVA_HOME/libexport CLASSPATH=.:$JAVA_LIB/tools.jar:$JAVA_LIB/dt.jarexport PATH=$JAVA_BIN:$PATH

2 . 安装Bazel:各版本地址release,本文是bazel-0.10.0,下载好.sh文件之后执行一下命令:

chmod +x bazel--installer-linux-x86_64.sh./bazel--installer-linux-x86_64.sh --user

bazel被装到了$HOME/bin目录下,添加到环境就OK了;之后输入bazel version看看版本是否安装成功;

安装Eigen3

之前提到了Tensorflow依赖Eigen矩阵运算库,在编译之前需要安装对应的版本;关于Eigen同样是一个坑,不对应的版本依然会让Tensorflow编译失败,这里提供一个最保险的方法,就是去tensorflow的tensorflow/tensorflow/workspace.bzl里下载;在 workspace.bzl中找到:

tf_http_archive(name = "eigen_archive",urls = ["https://mirror.bazel.build/bitbucket.org/eigen/eigen/get/2355b229ea4c.tar.gz","https://bitbucket.org/eigen/eigen/get/2355b229ea4c.tar.gz",],

下载其中任何一个链接都可,下载好之后解压,将Eigen添加到环境变量:

export CPLUS_INCLUDE_PATH="$HOME/tools/include/:$CPLUS_INCLUDE_PATH"export CPLUS_INCLUDE_PATH="$HOME/tools/include/eigen3/:$CPLUS_INCLUDE_PATH"

安装Protobuf

protocbuf是一种很强大的跨平台的数据标准,可以用于结构化数据序列化,用于通讯协议、数据存储等领域的语言无关、平台无关的序列化结构数据格式,在之后的gRPC中也会用到;
同样Protobuf的版本也会直接决定tensorflow是否编译成功,和安装Eigen同样的方法,去workspace.bzl中找protobuf下载对应的版本;下载好后进入protobuf目录输入以下命令安装,并添加到环境:

./autogen.sh./configure --prefix=$HOME/tools/binmakemake install

安装nsync

和Eigen同样的方式下载,添加环境路径即可:

export CPLUS_INCLUDE_PATH="$HOME/tools/include/nsync/public:$CPLUS_INCLUDE_PATH"

跳过这步安装会出现:fatal error : nsync_cv.h: No such file or dictionary的错误;

3abe94921dc43f8859470996a1850014.png

编译Tensorflow

经过以上的充足准备,终于可以编译Tensorflow啦,进入tensorflow下载目录,输入以下命令:

./configurebazel build //tensorflow:libtensorflow_cc.so

其中./configure之后没有选择CUDA支持,全部为no。经过4/5分钟之后,在bazel-bin/tensorflow下就会看到libtensorflow_cc.solibtensorflow_framework.so两个动态库;之后需要把这两个库复制到$HOME/tools/lib中,这样就可以连接来编译我们的模型了,之后的任务就是写Tensorflow的C++ API接口啦。

C++重写python API

在python API中主要有以下三步骤:
1 . 创建Session,读入计算图,恢复参数;
2 . 获取需要的输入,输出Tensor (graph.get_tensor_by_name);
3 . 给输入Tensor传值,run模型,得到输出结果;
C++ API也是相同的步骤;这里先给出python API的代码,Tensor的名字最好提前设定好,如果没有的话也可以直接Tensor.name查看:

def model_restore(self,model_file):sess = tf.Session()ckpt_file = tf.train.latest_checkpoint(self.model_file)saver = tf.train.import_meta_graph(ckpt_file+".meta")saver.restore(sess,ckpt_file)return sessdef recover(self,sess,paragraph):# 输入 : 无标点,大写字符串# 输出 : 带标点,大写字符串char_paragraph = get_char_id(paragraph)graph = tf.get_default_graph()#读入Tensorinputs = graph.get_tensor_by_name('word_id:0')logits_c = graph.get_tensor_by_name('Capt-Softmax/Reshape:0')logits_p = graph.get_tensor_by_name('Punc-Softmax/Reshape:0')tm_c = graph.get_tensor_by_name('loss/crf_capt/transitions:0')tm_p = graph.get_tensor_by_name('loss/crf_punc/transitions:0')feed_dict[inputs] = char_paragraph#运行模型logits_capt,logits_punc,transition_matrix_capt,transition_matrix_punc = sess.run([logits_c,logits_p,tm_c,tm_p],feed_dict=feed_dict)return self.sequence_viterbi_decode(label_pred_capt,label_pred_punc,word)

同样的结构用C++重写之后的代码如下,恢复模型部分:

void RecoverTool::modelLoader(const string& checkpoint_path){const string graph_path &#61; checkpoint_path&#43;".meta";// 读入模型的计算图 tensorflow::MetaGraphDef graph_def;tensorflow::Status status &#61; tensorflow::ReadBinaryProto(tensorflow::Env::Default(), graph_path, &graph_def);if(!status.ok())cout<<"Graph restore failed from "<Create(graph_def.graph_def());if(!status.ok())cout<<"Session created failed"<()() &#61; checkpoint_path;status &#61; session->Run({{graph_def.saver_def().filename_tensor_name(), checkpointPathTensor},},{},{graph_def.saver_def().restore_op_name()},nullptr);if(!status.ok())cout<<"Model restore failed from "<

API核心函数&#xff0c;C&#43;&#43;中Tensor返回的是Eigen::Tensor类型&#xff1b;

string RecoverTool::recover(const string& paragraph){// placeholder vectorvector> input &#61; utils.get_input_tensor_vector(paragraph);// 模型输出vector outputs;// 运行modeltensorflow::Status status &#61; session->Run(input, {"Capt-Softmax/Reshape:0","Punc-Softmax/Reshape:0","loss/crf_capt/transitions:0","loss/crf_punc/transitions:0"}, {}, &outputs);if(!status.ok())cout<<"Model run falied"<();auto trans_capt &#61; tran_capt.tensor();Eigen::Tensor logit_capt(logits_capt.dimension(1),logits_capt.dimension(2));Eigen::Tensor transitions_capt(trans_capt.dimension(0),trans_capt.dimension(1));for(int num_step(0);num_step captLabel &#61; viterbi_decode(logit_capt,transitions_capt);return paragraphDecode(captLabel,puncLabel,paragraph);}

如果模型输出不是最终结果&#xff0c;还需要进行行加工&#xff0c;这时就需要对Eigen的API有稍微的了解了&#xff0c;我用Eigen写了一个简单的CRF-Viterbi_decode代码&#xff0c;分享在这里供大家参考&#xff1a;

stack Utils::viterbi_decode(Eigen::Tensor score,Eigen::Tensor trans_matrix){//score: [seq_len,num_tags]//trans_matrix: [num_tags.num_tags]stack viterbi;Eigen::Tensor trellis &#61; score.constant(0.0f);//创建和score相同大小的全零数组Eigen::Tensor backpointers(score.dimension(0),score.dimension(1));backpointers.setZero();trellis.chip(0,0) &#61; score.chip(0,0);for(int i(1);i v &#61; trans_matrix.constant(0.0f);for(int j(0);j dims({0});Tensor maxCur &#61; v.maximum(dims);trellis.chip(i,0) &#61; maxCur&#43;score.chip(i,0);backpointers.chip(i,0) &#61; argmax(v,0);}viterbi.push(argmax_Dim1(trellis.chip(trellis.dimension(0)-1,0),0));for(int i(backpointers.dimension(0)-1);i>0;--i){viterbi.push(backpointers(i,viterbi.top()));}return viterbi;}

编译TF模型

通过以上步骤&#xff0c;我们就可以编译C&#43;&#43; API了&#xff0c;这里我们用make进行编译&#xff0c;链接上之前编译的libtensorflow_cc.so和libtensorflow_framework.so&#xff0c;命令如下&#xff0c;也可以写一个Makefile&#xff1b;

g&#43;&#43; -std&#61;c&#43;&#43;11 -g -Wall -D_DEBUG -Wshadow -Wno-sign-compare -w &#96;pkg-config --cflags --libs protobuf&#96;-I/home/xiaodl/tensorflow/bazel-genfiles -I/home/xiaodl/tensorflow/ -L/home/xiaodl/tools/lib-ltensorflow_framework -ltensorflow_cc -lprotobuf Utils.cc Recover.cc main.cc -o recover

之后在当前目录会生成一个可执行文件&#xff0c;这样就大功告成啦&#xff5e;输入一句没有标点的句子试一试&#xff0c;得到如下结果&#xff0c;试验成功&#xff1b;

We are living in the New York City now, and how is it going recent, Tom?

Tensorflow gRPC服务

有了C&#43;&#43; API就可以愉快的写gRPC服务了&#xff0c;那gPRC服务到底是什么呢&#xff1f;google家的RPC&#xff0c;传送官方文档&#xff1a;定义一个服务&#xff0c;指定其能够被远程调用的方法&#xff08;包含参数和返回类型&#xff09;。客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法。

05bbc0b8b8c9649e3bdc905bceb6887d.png
  • 安装gRPC
    直接github clone就好了&#xff1a;

$ git clone https://github.com/grpc/grpc.git

进入grpc目录&#xff0c;更新三方依赖源码&#xff0c;由于我们安装了protobuf&#xff0c;所以可以进入.gitmodules文件&#xff0c;删掉protobuf项&#xff0c;之后将已经安装的protobuf目录放入grpc/third_party就好&#xff1a;

$ git submodule update --init

更新完之后进入Makefile查找&#xff1a;ldconfig&#xff0c;把动态链接库指向自己的目录&#xff1b;

7f03593bc5e5f6bcf27f55ee81170970.png

把ldconfig替换成ldconfig -r $HOME/tools/bin&#xff0c;之后执行安装&#xff1a;

makemake install prefix&#61;$HOME/tools/

如果不是在grpc/third_party中安装的protobuf&#xff0c;在make过程中很有可能出如下错误&#xff1a;

In file included from src/compiler/php_generator.cc:23:0:./src/compiler/php_generator_helpers.h: In function ‘grpc::string grpc_php_generator::GetPHPServiceFilename(const FileDescriptor*, const ServiceDescriptor*, const string&)’:./src/compiler/php_generator_helpers.h:51:23: error: ‘const class google::protobuf::FileOptions’ has no member named ‘has_php_namespace’; did you mean ‘has_csharp_namespace’?if (file->options().has_php_namespace()) {^~~~~~~~~~~~~~~~~has_csharp_namespace

可以通过如下方式来解决这个错误&#xff1a;

make clean make HAS_SYSTEM_PROTOBUF&#61;false

最新版本的grpc需要protobuf的版本是3.5.0&#xff0c;安装成功之后可以去/grpc/examples/cpp/下测试grpc是否能正常工作&#xff1b;如果安装的protobuf版本不对会报错&#xff0c;更新protobuf到3.5.0即可&#xff0c;注意还要和Tensorflow要求的protobuf版本匹配才行&#xff1b;

Tensorflow C&#43;&#43; API 的gRPC服务

到这里总算可以开始写服务了&#xff0c;在实际运用中要求服务的client和server端都能够异步工作&#xff0c;也就是请求不产生阻塞&#xff1b;gPRC提供了很强的异步服务机制来实现客户和服务之间的异步无阻塞&#xff0c;这里将简单分析一下client和server端的异步机制&#xff1a;

1 . Client端&#xff1a;客户端会生成一个队列CompletionQueue&#xff0c;并用CallData类来记录RPC的状态和标签&#xff0c;每个request对应一个Calldata&#xff0c;在接收到请求的时候将其放入CompletionQueue中&#xff0c;并调用Finish函数向服务器端发送请求&#xff0c;寻求应答后立即返回处理新的待发送请求(无阻塞)&#xff1b;另开一个线程去等待处理CompletionQueue中的服务端应答&#xff1b;

2 . Server端&#xff1a;服务端有两个任务&#xff1a;接收request和处理request并返回Client&#xff1b;服务端用ServerData类来接收request&#xff0c;为了不让Service处理请求过程中有新的request到来产生阻塞&#xff0c;服务端将ServerData放入CompletionQueue队列后新建一个ServerData去接收新的请求(无阻塞)&#xff1b;另开一个线程处理队列中各种状态的ServiceData&#xff0c;并实现应答&#xff1b;

以上是我自己的理解&#xff0c;如果有错误请大家指出&#xff1b;根据这种理解实现Tensorflow的gRPC服务就不难了&#xff0c;服务端在创建Service之前先restore model&#xff0c;服务启动之后直接调用API即可&#xff0c;异步的实现和上面提到的流程一样&#xff0c;grpc有一个官方的案例非常不错/grpc/examples/cpp/helloworld/helloworld_async_client2.cc
测试Tensorflow gRPC的结果如下&#xff1a;

fa1e0b6dd03c010ca4a1b0a9ff6ce993.png

总结

这么折腾下来总算是完成了Mentor的任务了&#xff0c;感觉大部分的时间都花在安装三方库和配环境上&#xff0c;不过也是有收获的&#xff0c;这篇文章作为这一套工作的简单总结&#xff0c;文章中的错误或者过时的东西请各位看官大神们大声说出来呀&#xff5e;&#xff5e;时间不早了&#xff0c;明儿还要实习&#xff0c;晚安&#xff5e;



推荐阅读
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 通过Anaconda安装tensorflow,并安装运行spyder编译器的完整教程
    本文提供了一个完整的教程,介绍了如何通过Anaconda安装tensorflow,并安装运行spyder编译器。文章详细介绍了安装Anaconda、创建tensorflow环境、安装GPU版本tensorflow、安装和运行Spyder编译器以及安装OpenCV等步骤。该教程适用于Windows 8操作系统,并提供了相关的网址供参考。通过本教程,读者可以轻松地安装和配置tensorflow环境,以及运行spyder编译器进行开发。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Python已成为全球最受欢迎的编程语言之一,然而Python程序的安全运行存在一定的风险。本文介绍了Python程序安全运行需要满足的三个条件,即系统路径上的每个条目都处于安全的位置、"主脚本"所在的目录始终位于系统路径中、若python命令使用-c和-m选项,调用程序的目录也必须是安全的。同时,文章还提出了一些预防措施,如避免将下载文件夹作为当前工作目录、使用pip所在路径而不是直接使用python命令等。对于初学Python的读者来说,这些内容将有所帮助。 ... [详细]
  • 本文介绍了在Android Studio中使用命令行build gradle的方法,并解决了一些常见问题,包括手动配置gradle环境变量和解决External Native Build Issues的方法。同时提供了相关参考文章链接。 ... [详细]
  • 打开文件管理器_【教程】模组管理器3.1食用指南
    文编:byakko最近有部分小伙伴反应还不会使用unity模组管理器,现在我就给大家讲一下unity模组管理器——从下载到使用。完整视频版以下是无WiF ... [详细]
  • Linuxchmod目录权限命令图文详解在Linux文件系统模型中,每个文件都有一组9个权限位用来控制谁能够读写和执行该文件的内容。对于目录来说,执行位的作用是控制能否进入或者通过 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Linux磁盘的分区、格式化的观察和操作步骤
    本文介绍了如何观察Linux磁盘的分区状态,使用lsblk命令列出系统上的所有磁盘列表,并解释了列表中各个字段的含义。同时,还介绍了使用parted命令列出磁盘的分区表类型和分区信息的方法。在进行磁盘分区操作时,根据分区表类型选择使用fdisk或gdisk命令,并提供了具体的分区步骤。通过本文,读者可以了解到Linux磁盘分区和格式化的基本知识和操作步骤。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了StartingzookeeperFAILEDTOSTART相关的知识,希望对你有一定的参考价值。下载路径:https://ar ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
author-avatar
人帅刀快爱美女_915
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有