用keras训练模型并用Tensorflow的C++API调用模型

本文转载自:https://blog.csdn.net/qq_25109263/article/details/81285952

             将Keras训练的模型部署到C++平台上的可行方案

一. 背景:

        本人这几天由于公司要求将Deep learning的项目迁移到C++的平台,以便作为一个子模块嵌入到整个公司的C++Project当中。在算法研究阶段,想必很多人会喜欢keras,因为keras的代码简介性,十分有利于Deeplearning的编程,十分容易上手,tensorflow虽然目前google创造了广大的生态,支持各种语言的接口,但是本人至今还是不习惯用tensorflow这种先定义图,再执行运算的方式,不知道大家是否有同感。我之前的研究内容在Python端已经通过keras训练出来了可用的model,但是keras并没有提供C++的API,所以似乎我得用另外的框架,如caffe2,tensorflow,caffe,pytorch等去重新train我的model,再用他们的C++API去在C++平台上运行。通过尝试caffe2,发现这玩意儿第一来说,环境配置过程虽然简单,但是很容易出各种error,,配置就是一个坑。第二,其官方的API写的也太水了吧,跟没有一样,只能按照上面的几个demo去跑,学起来十分费劲。caffe2和tensorflow在python端的方式是差不多的,都是先定义图,再运行,所以从训练角度,这两者都让我很不舒服。之前用caffe还算熟练,但是若要用caffe,则需要重新training,所以我心心念念地想要如果keras训练好的Model能直接来用,该多好,所以倒腾几天,终于实现了这个目标,下面话不多说,给大家讲解整个过程

Plus,相关的代码在我百度云上可以找到,链接路径如下:

链接: https://pan.baidu.com/s/1FHRTC1NZRSyaMuVBMGUihA 密码: h7x8

二. Pipline:

        整个工作流:keras训练->"my_model.h5"->转换到pb类型->"my_model.pb"->C++端用tensorflowAPI调用并运行模型

三. 必备环境配置:

本机配置:GTX1080,Ubuntu16.04

(1)一个配有tensorflow和keras的python环境,建议用anaconda去创建

  • conda create -n keras python=3.6
  • source activate keras
  • conda install tensorflow-gpu==1.8
  • pip install keras==2.1

(2) CUDA9.0, cudnn7.0.5(安装CUDA和cudnn的方法请自行查找,不在本文范围内)

(3)OpenCV3.4(安装OpenCV的方法请自行查找,不在本文范围内)

(5)编译tensorflow C++接口,这在接下来会介绍

四.编译安装tensorflow的C++接口,本文编译的tensorflow C++是1.8版的

1.配置C++版tensorflow使用时的第三方依赖

(1)Protobuf!!!!!这玩意儿是重中之重,它的版本与tensorflow的版本密切相关,它的版本错了就无法work,我用的3.5.0

先从以下网址下载protobuf-cpp-3.5.0.tar.gz
https://github.com/google/protobuf/releases
再解压出来,获得一个protobuf-3.5.0的文件夹
cd prtobuf-3.5.0
./configure
sudo make -j8
make check -j8
sudo make install
sudo ldconfig
以上步骤可以完成Protubuf的源码的编译和安装
如果遇到什么问题,建议去看Protobuf的官方的编译安装指南:
https://github.com/google/protobuf/blob/master/src/README.md

(2)Eigen,这是一个C++端的矩阵运算库,这个库只要下载压缩包,解压到某个自己知道的路径下即可

先下载eigen的压缩包
wget http://bitbucket.org/eigen/eigen/get/3.3.4.tar.bz2
下载之后解压,重新命名为eigen3,放到某个路径下
我存放的路径是,~/tools/tf-C/

2.编译安装Tensorflow

(1)下载安装编译工具bazel

先下载Bazel的安装包
https://github.com/bazelbuild/bazel/releases,我下载的是bazel-0.10.1-installer-linux-x86_64.sh
然后执行安装
./bazel-0.10.1-installer-linux-x86_64.sh

(2)编译安装Tensorflow,我的源码路径是~/tensorflow

# 先下载tensorflow源码
git clone –recursive https://github.com/tensorflow/tensorflow

# 进入tensorflow文件夹
cd tensorflow

# 切换到1.8版本:
git checkout r1.8

# 执行configure
sudo ./configure
这一步需要你指定python路径,需要有各种y/N的选择
建议如下:

  • python路径用之前创建的keras的路径:/home/xxx/anaconda2/envs/keras/bin/python
  • 其他的第一个y/N选择y,后面的都是N
  • cuda要选择y,然后会自动搜索cudnn版本
  • nccl选择默认的1.3,
  • 后面的不是选择N就是默认

详见Tensorflow官网的提示:
https://www.tensorflow.org/install/install_sources

# 使用bazel去编译,--config=monolithic是为了解决与OpenCV的冲突问题
sudo bazel build --config=opt --config=cuda --config=monolithic //tensorflow:libtensorflow_cc.so

....漫长的等待编译,大约20分钟
# 最后显示类似如下的信息,说明编译成功了:
....
Target //tensorflow:libtensorflow_cc.so up-to-date:
  bazel-bin/tensorflow/libtensorflow_cc.so
INFO: Elapsed time: 1192.883s, Critical Path: 174.02s
INFO: 654 processes: 654 local.
INFO: Build completed successfully, 656 total actions

# 再把必要.h头文件以及编译出来.so的动态链接库文件复制到指定的一些路径下:
sudo mkdir /usr/local/include/tf
sudo cp -r bazel-genfiles/ /usr/local/include/tf/
sudo cp -r tensorflow /usr/local/include/tf/
sudo cp -r third_party /usr/local/include/tf/
sudo cp bazel-bin/tensorflow/libtensorflow_cc.so /usr/local/lib/
sudo cp bazel-bin/tensorflow/libtensorflow_framework.so /usr/local/lib

OK到此为止,tensorflow C++的接口已经搞定!

 五.整个pipline的演示

1.python端用keras训练一个手写数字识别的mnist的demo,代码如下,训练完会产生一个my_model_ep20.h5的模型文件

from tensorflow.examples.tutorials.mnist import *

from keras.models import *

from keras.layers import *

import numpy as np


# os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'


# 加载数据集

mnist=input_data.read_data_sets("MNIST_data/",one_hot=True)

train_X=mnist.train.images

train_Y=mnist.train.labels

test_X=mnist.test.images

test_Y=mnist.test.labels


train_X=train_X.reshape((55000,28,28,1))

test_X=test_X.reshape((test_X.shape[0],28,28,1))


print("type of train_X:",type(train_X))

print("size of train_X:",np.shape(train_X))

print("train_X:",train_X)


print("type of train_Y:",type(train_Y))

print("size of train_Y:",np.shape(train_Y))

print("train_Y:",train_Y)


print("num of test:",test_X.shape[0])



# 配置模型结构

model=Sequential()


model.add(Conv2D(32,(3,3),activation='relu',input_shape=(28,28,1),padding="same"))

model.add(MaxPool2D(pool_size=(2,2)))

model.add(Dropout(0.5))



model.add(Conv2D(64, (3, 3), activation='relu',padding="same"))

model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Dropout(0.5))


model.add(Conv2D(128, (3, 3), activation='relu',padding="same"))

model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Dropout(0.5))


model.add(Flatten())

model.add(Dense(625,activation="relu"))

model.add(Dropout(0.5))


model.add(Dense(10,activation='softmax'))


model.compile(loss='categorical_crossentropy',optimizer='adadelta',metrics=['accuracy'])


# 训练模型

epochs=20

model.fit(train_X, train_Y, batch_size=32, epochs=epochs)


# 用测试集去评估模型的准确度

accuracy=model.evaluate(test_X,test_Y,batch_size=20)

print('\nTest accuracy:',accuracy[1])


save_model(model,'my_model_ep{}.h5'.format(epochs))

2.将my_model_ep20.h5的模型转化为my_model_ep20.pb的模型,用的脚本为h5_to_pb.py


from keras.models import load_model

import tensorflow as tf

from keras import backend as K

from tensorflow.python.framework import graph_io


def freeze_session(session, keep_var_names=None, output_names=None, clear_devices=True):

from tensorflow.python.framework.graph_util import convert_variables_to_constants

graph = session.graph

with graph.as_default():

freeze_var_names = list(set(v.op.name for v in tf.global_variables()).difference(keep_var_names or []))

output_names = output_names or []

output_names += [v.op.name for v in tf.global_variables()]

input_graph_def = graph.as_graph_def()

if clear_devices:

for node in input_graph_def.node:

node.device = ""

frozen_graph = convert_variables_to_constants(session, input_graph_def,

output_names, freeze_var_names)

return frozen_graph



"""----------------------------------配置路径-----------------------------------"""

epochs=20

h5_model_path='./my_model_ep{}.h5'.format(epochs)

output_path='.'

pb_model_name='my_model_ep{}.pb'.format(epochs)



"""----------------------------------导入keras模型------------------------------"""

K.set_learning_phase(0)

net_model = load_model(h5_model_path)


print('input is :', net_model.input.name)

print ('output is:', net_model.output.name)


"""----------------------------------保存为.pb格式------------------------------"""

sess = K.get_session()

frozen_graph = freeze_session(K.get_session(), output_names=[net_model.output.op.name])

graph_io.write_graph(frozen_graph, output_path, pb_model_name, as_text=False)

3.测试模型类型转换后,是否测试效果一致,如果不一致,那就说明转换失败

测试my_model_ep20.h5模型:写了一个load_h5_test.py


import os

import cv2

import numpy as np

from keras.models import load_model


os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'


"""---------载入已经训练好的模型---------"""

new_model = load_model('my_model_ep20.h5')


"""---------用opencv载入一张待测图片-----"""

# 载入图片

src = cv2.imread('Pictures/6.png')

cv2.imshow("test picture", src)


# 将图片转化为28*28的灰度图

src = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)

dst = cv2.resize(src, (28, 28))

dst=dst.astype(np.float32)


# 将灰度图转化为1*784的能够输入的网络的数组

picture=1-dst/255

picture=np.reshape(picture,(1,28,28,1))


# 用模型进行预测

y = new_model.predict(picture)

print("softmax:")

for i,prob in enumerate(y[0]):

print("class{},Prob:{}".format(i,prob))

result = np.argmax(y)

print("你写的数字是:", result)

print("对应的概率是:",np.max(y[0]))

cv2.waitKey(20170731)
  1. 效果如下:

 测试my_model_ep20.pb模型,写了一个load_pb_test.py:

 
  1. import tensorflow as tf

  2. import numpy as np

  3. import cv2

  4.  
  5. """-----------------------------------------------定义识别函数-----------------------------------------"""

  6. def recognize(jpg_path, pb_file_path):

  7. with tf.Graph().as_default():

  8. output_graph_def = tf.GraphDef()

  9.  
  10. # 打开.pb模型

  11. with open(pb_file_path, "rb") as f:

  12. output_graph_def.ParseFromString(f.read())

  13. tensors = tf.import_graph_def(output_graph_def, name="")

  14. print("tensors:",tensors)

  15.  
  16. # 在一个session中去run一个前向

  17. with tf.Session() as sess:

  18. init = tf.global_variables_initializer()

  19. sess.run(init)

  20.  
  21. op = sess.graph.get_operations()

  22.  
  23. # 打印图中有的操作

  24. for i,m in enumerate(op):

  25. print('op{}:'.format(i),m.values())

  26.  
  27. input_x = sess.graph.get_tensor_by_name("conv2d_1_input:0") # 具体名称看上一段代码的input.name

  28. print("input_X:",input_x)

  29.  
  30. out_softmax = sess.graph.get_tensor_by_name("dense_2/Softmax:0") # 具体名称看上一段代码的output.name

  31. print("Output:",out_softmax)

  32.  
  33. # 读入图片

  34. img = cv2.imread(jpg_path, 0)

  35. img=cv2.resize(img,(28,28))

  36. img=img.astype(np.float32)

  37. img=1-img/255;

  38. # img=np.reshape(img,(1,28,28,1))

  39. print("img data type:",img.dtype)

  40.  
  41. # 显示图片内容

  42. for row in range(28):

  43. for col in range(28):

  44. if col!=27:

  45. print(img[row][col],' ',end='')

  46. else:

  47. print(img[row][col])

  48.  
  49. img_out_softmax = sess.run(out_softmax,

  50. feed_dict={input_x: np.reshape(img,(1,28,28,1))})

  51.  
  52. print("img_out_softmax:", img_out_softmax)

  53. for i,prob in enumerate(img_out_softmax[0]):

  54. print('class {} prob:{}'.format(i,prob))

  55. prediction_labels = np.argmax(img_out_softmax, axis=1)

  56. print("Final class if:",prediction_labels)

  57. print("prob of label:",img_out_softmax[0,prediction_labels])

  58.  
  59.  
  60. pb_path = './my_model_ep20.pb'

  61. img = 'Pictures/6.png'

  62. recognize(img, pb_path)

效果如下:与上面的h5模型的结果基本一致,说明转换没问题

4.在C++中调用my_model_ep20.pb模型,写了一个hello.cpp,代码如下:注意,代码中input_tensor_name和output_tensor_name很关键,这两个是模型的输入和输出接口,这两个名字怎么确定,就要看你模型定义的时候,对应的tensor的名字了,这个名字可以在load_pb_test.py运行时,看它打印的信息,你就会明白该用什么名字了

 
  1. #include <fstream>

  2. #include <utility>

  3. #include <Eigen/Core>

  4. #include <Eigen/Dense>

  5. #include <iostream>

  6.  
  7. #include "tensorflow/cc/ops/const_op.h"

  8. #include "tensorflow/cc/ops/image_ops.h"

  9. #include "tensorflow/cc/ops/standard_ops.h"

  10.  
  11. #include "tensorflow/core/framework/graph.pb.h"

  12. #include "tensorflow/core/framework/tensor.h"

  13.  
  14. #include "tensorflow/core/graph/default_device.h"

  15. #include "tensorflow/core/graph/graph_def_builder.h"

  16.  
  17. #include "tensorflow/core/lib/core/errors.h"

  18. #include "tensorflow/core/lib/core/stringpiece.h"

  19. #include "tensorflow/core/lib/core/threadpool.h"

  20. #include "tensorflow/core/lib/io/path.h"

  21. #include "tensorflow/core/lib/strings/stringprintf.h"

  22.  
  23. #include "tensorflow/core/public/session.h"

  24. #include "tensorflow/core/util/command_line_flags.h"

  25.  
  26. #include "tensorflow/core/platform/env.h"

  27. #include "tensorflow/core/platform/init_main.h"

  28. #include "tensorflow/core/platform/logging.h"

  29. #include "tensorflow/core/platform/types.h"

  30.  
  31. #include "opencv2/opencv.hpp"

  32.  
  33. using namespace tensorflow::ops;

  34. using namespace tensorflow;

  35. using namespace std;

  36. using namespace cv;

  37. using tensorflow::Flag;

  38. using tensorflow::Tensor;

  39. using tensorflow::Status;

  40. using tensorflow::string;

  41. using tensorflow::int32 ;

  42.  
  43. // 定义一个函数讲OpenCV的Mat数据转化为tensor,python里面只要对cv2.read读进来的矩阵进行np.reshape之后,

  44. // 数据类型就成了一个tensor,即tensor与矩阵一样,然后就可以输入到网络的入口了,但是C++版本,我们网络开放的入口

  45. // 也需要将输入图片转化成一个tensor,所以如果用OpenCV读取图片的话,就是一个Mat,然后就要考虑怎么将Mat转化为

  46. // Tensor了

  47. void CVMat_to_Tensor(Mat img,Tensor* output_tensor,int input_rows,int input_cols)

  48. {

  49. //imshow("input image",img);

  50. //图像进行resize处理

  51. resize(img,img,cv::Size(input_cols,input_rows));

  52. //imshow("resized image",img);

  53.  
  54. //归一化

  55. img.convertTo(img,CV_32FC1);

  56. img=img/255;

  57.  
  58. //创建一个指向tensor的内容的指针

  59. float *p = output_tensor->flat<float>().data();

  60.  
  61. //创建一个Mat,与tensor的指针绑定,改变这个Mat的值,就相当于改变tensor的值

  62. cv::Mat tempMat(input_rows, input_cols, CV_32FC1, p);

  63. img.convertTo(tempMat,CV_32FC1);

  64.  
  65. // waitKey(0);

  66.  
  67. }

  68.  
  69. int main(int argc, char** argv )

  70. {

  71. /*--------------------------------配置关键信息------------------------------*/

  72. string model_path="../my_model_ep20.pb";

  73. string image_path="../test_images/6.png";

  74. int input_height =28;

  75. int input_width=28;

  76. string input_tensor_name="conv2d_1_input";

  77. string output_tensor_name="dense_2/Softmax";

  78.  
  79. /*--------------------------------创建session------------------------------*/

  80. Session* session;

  81. Status status = NewSession(SessionOptions(), &session);//创建新会话Session

  82.  
  83. /*--------------------------------从pb文件中读取模型--------------------------------*/

  84. GraphDef graphdef; //Graph Definition for current model

  85.  
  86. Status status_load = ReadBinaryProto(Env::Default(), model_path, &graphdef); //从pb文件中读取图模型;

  87. if (!status_load.ok()) {

  88. cout << "ERROR: Loading model failed..." << model_path << std::endl;

  89. cout << status_load.ToString() << "\n";

  90. return -1;

  91. }

  92. Status status_create = session->Create(graphdef); //将模型导入会话Session中;

  93. if (!status_create.ok()) {

  94. cout << "ERROR: Creating graph in session failed..." << status_create.ToString() << std::endl;

  95. return -1;

  96. }

  97. cout << "<----Successfully created session and load graph.------->"<< endl;

  98.  
  99. /*---------------------------------载入测试图片-------------------------------------*/

  100. cout<<endl<<"<------------loading test_image-------------->"<<endl;

  101. Mat img=imread(image_path,0);

  102. if(img.empty())

  103. {

  104. cout<<"can't open the image!!!!!!!"<<endl;

  105. return -1;

  106. }

  107.  
  108. //创建一个tensor作为输入网络的接口

  109. Tensor resized_tensor(DT_FLOAT, TensorShape({1,input_height,input_width,1}));

  110.  
  111. //将Opencv的Mat格式的图片存入tensor

  112. CVMat_to_Tensor(img,&resized_tensor,input_height,input_width);

  113.  
  114. cout << resized_tensor.DebugString()<<endl;

  115.  
  116. /*-----------------------------------用网络进行测试-----------------------------------------*/

  117. cout<<endl<<"<-------------Running the model with test_image--------------->"<<endl;

  118. //前向运行,输出结果一定是一个tensor的vector

  119. vector<tensorflow::Tensor> outputs;

  120. string output_node = output_tensor_name;

  121. Status status_run = session->Run({{input_tensor_name, resized_tensor}}, {output_node}, {}, &outputs);

  122.  
  123. if (!status_run.ok()) {

  124. cout << "ERROR: RUN failed..." << std::endl;

  125. cout << status_run.ToString() << "\n";

  126. return -1;

  127. }

  128. //把输出值给提取出来

  129. cout << "Output tensor size:" << outputs.size() << std::endl;

  130. for (std::size_t i = 0; i < outputs.size(); i++) {

  131. cout << outputs[i].DebugString()<<endl;

  132. }

  133.  
  134. Tensor t = outputs[0]; // Fetch the first tensor

  135. auto tmap = t.tensor<float, 2>(); // Tensor Shape: [batch_size, target_class_num]

  136. int output_dim = t.shape().dim_size(1); // Get the target_class_num from 1st dimension

  137.  
  138. // Argmax: Get Final Prediction Label and Probability

  139. int output_class_id = -1;

  140. double output_prob = 0.0;

  141. for (int j = 0; j < output_dim; j++)

  142. {

  143. cout << "Class " << j << " prob:" << tmap(0, j) << "," << std::endl;

  144. if (tmap(0, j) >= output_prob) {

  145. output_class_id = j;

  146. output_prob = tmap(0, j);

  147. }

  148. }

  149.  
  150. // 输出结果

  151. cout << "Final class id: " << output_class_id << std::endl;

  152. cout << "Final class prob: " << output_prob << std::endl;

  153.  
  154. return 0;

  155. }

  156.  
  157.  

然后要写CmakeList.txt,如下所示:

 
  1. cmake_minimum_required(VERSION 3.10)

  2. project(demo)

  3. set(CMAKE_CXX_STANDARD 11)

  4.  
  5. # 配置.so文件存在的路径,默认会去/usr/local/lib下找,

  6. # 放在其他地方的.so文件路径就要用下面的代码添加

  7. link_directories(/home/czj/anaconda2/envs/tf/lib)

  8.  
  9. # 配置.h头文件路径,默认会去/usr/local/include下找,

  10. # 放在其他地方的.h文件就要用以下代码添加进来

  11. include_directories(

  12. /home/czj/tensorflow

  13. /home/czj/tensorflow/bazel-genfiles

  14. /home/czj/tensorflow/bazel-bin/tensorflow

  15. /home/czj/tools/tf-C/eigen3

  16. )

  17.  
  18. # 配置生成的可执行文件名为hello,要用到的源文件为hello.cpp

  19. add_executable(hello hello.cpp)

  20.  
  21. # 以下使能OpenCV的路径查找

  22. find_package(OpenCV REQUIRED)

  23.  
  24. # 以下是将可执行文件与一些.so文件建立动态链接关系,

  25. # 用到的有libtensorflow_cc.so,libtensorflow_framework.so,以及opencv相关的so

  26. target_link_libraries(hello tensorflow_cc tensorflow_framework ${OpenCV_LIBS})

执行以下步骤通过cmake去编译C++文件,生产可执行文件,然后执行:

# 先创建编译生成文件夹
mkdir build
cd build
# 在build中执行cmkae,生成makefile
cmake ..
# 执行make,产生可执行文件
make
# 执行可执行文件
./hello

C++端的可执行文件运行结果如下:与上面python代码的运行结果也基本一致,说明整个pipline是可以work的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值