c++调用tflite实战

一,概述

深度学习模型在移动端的应用越来越多,tensorflow lite就是专门为tensorflow模型在移动端上线推断设计的框架。tensorflow 官方提供了不少cv的tflite模型,以及c++调用的例子。我们在这里以一个nlp的例子来从零实现到c++调用,并且以调用so动态库,用cmake编译的方式的来实现调用tflite模型进行预测。

二,模型训练

采用的例子是一个类似语言模型的例子,用上文预测当前词,详情见nlp_tflite/char_rnn。

在模型的构造过程中要注意一些问题,因为tensorflow lite为了轻量化(编译后的so文件只有2.7M大小),所以很多tensorflow中的operation都不支持,所以在编写模型的过程中需要注意,来说下当前碰到的点,当然肯定还有很多的坑没有碰到。

1),显示确定计算图中所有tensor的维度

我们在搭建模型时在使用tf.placeholder传入数据的时候,有时候会将维度指定为None,在用python训练和预测时会根据你传入的数据去动态的确定这个维度的值,如果你想转换成tflite模型,就必须确定输入的tensor的维度。因为在执行转换到tflite的时候需要明确指定输入的tensor的维度,否则会将第一个维度定为1(tflite会默认所有输入的tensor的第一个维度时batch_size,并赋值为1)。

计算图中的tensor同样要确定维度,一般来说输入的维度确定了,中间维度一定会是确定,但也有一些特殊的情况,这种我就碰到过,比如我传入一个标量sequence_len到模型中,标量的值是动态变化的(如序列的长度),然后用tf.range(sequence_len),这种情况不影响转换成tflite模型,但是在c++调用时会出问题,因为c++是预先给每个tensor分配一个固定大小的地址,这个地址就是由这个tensor的维度和tensor的数据类型确定的,而tf.range是会根据sequence_len的长度生成一个长度不等的一维tensor,此时c++在分配内存时竟然默认数据长度为1。

因此无论如何都要确定计算图中所有tensor的维度,这样在c++编译时才能合理的预先分配内存。

2),不支持random_uniform

这个operation不支持确实让有点惊讶,但确实不支持,这里影响到的operation挺多的,如tf.random_unifrom_initializer(), tf.glorot_uniform_initializer(), tf.nn.dropout()等等。只要里面调用了random_uniform就不支持,但是有的初始化可能用random_uniform就是比random_normal好,可以用numpy初始化,然后传入到模型中。

3),不支持dynamic_rnn

dynamic_rnn是用于rnn类模型解码的operation,这个暂时不支持,也不支持这里面使用到的tf.while_loop之类的操作,需要更换成static_rnn。

4),不支持tf.boolean_mask

这个ops在rnn中用的也很多,通常用在计算损失的时候mask掉序列中padding的部分,但这个也不支持。

5),定义两份计算图

训练的时候通常使用batch_size=32,64这种形式输入数据,而预测时通常是一条样本,这两种情况有时候使用的operation是不一样的,所以可以定义两份计算图,但是要保证两份图中的权重是一样的,将训练的图保存成checkpoint,之后可以加载训练的图,并将相应的权重赋给预测的图,并保存成freeze pb文件。

6),量化

量化可以见tensorflow模型量化实例,在转换成tflite时候可以量化,但对于模型空间比较小的小模型,采用posted training quanzitation的效果可能会不太好,可采用尝试quantization aware training。采用cpu推断时建议量化,cpu在int8的计算效率上要高于float32。

三,编译tensorflowlite.so

1),安装bazel,见官方教程 https://docs.bazel.build/versions/master/install.html

2),git clone https://github.com/tensorflow/tensorflow.git

3),进入到tensorflow文件夹下,也就是位于WORKSPACE下面,bazel需要在这个文件的目录下执行编译

4),./configure

5),bazel build –cxxopt=’–std=c++11′ //tensorflow/lite:libtensorflowlite.so 如果要在安卓上使用,需要指定ndk

四,创建c++工程,并将依赖的头文件和so文件放到工程下

1),将tflite的头文件放置到include下(cd tensorflow/tensorflow) (find ./lite -name “*.h” | tar -cf headers.tar -T -)

2),将上面的headders.tar压缩包移动到自己项目下的cpp_tflite/include中并解压,解压后可以直接去除

3),将tensorflow/bazel-bin/tensorflow/lite/libtensorflowlite.so和tensorflow/bazel-bin/external/flatbuffers/libflatbuffers.a 放到cpp_tflite/lib文件夹中

4),进入到tensorflow/tensorflow/lite/tools/make下执行download_dependencies.sh,或者直接下载flatbuffers压缩包,然后取出include的内容放置到cpp_tflite/include中即可。

后序编译详情见github项目中的README

五,c++调用tflite

tflite中提供了一种数据结构——flatbuffers,会将tflite模型转换成flatbuffers模型,之后会将模型中的tensor映射到tflite的解释器中,之后会使用tflite中的解释器interpreter进行推断,在加载模型之后一定要确保flatterbuffers的model指针和interpreter两个指针都是一直存在的,即在整个模型的工作期间都要一直存在,因为依赖这两个指针进行tensor的查找并进行推断。我开始就犯了一个错误,以为只需要保持interpreter存在就行,将interpreter写在成员属性中,而flatbuffers model只在构造函数中使用,结果就一直报错。

另外在c++中所有的tensor都是由一个指针和一片连续的内存表示,所以无论多少维的输入,都需要一个个的循环写入到c++分配的内存中。

以上就是一些注意的点,基本上就能完成一个简单的模型转tflite,并用c++进行推断。

参考文献

Original: https://www.cnblogs.com/jiangxinyang/p/13215724.html
Author: 微笑sun
Title: c++调用tflite实战

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/535534/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • C#调用C++的dll两种方法(托管与非托管)

    C#与C++交互,总体来说可以有两种方法: 利用PInvoke实现直接调用 非托管C+ 利用C++/CLI作为代理中间层 一、非托管C++ 由于C#编写的是托管代码,编译生成微软中…

    C++ 2023年5月29日
    048
  • 【面试攻略】C++面试-成都星合互娱

    2020-11-26-成都星合互娱 hr1.离职原因2.工作总结3.学历4.期望薪资 技术面1.自我介绍2.项目架构3.内存管理4.智能指针5.对象封装6.lua热更,热更对象的成…

    C++ 2023年5月29日
    064
  • C++中的三种继承关系

    先看类中声明成员时的三种访问权限 public : 可以被任意实体访问 protected : 只允许子类及本类的成员函数访问 private : 只允许本类的成员函数访问 在类继…

    C++ 2023年5月29日
    067
  • 【C++服务端技术】对象池

    代码没贴全,就少一个锁头文件,可以做设计参考 设计思想就是维护一个空闲链表,没有用的就重新申请,有的话就拿链表的头,使用完又还给空闲链表。 /* 一个分配固定大小内存的内存池,使用…

    C++ 2023年5月29日
    066
  • C++ mutable的用法

    mutalbe的中文意思是”可变的,易变的”,跟constant(既C++中的const)是反义词。 在C++中,mutable也是为了突破const的限制…

    C++ 2023年5月29日
    050
  • c++类大四个默认函数-构造函数 析构函数 拷贝构造函数 赋值构造函数

    每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。对于任意一个类A,如果不编写上述函数,C++编译器将自动为A 产生四个缺…

    C++ 2023年5月29日
    060
  • std::get<C++11多线程库~线程间共享数据>(10):使用互斥量保护共享数据(5)

    1 /* 2 * 话题1:使用互斥量保护共享数据 3 * 4 * 接下来学习第五个小话题:避免死锁的进阶指导 5 * 6 * 这一小节的内容,完全引用,只在最后补充上我对这部分的理…

    C++ 2023年5月29日
    081
  • c++智能指针

    跟comptr类似 明确定义AddRef和Release,然后定义与comptr类似的一个辅助类. 这里有2种方式 1.Release的时候引用计数为0的时候删除对象 2.定义一个…

    C++ 2023年5月29日
    094
  • The main difference between Java & C++(转载)

    C++ supports pointers whereas Java does not pointers. But when many programmers questioned…

    C++ 2023年5月29日
    063
  • C++ 创建静态链接库和动态链接库

    上篇文章演示了如恶化使用C++ 编译的静态链接库和动态链接库,本篇文章主要介绍如何创建静态链接库和动态链接库,本文使用的工具是visual studio 2019 企业版,需要安装…

    C++ 2023年5月29日
    056
  • 【面试攻略】C++面试-沐瞳游戏

    2020-11-24-沐瞳游戏自我介绍 1.你们IO用的什么(HPsocket),他是怎么实现的,(HPsocket他Windows通信用的什么)2.tcp的三次握手四次挥手3.有…

    C++ 2023年5月29日
    072
  • 关于C++ const 的全面总结

    C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性,本人根据各方面查到的资料进行总结如下,期望对朋友们有所帮助。 Const 是C++中常用的类型修饰…

    C++ 2023年5月29日
    041
  • C++调用C#的动态库dll

    以往我们经常是需要使用C#来调用C++的dll,这通过PInvoke就能实现。现在在实际的项目过程中,有时会遇到在C++的项目中调用某个C#的dll来完成特定的某个功能,我们都知道…

    C++ 2023年5月29日
    086
  • VC++ 使用attributes定义接口

    1.定义预处理命令_ATL_ATTRIBUTES 2.在一个全局的Cpp文件里面配置module的attribute [module(dll, uuid = "{3845…

    C++ 2023年5月29日
    065
  • C++ #ifndef、#define、#endif作用

    在C++项目中,#ifndef、#define、#endif非常常见,接下来就来简单说一下它们的作用。 作用:防止头文件被重复引用,防止被重复编译。 简介: ifndef 它是if…

    C++ 2023年5月29日
    072
  • [C++] 引用类型&

    引用的方法: 类型 &引用名 = 变量名; 例如: int a = 5; int &b = a; 引用的规则: 1、引用被创建的同时必须被初始化 2、无null引用…

    C++ 2023年5月29日
    056
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球