Tensorflow c++ inference

利用Tensorflow Python接口训练可以得到ckpt模型文件,这就是本文的起点。我们假设读者机器上已经有可用的C++ Tensorflow库。用Tensorflow的C++接口做inference的大致流程如下:

  1. 用python脚本将ckpt等模型文件转换生成.pb文件
  2. 数据准备
  3. 运行模型
  4. 必要的后处理

ckpt转为pb文件

某些时候这样的步骤并不是必须的,因为Tensorflow C++接口也是支持直接读入ckpt模型的,但是我们依然推荐这样做:

  1. ckpt是训练模型得到的中间结果,设计本身非常方便于模型的retrain,但对于inference而言却有不少冗余的信息,如loss函数的计算,这一部分在做inference时可以剔除。
  2. Tensorflow Python接口允许用户利用Tensorflow的PyFunc接口把自己写的python函数打包成Tensorflow的op保存到计算图中,这些op自然也会被保存在ckpt中,可惜,这些op在Tensorflow C++接口中并不支持,直接读入ckpt会读取失败,如果遇到诸如
Not found: Op type not registered 'PyFunc' in binary running on ...

的错误,那就说明模型中含有这样的op。

  1. 在将ckpt模型转为pb模型的过程中可以指定模型的输入和输出,过程用没有使用到的op会被剔除,因此只要输入->输出过程中使用的都是Tensorflow的原生op,那么生成的pb模型就是可以用C++读取的。

ckpt转pb文件用python写即可,网上教程一大堆,比如这儿,这里就不啰嗦了。

C++读取模型

转换后的模型应该包含一个.pb文件和一个variables目录,c++读取模型的时候只需要指定pb和variables所在的目录即可。读取流程如下:

/*
此处应有
#include "tensorflow/core/public/session.h"
#include "tensorflow/cc/saved_model/loader.h"
#include "tensorflow/core/framework/graph.pb.h"
#include "tensorflow/core/protobuf/meta_graph.pb.h"
*/
  // contains $model_dir/**.pb and $model_dir/variables
  const std::string& graph_fn = model_dir_;
  // prepare session
  tensorflow::SessionOptions sess_options;
  tensorflow::RunOptions run_options;
  tensorflow::SavedModelBundle bundle;
  TF_CHECK_OK(tensorflow::LoadSavedModel(sess_options, run_options, \
    graph_fn, {tensorflow::kSavedModelTagServe}, &bundle));
  tensorflow::MetaGraphDef graph_def = bundle.meta_graph_def;
  std::unique_ptr<tensorflow::Session>& sess = bundle.session;

模型被读取时会自动构建一个ModelBundle,其中包含graph本身以及Session等,对于运行模型而言,这已经足够了。

数据准备

正如Python接口一样,模型的输入必须是tensor,即tensorflow::Tensor。如果你的数据是用OpenCV读取的图片,那么数据类型一定是cv::Mat,你需要一些魔法把他转成Tensor类型,比如这种魔法,当然,这样的魔法是有点蠢的,用for循环把数据一个个复制过去非常耗时,正如下文中提到的,我们在后处理时可以通过找到Tensor实际存储数据的内存地址直接读取而不必复制,我想给Tensor赋值也是有同样方法的,但时间所限,还没有研究。

运行模型

/*
此处应有
#include "tensorflow/core/framework/tensor.h"
*/
std::vector<tensorflow::Tensor> output_tensor;
TF_CHECK_OK(sess -> Run({{"Print:0", image_tensor}, }, {"strided_slice_4:0", "Shape:0"}, {}, &output_tensor));

可以说是很简单粗暴了,和Python跑模型的代码几乎一模一样:

  1. 第一个参数是输入,每个内层的大括号都是两个元素,第一个是op的名称,第二个是输入的数据,也就是上一步中准备好的数据
  2. 第二个参数是输出哪些op,数量不限,可以输出任何模型中有的op,可以不必只是最终的输出
  3. 第三个参数跑模型用不到,留空吧
  4. 第四个参数是指定输出保存到哪里,它的类型必须是Tensor的vector,你的第二个参数指定了输出几个op,最终得到的output_tensor的长度就是几。
注意
  1. TF_CHECK_OK是个好同志,几乎所有Tensorflow的代码都可以用它包起来,因为这些函数都会返回tensorflow::Status,如果运行有问题TF_CHECK_OK可以即时报错,因为很多函数运行错误不会报错,但是会悄咪咪的return,你会发现最终结果不对,但是哪里出了问题完全看不出来。
  2. ckpt模型转换为pb模型时会有一步指定op的name,但是运行模型时写的op的name,并不是你在转换模型时指定的name,而是op在ckpt图中的name,搞错了的话可能会有诸如op not found之类的错误。

必要的后处理

后处理是什么并不重要,重要的是tensorflow::Tensor提供的操作数据可用接口少得可怜,所以常常需要把Tensor转为我们需要的类型,此处必要的一个接口就是tensorflow::Tensor.tensor_data().data(),没记错的话默认它返回是一个unsigned char*类型,它返回什么无所谓,总之它就是Tensor实际存储数据的内存地址,只要你确切的知道神经网络返回的数据的类型和大小,就是把它reinterpret_cast成正确的指针类型,之后的数据都是连续存储的,别问我怎么访问。

// 刚刚说了output_tensor是一个vector,这里的示例中我输出的op有两个,所以vector长度是2
  auto &score_tensor = output_tensor[0];
  auto &shape_tensor = output_tensor[1];
  int height = score_tensor.dim_size(0);
  int width = score_tensor.dim_size(1);
  int channel = score_tensor.dim_size(2);
// StringPiece是引自Google ProtoBuffer中的一个数据结构,是把一段连续的内存当做字符串来对待,
// StringPiece类中有几乎所有std::string具有的接口,比如size()什么的,但你要清楚它实际上不一定是字符串,这样做只是为了方便操作
  tensorflow::StringPiece lane_buffer = score_tensor.tensor_data();
  std::cout << "buffer size: " << lane_buffer.size() << "\n";
  const float *lane_res = reinterpret_cast<const float*>(lane_buffer.data());
//但如果你还是要问我怎么访问,我可以告诉你lane_res就是一个数组,直接下标索引就可以了,注意大小(此处是height * width * channel)不要访问越界。
  return lane_res;

网上关于Tensor的数据转换内容比较少,但个人感觉既然有办法在后处理时直接得到Tensor数据内存地址,应该一定有办法用类似的方式赋值给Tensor,而不必一一拷贝。

Demo

不存在的,等有空了再整理吧...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容