C++部署TensorFlow模型

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MOU_IT/article/details/88070285

    安装好C++版的TensorFlow之后,我们就可以用C++来部署python训练好的TensorFlow模型了。安装C++版的TensorFlow的教程可以参考这里。部署TensorFlow模型主要分为两步,第一步是用python训练模型,然后保存模型为.pb格式的二进制文件;第二步则是在C++中加载python保存的模型并进行预测。

1、python训练模型并保存

2、使用C++加载python训练好的模型并进行预测


1、python训练模型并保存

    这里我们以mnist数据集为例,训练一个三层的多层感知机对手写数字图片进行识别,具体的训练代码如下:

#coding=utf-8
import pickle
import numpy as np
import tensorflow as tf
import time 
import os
from tensorflow.python.framework.graph_util import convert_variables_to_constants

# 定义一个mnist数据集的类
class mnistReader():  
    def __init__(self, mnistPath, onehot = True):  
        self.mnistPath = mnistPath
        self.onehot = onehot  
        self.batch_index = 0
        print ('read:',self.mnistPath)
        fo = open(self.mnistPath, 'rb')
        self.train_set,self.valid_set,self.test_set = pickle.load(fo, encoding='bytes')
        fo.close()        
        self.data_label_train = list(zip(self.train_set[0], self.train_set[1]))
        np.random.shuffle(self.data_label_train)       
               

    # 获取下一个训练集的batch
    def next_train_batch(self, batch_size = 100):
        if self.batch_index < len(self.data_label_train)/batch_size:  
            print ("batch_index:",self.batch_index )
            datum = self.data_label_train[self.batch_index*batch_size:(self.batch_index+1)*batch_size]  
            self.batch_index+=1  
            return self._decode(datum, self.onehot)  
        else:  
            self.batch_index=0  
            np.random.shuffle(self.data_label_train)  
            datum=self.data_label_train[self.batch_index*batch_size:(self.batch_index+1)*batch_size]  
            self.batch_index+=1  
            return self._decode(datum,self.onehot)          
    
    # 获取测试集的数据
    def test_data(self):
        tdata, tlabel = self.test_set
        data_label_test=list(zip(tdata,tlabel))
        return self._decode(data_label_test,self.onehot)
    
    
    # 把一个batch的训练数据转换为可以放入模型训练的数据 
    def _decode(self, datum, onehot):  
        rdata=list()                 
        rlabel=list()  
        if onehot:  
            for d,l in datum:   
                img = np.reshape(d, (28,28))
                img = np.expand_dims(img, 2)           
                rdata.append(img) 
                hot = np.zeros(10)    
                hot[int(l)] = 1                   
                rlabel.append(hot)  
        else:  
            for d,l in datum:  
                img = np.reshape(d, (28,28))                
                img = np.expand_dims(img,2)            
                rdata.append(img)
                rlabel.append(int(l))  
        return rdata,rlabel  


#多层感知机模型(只有一个隐藏层)
def multi_perceptron():      
    batch_size = 100                              # batch大小
    height = 28                                   # 图片高度
    width = 28                                    # 图片宽度
    channel = 1                                   # 图片通道数
    in_units = 784                                # 多层感知机的输入
    h1_units=300                                  # MLP隐藏层的输出节点数
    mnist_path = "E:/testdata/mnist.pkl"          # mnist数据集路径
    save_path = "./output"                        #保存模型的路径

    images = tf.placeholder(tf.float32, shape = [None, height, width, channel],name = "images")
    labels =  tf.placeholder(tf.float32,[None, 10], name = "labels")

    w1=tf.Variable(tf.truncated_normal([in_units,h1_units],stddev=0.1))
    b1=tf.Variable(tf.zeros([h1_units]))     
    w2=tf.Variable(tf.truncated_normal([h1_units,10],stddev=0.1))   
    b2=tf.Variable(tf.zeros([10]))           
    
    # 网络各层的计算
    inputs = tf.reshape(images, [-1, in_units])
    hidden1 = tf.nn.relu(tf.matmul(inputs, w1) + b1)         
    hidden1_drop = tf.nn.dropout(hidden1, 0.75)         
    logits = tf.nn.softmax(tf.matmul(hidden1_drop, w2) + b2, name = "logits") 
    
    # 定义交叉熵为损失函数和优化器
    cross_entropy=tf.reduce_mean(-tf.reduce_sum(labels*tf.log(logits),reduction_indices=[1])) 
    train_step=tf.train.AdagradOptimizer(0.3).minimize(cross_entropy)  
    
    # 开始训练
    print ("begin training ...\n")
    sess = tf.InteractiveSession()              # 创建会话  
    tf.global_variables_initializer().run()     # 初始化全局变量

    mnist = mnistReader(mnistPath = mnist_path)
    
    for i in range(1000):
        image_batch, label_batch = mnist.next_train_batch()        
        train_step.run({images:image_batch, labels:label_batch})        

    # 计算测试集上的准确率
    correction_prediction = tf.equal(tf.argmax(labels, 1), tf.argmax(logits, 1))     
    accuracy = tf.reduce_mean(tf.cast(correction_prediction, tf.float32))
    test_data,test_label = mnist.test_data()
    print (accuracy.eval({images : test_data, labels :test_label}))
    
    # 保存模型的参数
    saver = tf.train.Saver() 
    saver.save(sess, os.path.join(save_path, "model.ckpt"))  
    graph = convert_variables_to_constants(sess, sess.graph_def, ["logits"])
    tf.train.write_graph(graph, save_path, 'model.pb',as_text=False)
    print ("save model sucessful")

if __name__ == '__main__':
    multi_perceptron()       # 多层感知机得到的准确率大概为0.98左右

    模型训练好之后,保存模型文件为./output/model.pb

2、使用C++加载python训练好的模型并进行预测

   (1) 文件结构如下,需要将python保存的模型复制到build文件夹下:

├── src
| └── hello.cpp
| 
├── CMakeLists.txt
| 
├── build
| └──model.pb
| |
| └──digit.jpg

   (2)hello.cpp的内容为(C++程序主要参考官网的实现例子):

#include <fstream>
#include <utility>
#include <vector>
#include <Eigen/Core>
#include <Eigen/Dense>

#include "tensorflow/cc/ops/const_op.h"
#include "tensorflow/cc/ops/image_ops.h"
#include "tensorflow/cc/ops/standard_ops.h"
#include "tensorflow/core/framework/graph.pb.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/graph/default_device.h"
#include "tensorflow/core/graph/graph_def_builder.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/stringpiece.h"
#include "tensorflow/core/lib/core/threadpool.h"
#include "tensorflow/core/lib/io/path.h"
#include "tensorflow/core/lib/strings/stringprintf.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/init_main.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/types.h"
#include "tensorflow/core/public/session.h"
#include "tensorflow/core/util/command_line_flags.h"

using namespace std;
using namespace tensorflow;
using namespace tensorflow::ops;
using tensorflow::Flag;
using tensorflow::Tensor;
using tensorflow::Status;
using tensorflow::string;
using tensorflow::int32;


//读取一个文件放入一个tensor之中
static Status ReadEntireFile(tensorflow::Env* env, const string& filename,Tensor* output) {
	tensorflow::uint64 file_size = 0;
	TF_RETURN_IF_ERROR(env->GetFileSize(filename, &file_size));          //获取文件的大小

	string contents;
	contents.resize(file_size);                                          //文件内容

	std::unique_ptr<tensorflow::RandomAccessFile> file;	                 //创建一个指向文件的智能指针
	TF_RETURN_IF_ERROR(env->NewRandomAccessFile(filename, &file));       //将这个指针指向文件
	
	tensorflow::StringPiece data;                                        //声明stringpiece类型的data存放文件内容
	TF_RETURN_IF_ERROR(file->Read(0, file_size, &data, &(contents)[0])); //将文件读入data之中

	if (data.size() != file_size) {
            return tensorflow::errors::DataLoss("Truncated read of '", filename,"' 
                                                expected ", file_size, " got ", data.size());
	}

  	output->scalar<string>()() = string(data);                           //将data的值赋给output的tensor
	return Status::OK();
}


//从图像读取数据并放入tensor之中
Status ReadTensorFromImageFile(const string& file_name, const int input_height,
                               const int input_width, const float input_mean,
                               const float input_std,
                               std::vector<Tensor>* out_tensors) {
	auto root = tensorflow::Scope::NewRootScope();
	using namespace ::tensorflow::ops;               // NOLINT(build/namespaces)

	string input_name = "file_reader";
	string output_name = "normalized";

	//把文件读入一个叫input的tensor中
	Tensor input(tensorflow::DT_STRING, tensorflow::TensorShape());     //声明一个string类型的tensor,名为input
	TF_RETURN_IF_ERROR(ReadEntireFile(tensorflow::Env::Default(), file_name, &input)); //把文件读入这个tensor中

    //用一个placeholder来读取input的数据
	auto file_reader = Placeholder(root.WithOpName("input"), tensorflow::DataType::DT_STRING);
	std::vector<std::pair<string, tensorflow::Tensor>> inputs = {{"input", input},};

	//确定是什么类型的文件并且进行解码
	const int wanted_channels = 1;
	tensorflow::Output image_reader;
	if (tensorflow::str_util::EndsWith(file_name, ".png")) {
		image_reader = DecodePng(root.WithOpName("png_reader"), file_reader,DecodePng::Channels(wanted_channels));
  	} 
	else if (tensorflow::str_util::EndsWith(file_name, ".gif")) {
        // gif decoder returns 4-D tensor, remove the first dim
    	image_reader = Squeeze(root.WithOpName("squeeze_first_dim"),DecodeGif(root.WithOpName("gif_reader"), file_reader));
	}
	else if (tensorflow::str_util::EndsWith(file_name, ".bmp")) {
    	image_reader = DecodeBmp(root.WithOpName("bmp_reader"), file_reader);
	} 
	else {
        // Assume if it's neither a PNG nor a GIF then it must be a JPEG.
    	image_reader = DecodeJpeg(root.WithOpName("jpeg_reader"), file_reader,DecodeJpeg::Channels(wanted_channels));
	}

    //现在把图像数据转为float,这样我们才对它进数据操作
	auto float_caster = Cast(root.WithOpName("float_caster"), image_reader, tensorflow::DT_FLOAT);
    // The convention for image ops in TensorFlow is that all images are expected
    // to be in batches, so that they're four-dimensional arrays with indices of
    // [batch, height, width, channel]. Because we only have a single image, we
    // have to add a batch dimension of 1 to the start with ExpandDims().
	auto dims_expander = ExpandDims(root.WithOpName("expand"), float_caster, 0);
    // Bilinearly resize the image to fit the required dimensions.
    // auto resized = ResizeBilinear(                               //图像进行双线性插值到固定尺寸
    //root, dims_expander,
    //Const(root.WithOpName("size"), {input_height, input_width}));
    // Subtract the mean and divide by the scale.                  //减去图像均值
    //Div(root.WithOpName(output_name), Sub(root, resized, {input_mean}),
    //{input_std});
//	float input_max = 255;
//	Div(root.WithOpName("div"),dims_expander,input_max);           //图像除以255,进行归一化处理
    // This runs the GraphDef network definition that we've just constructed, and
    // returns the results in the output tensor.
	tensorflow::GraphDef graph;
	TF_RETURN_IF_ERROR(root.ToGraphDef(&graph));
	std::unique_ptr<tensorflow::Session> session(tensorflow::NewSession(tensorflow::SessionOptions()));
	TF_RETURN_IF_ERROR(session->Create(graph));
	TF_RETURN_IF_ERROR(session->Run({inputs}, {"expand"}, {}, out_tensors));
  	return Status::OK();
}


//主函数
int main(int argc, char** argv ){

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

	string model_path="model.pb";                                     //保存的模型路径
	GraphDef graphdef;                                                //当前模型的图定义
	Status status_load = ReadBinaryProto(Env::Default(), model_path, &graphdef); //从pb文件中读取图模型;
	if (!status_load.ok()) {
    	std::cout << "ERROR: Loading model failed..." << model_path << std::endl;
    	std::cout << status_load.ToString() << "\n";
      	return -1;
  	}

  	Status status_create = session->Create(graphdef);               //将图模型导入会话Session中;
	if (!status_create.ok()) {
    	std::cout << "ERROR: Creating graph in session failed..." << status_create.ToString() << std::endl;
    	return -1;
	}
	cout << "Session successfully created."<< endl;

	//开始进行预测
	string image_path= argv[1];                 //图片路径
	int input_height =28;                       //高度
	int input_width=28;                         //宽度
	int input_mean=0;                           //均值
	int input_std=1;                            //方差
	std::vector<Tensor> resized_tensors;        //用于保存读取的图片的tensor数组
	
	//从图片读取数据并放入一个tensor之中
	cout<<"begin load image..."<<endl;
	Status read_tensor_status = ReadTensorFromImageFile(image_path, input_height, input_width, input_mean,
                          				   			    input_std, &resized_tensors);
    if (!read_tensor_status.ok()) {
  		LOG(ERROR) << read_tensor_status;
    	cout<<"resing error"<<endl;
    	return -1;
  	}
	cout<<"load image successful!"<<endl;

	const Tensor& resized_tensor = resized_tensors[0];
	std::cout << resized_tensor.DebugString()<<endl;          //打印出输入模型的tensor形状
	vector<tensorflow::Tensor> outputs;
	string output_node = "logits";                            //这里的输出名logits要和模型的输出相匹配

	//开始预测,这里的输入名images要和模型的输入相匹配
	Status status_run = session->Run({{"images", resized_tensor}}, {output_node}, {}, &outputs);
	if (!status_run.ok()) {
		std::cout << "ERROR: RUN failed..."  << std::endl;
    	std::cout << status_run.ToString() << "\n";
    	return -1;
  	}

	//取出输出值
    std::cout << "Output tensor size:" << outputs.size() << std::endl;
    for (std::size_t i = 0; i < outputs.size(); i++) {
    	std::cout << outputs[i].DebugString()<<endl;         //打印出模型输出的tensor的形状
  	}

  	Tensor t = outputs[0];                    // 取出第一个tensor
  	int ndim2 = t.shape().dims();             // 得到tensor的维度
  	auto tmap = t.tensor<float, 2>();         // Tensor Shape: [batch_size, target_class_num]
  	int output_dim = t.shape().dim_size(1);   // Get the target_class_num from 1st dimension
  	std::vector<double> tout;

	// Argmax:获取最终的预测label和概率值
  	int output_class_id = -1;
  	double output_prob = 0.0;
  	for (int j = 0; j < output_dim; j++){
        std::cout << "Class " << j << " prob:" << tmap(0, j) << "," << std::endl;
        if (tmap(0, j) >= output_prob) {
              output_class_id = j;
              output_prob = tmap(0, j);
        }
  	}

    // 打印日志
 	std::cout << "Final class id: " << output_class_id << std::endl;
  	std::cout << "Final class prob: " << output_prob << std::endl;

     //auto f=t.shaped<float,2>({2,5});
     //cout << f <<endl;
     //Eigen::array<int, 1> reduction_dims{0};

     //cout<< f.maximum(reduction_dims)<<endl;
     //Eigen::array<int, 2> offsets = {0, 0};
     //Eigen::array<int, 2> extents = {2, 1};
     //auto slice = f.slice(offsets, extents);
     //float *index = slice.data();
     //cout << "slice" << endl << slice << endl;
     //cout << slice.argmax()<<endl;
	return 0;
}

(3)CMakeLists的内容为

cmake_minimum_required (VERSION 2.8.8)           # cmake的最低版本号
project (tf_test)                                # 工程名
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++11 -W")  # 设置编译器,这里设为C++编译器

link_directories(path_to_tensorflow/bazel-bin/tensorflow)   # 链接库搜索目录

include_directories(                              # 头文件的搜索目录
   path_to_tensorflow/
   path_to_tensorflow/bazel-genfiles
   path_to_tensorflow/bazel-bin/tensorflow
   path_to_tensorflow/tensorflow/contrib/makefile/downloads/nsync/public
   path_to_tensorflow/tensorflow/contrib/makefile/downloads/absl
   path_to_tensorflow/tensorflow/contrib/makefile/gen/protobuf/include
   /usr/local/include/eigen3
   ) 

add_executable(tf_test  hello.cpp)                # 将源码编译为目标文件

target_link_libraries(tf_test tensorflow_cc tensorflow_framework) # 把动态链接库链接到目标文件中

(4)编译和运行

cd build
cmake ..
make
./tf_test digit.jpg

  输出结果为:

Session successfully created.
begin load image...
load image successful!
Tensor<type: float shape: [1,28,28,1] values: [[[253][21][0]]]...>
Output tensor size:1
Tensor<type: float shape: [1,10] values: [0 1 0...]...>
Class 0 prob:0,
Class 1 prob:1,
Class 2 prob:0,
Class 3 prob:0,
Class 4 prob:0,
Class 5 prob:0,
Class 6 prob:0,
Class 7 prob:0,
Class 8 prob:0,
Class 9 prob:0,
Final class id: 1
Final class prob: 1

参考:https://blog.csdn.net/zwx1995zwx/article/details/79064064

          https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/label_image/main.cc

猜你喜欢

转载自blog.csdn.net/MOU_IT/article/details/88070285