安装好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 ..
./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
安装好C++版的TensorFlow之后,我们就可以用C++来部署python训练好的TensorFlow模型了。安装C++版的TensorFlow的教程可以参考这里。部署TensorFlow模型主要分为两步,第一步是用python训练模型,然后保存模型为.pb格式的二进制文件;第二步则是在C++中加载python保存的模型并进行预测。1、python训练模型并保存2、使用C++加...
tensorflow作为一个著名的开源深度学习框架,其在python平台的神经网络模型搭建、训练和测试等是很齐全的,但其在C++端的设计方面性能相对较差。在实际工程中,C++项目具有更省时的优点,因此基于C++的tensorflow的开发是很重要的。
一些比较知名的网络模型,例如yolo-v3系列等的C++代码较为全面,一定程度属于定制的。
然而对于一些自己编写的网络模型的支持方面,我们往往需要自己来配置环境。坦白说,笔者在配置环境方面花费了很多时间,参考了很多优秀的资料,但最后都不尽如意。作为一个小白
tensorflow目前支持最好的语言还是python,但大部分服务都用C++ or Java开发,一般采用动态链接库(.so)方式调用算法,因此tensorflow的c/c++ API还是有必要熟悉下,而且经过本人测试,相同算法,c接口相比python速度更快。
下面讲解如何让程序调用tensorflow c/c++库
1.编译库
先在github上下载tensorflow源码,执行...
在实际项目部署过程中,会需要tensorflow c或者c++版本, 网上一通查,说自己编译会各种坑,投机取巧。。在拿到别人编译好的c和c++版本调用报错后,开始痛定思痛,躲不过的坑 自己来趟好了。。
先说下电脑环境:
linux18.04
cuda 10.0
cudnn 7.6.5
make 4.1
protobuf 3.7
############环境安装#############
怕tensorflow版本高 容易出错,保守选择 tensroflow 1.13.1版本, 并选择
摘要: 最近在研究如何使用tensorflow c++ API调用tensorflow python环境下训练得到的网络模型文件。参考了很多博客,文档,一路上踩了很多坑,现将自己的方法步骤记录下来,希望能够帮到有需要的人!(本文默认读者对python环境下tensorflow的使用已经比较熟悉了)
方法简要梳理如下:
安装bazel,然后使用bazel编译tensorflow源码,产生我们
tensorflow_service
tensorflow是被广泛应用的深度学习框架,提供丰富的API接口,可以省去很多自己的开发工作。python版本的tensorflow是被应用最多的。但是python的执行效率偏低。有很多公司后台是用C++编写的,为了更好的将深度模型应用到线上,通常需要进行模型在线inference。
最近在做tensorflow模型的C++线上inference, 模型训练仍然利用python tensorflow验证效果,实际上线时,采用更加高效的C++ API进行服务。将经
有时候,我们需要将TensorFlow的模型导出为单个文件(同时包含模型架构定义与权重),方便在其他地方使用(如在c++中部署网络)。利用tf.train.write_graph()默认情况下只导出了网络的定义(没有权重),而利用tf.train.Saver().save()导出的文件graph_def与权重是分离的,因此需要采用别的方法。
我们知道,graph_def文件中没有包含网络中的Variable值(通常情况存储了权重),但是却包含了constant值,所以如果我们能把Variable转换为constant,即可达到使用一个文件同时存储网络架构与权重的目标。
我们可以采用以下方式冻结
现在的深度学习框架一般都是基于 Python 来实现,构建、训练、保存和调用模型都可以很容易地在 Python 下完成。但有时候,我们在实际应用这些模型的时候可能需要在其他编程语言下进行,本文将通过直接调用 TensorFlow 的 C/C++ 接口来导入 TensorFlow 预训练好的模型。
1.环境配置 点此查看 C/C++ 接口的编译
2. 导入预定义的图和训练好的参数值
// se
将Keras训练的.hdf5模型固化为.pb模型
from tensorflow.python.framework import graph_io
from keras.models import load_model
from keras import backend as K
import tensorflow as tf
def freeze_session(session,...
一、编译TensorFlow的c++接口
在调用TensorFlow的c++接口之前,首先要安装bazel、protobuf、Eigen等软件,然后下载TensorFlow源码进行编译,整体过程还是比较麻烦。
1、配置C++版tensorflow使用时的第三方依赖
(1)protobuf下载及安装
Protobuf这玩意儿是重中之重,它的版本与tensorflow的版本密切相关,它的版本错...
花了整整一天的时间,终于编译好了
c++版本的
tensorflow,下面将给出我的整个编译过程,我只能说过程很艰辛,查了很多资料,不过最后总算是成功了,功夫不负有心人。下面将给我的编译过程:
参考博客:
https://blog.csdn.net/jiugeshao/article/details/79144438
https://www.jianshu.com/p/3549c8e9cc15
CNN(卷积神经网络)是一种在计算机视觉领域常用的深度学习模型,可以对图像进行特征提取和分类。而手写字体识别是一种常见的应用场景,在数字识别、文字识别等方面都有广泛的应用。TensorFlow是一种基于数据流图的开源机器学习框架,其支持各种机器学习算法的实现,并能够在多种硬件和操作系统上运行。因此, CNN和TensorFlow结合使用可以有效提高手写字体识别的准确率和效率。
在应用CNN进行手写字体识别时,首先需要对训练数据进行处理和标记。接着,构建CNN网络模型,通过反向传播算法进行训练,不断优化模型参数,提高识别准确率。然后,将模型部署到TensorFlow平台上,加快运行速度,提高效率。
在实际应用中,CNN和TensorFlow结合使用可以实现高效的手写字体识别。例如,在自动化识别银行支票、快递单等场景中,可以通过CNN模型提取特征并使用TensorFlow优化模型,实现较高的识别率和较快的处理速度。总的来说, CNN和TensorFlow结合使用为手写字体识别提供了一种强有力的解决方案,并在实际应用中得到了广泛的应用和验证。