c++调用tflite实战

一,概述

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

  项目源代码:https://github.com/jiangxinyang227/nlp_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++进行推断。

 

参考文献

C++TensorFlow Lite编译及使用

https://tensorflow.google.cn/lite/guide

posted @ 2020-06-30 23:45  微笑sun  阅读(273)  评论(0编辑  收藏