TFLite介绍

转自:https://www.cnblogs.com/vitoyeah/p/10273299.html

概述

TFLite主要含有如下内容:

(1)TFLite提供一系列针对移动平台的核心算子,包括量化和浮点运算。另外,TFLite也支持在模型中使用自定义算子。

(2)TFLite基于FlatBuffers定义了一种新的模型文件格式。FlatBuffers类似于protocol buffers, FlatBuffers在访问数据之前不需要进行解析/解包步骤,通常与每个对象的内存分配相结合。而且,FlatBuffers的代码占用空间比protocol buffers小一个量级。

(3)TFLite拥有一个新的优化解释器,其主要目标是保持应用程序的精简和快速。 解释器使用静态图形排序和自定义(动态性较小)内存分配器来确保最小的负载、初始化和执行延迟。

(4)TFLite提供了一个利用硬件加速的接口,通过安卓端的神经网络接口(NNAPI)实现,可在Android 8.1(API级别27)及更高版本上使用。

TFLite提供的支持:

(1)一组核心ops,包括量化和浮点运算,其中许多已经针对移动平台进行了调整。 这些可用于创建和运行自定义模型。开发人员还可以编写自己的自定义ops,并在模型中使用。

(2)一种新的基于FlatBuffers的模型文件格式。

(3)MobileNet模型的量化版本,其运行速度比CPU上的非量化(浮点)版本快。

(4)更小的模型:当使用所有支持的运算符时,TFLite小于300KB,当仅使用支持InceptionV3和Mobilenet所需的运算符时,TFLite小于200KB。

(5)支持Java和C++接口

(6)提供一些pre-trained模型,例如mobileNets、Inception。

TensorFlow Lite的架构设计:

一、开发指南

在移动应用程序中使用TensorFlow Lite模型,分为三个以下步骤:

(1)选择预先训练或自定义模型;

这一步骤有三种选择模型的方式:a)使用pre-trained模型,例如mobileNets、Inception;b)在新的数据集上重新训练pre-trained模型;c)使用tf训练自定义的模型

(2)将模型转换为TensorFLow Lite格式;

TFLite Converter的支持输入:SavedModels,frozen graphs(由freeze_graph.py生成的模型)和tf.keras HDF5模型。输出文件为TFLite模型文件,部署到客户端(包括移动端或嵌入式设备)后,通过TFLite interpreter提供的接口使用TFLite模型文件。(具体转换过程见博文:TFLite模型转换过程)

(3)最后将模型集成到应用程序中。

Android端:由于Android应用程序是用Java编写的,而核心TensorFlow库是用C ++编写的,因此需要提供一个JNI库作为接口。(在Android端如何编写JNI库,以及将C++程序成封装.so文件,包括JNI基础、javaC++如何通过JNI来建立连接、.so封装的过程、makelist编写,具体见博文:Android studio 编写JNI库,封装成.so文件)

二 、TFLite接口

(1)C++接口

模型加载到FlatBufferModel对象中,然后由Interpreter类来执行。FlatBufferModel需要在Interpreter类的整个生命周期内保持有效,单个FlatBufferModel可以由多个Interpreter同时使用。具体而言,FlatBufferModel对象必须在使用它的任何Interpreter对象之前创建,并且必须保持至所有FlatBufferModel对象被销毁。

a)数据对齐

TFLite数据通常与16字节边界对齐。建议TFLite的所有输入数据都以这种方式对齐。

b)加载模型

FlatBufferModel类封装了模型加载,根据模型的存储位置以几种略有不同的方式构建:

请注意,如果TFLite检测到Android NNAPI的存在,它将自动尝试使用共享内存来存储FlatBuffer模型。

c)运行模型

几个简单的步骤:

    • 基于现有的FlatBufferModel构建解释器(Interpreter)
    • 如果不需要预定义的大小,可以选择调整输入tensor的大小。
    • 设置输入tensor值
    • 调用inference类
    • 读取输出张量值

Interpreter的公共接口,需要注意如下几点:

    • 为了避免字符串比较,tensor由整数表示;
    • 不能从并发线程中访问解释器;
    • 在调整张量大小后,立即调用AllocateTensors()来分配输入和输出tensor的内存。

d)自定义算子

所有TFLite运算符(自定义和内置运算符)都使用纯C接口定义,该接口由四个函数组成:

有关TfLiteContext和TfLiteNode的详细信息,请参阅tensorflow\lite\c\c_api_internal.h,该文件定义了在tflite中实现操作的C API。

如下所示的全局注册函数(如同tensorflow\lite\kernels\下的内置算子)中自定义上述四个函数,可以与内置操作完全相同的方式实现自定义操作:

请注意,注册不是自动的,应该在某处使用BuiltinOpResolver显式调用Register_MY_CUSTOM_OP。

e)自定义内核库

Interpreter将加载一个内核库(kernel),这些内核将用于执行模型中的每个操作符。Interpreter使用OpResolver将operator codes和name转换为实际代码:

通常的使用方法(tensorflow\lite\mutable_op_resolver.h):

MutableOpResolver resolver;

resolver.AddBuiltin(BuiltinOperator_ADD, Register_ADD());  //添加内置算子

resolver.AddCustom("CustomOp", Register_CUSTOM_OP()); //注册自定义算子

InterpreterBuilder(model, resolver)(&interpreter);  

 (2)Java接口

TensorFlow Lite的Java API支持设备上inference,并作为Android Studio库提供,允许加载模型,提供输入和检索输出。

加载模型:使用Interpreter.java类进行TFLite模型推断(model inference)。使用模型文件初始化Interpreter类,Interpreter(@NonNull File modelFile, Options options)。

运行模型:

a)支持的数据类型

要使用TFLite,输入和输出tensor的数据类型必须是以下基本类型之一:float、int、long、byte。如果使用其他数据类型(包括类似Integer和Float的封装类型),则会抛出IllegalArgumentException。

b)输入值

每个输入应该是受支持的基本类型的数组或多维数组,或者是适当大小的原始ByteBuffer。 如果输入是数组或多维数组,则在模型推断时,模型相关的输入tensors将被隐式调整为数组的维度。如果输入是ByteBuffer,则调用者应首先在运行推断(inference)之前,手动调整输入张量(通过Interpreter.resizeInput())。

ByteBuffer是缓冲区类,使用它可以进行高效的IO操作,允许解释器避免不必要的copy。 如果ByteBuffer是直接字节缓冲区,则其顺序必须为ByteOrder.nativeOrder()。 ByteBuffer用于模型推断后,必须保持不变,直到模型推断完成。

c)输出

输出应该是受支持的基本类型的数组或多维数组,或者是适当大小的ByteBuffer。 请注意,某些型号具有动态输出,其中输出张量的形状可根据输入而变化。 使用现有的Java推理API没有直接的方法来处理这个问题,但未来计划的扩展将使这成为可能。

d)运行模型推断

一种输入和一种输出:

               run(@NonNull Object input, @NonNull Object output)
 

多种输入或者多种输出:

               runForMultipleInputsOutputs(Object[] inputs, @NonNull Map<Integer, Object> outputs)

e)释放资源

为避免内存泄漏,必须在使用后释放资源:interpreter.close();

三、如何使用自定义算子

通过一个例子来讨论这个问题。 假设我们正在使用Sin运算符,并且我们正在为函数y = sin(x + offset)构建一个非常简单的模型,其中offset是可训练的。

训练TensorFlow模型的代码将类似于:

如果使用带有--allow_custom_ops参数的TensorFlow Lite优化转换器将此模型转换为Tensorflow Lite格式,并使用默认解释器运行它,则解释器将引发以下错误消息:

TFLite中使用op所需要做的就是定义两个函数(Prepare和Eval),并构造一个TfLiteRegistration。如下示例(也可以根据kernel文件中的内置算子就行仿写):

初始化OpResolver时,将自定义op添加到解析器中,这将使用Tensorflow Lite注册操作符,以便TensorFlow Lite可以使用新的实现。

编写自定义运算符的最佳实践:

(a)谨慎地优化内存分配和取消分配。相比于invake(),在Prepare()中分配内存更有效,并在循环之前而不是在每次迭代中分配内存。使用临时tensor数据而不是自己进行mallocing(参见第2项)。尽可能使用指针/引用而不是复制。

(b)如果数据结构在整个操作期间一直存在,我们建议使用临时张量预分配内存。可能需要使用OpData结构来引用其他函数中的张量索引。请参阅kernel for convolution

(c)倾向于使用静态固定大小的数组(或者在Resize()中预先分配的std :: vector),而不是每次执行迭代时都使用动态分配std :: vector。

(d)避免实例化尚不存在的标准库容器模板,它们会影响二进制文件大小。例如,如果操作中需要std :: map而其他内核中不存在,则使用带有直接索引映射的std :: vector就可以,这样可以保持二进制大小较小。

(e)检查指向malloc返回的内存的指针。如果此指针为nullptr,则不应使用该指针执行任何操作。如果函数中有malloc()并且出现错误,在退出之前释放内存。

(f)使用TF_LITE_ENSURE(context,condition)检查特定条件。

特殊TF Graph属性:

当Toco将TF graph转换为TFLite格式时,生成的graph可能不可执行。

因此,在转换之前,可以将有关自定义算子输出的附加信息添加到TF graph中。 支持以下属性:

_output_quantized一个布尔属性,如果操作输出被量化,则为true

_output_types     输出张量的类型列表

_output_shapes   输出张量的形状列表

如何设置属性的示例:

四、TFLite 算子更新

由于TensorFlow Lite操作集小于TensorFlow,因此并非每个模型都可以转换。 即使对于受支持的操作,出于性能原因,有时也会出现非常具体的使用模式。在未来的TensorFlow Lite版本中将扩展支持的操作集。

本文档描述了TensorFlow Lite的op算子更新框架。 Op算子的更新使得开发人员能够将新功能和参数添加到现有操作中。 此外,它保证以下内容:

  • 向后兼容性:新的TensorFlow Lite实现应该处理旧的模型文件。
  • 向前兼容性:只要没有使用新功能,旧TensorFlow Lite实现应该处理由新版TOCO生成的新模型文件。
  • 正向兼容性检测:如果旧的TensorFlow Lite实现不支持的新版本操作的新模型,则应报告错误。

示例:将dilation添加到卷积操作

本文档的其余部分通过展示如何将dilation参数添加到卷积操作来解释TFLite中的如何进行算子更新。需要注意两点:

  • 将添加2个新的整数参数:dilation_width_factor和dilation_height_factor。
  • 不支持扩张的旧卷积核相当于将扩张因子设置为1。

(1)更改FlatBuffer架构

将新参数添加到op中,需要更改lite/schema/schema.fbs中的options表。

例如,卷积选项表如下所示:

添加新参数时,需要添加注释,指示哪个版本支持哪些参数。

添加新参数后,表格将如下所示:

(2)更改C结构和内核实现

在TensorFlow Lite中,内核实现与FlatBuffer定义分离。 内核从lite/builtin_op_data.h中定义的C结构中读取参数。

原始卷积参数如下:

与FlatBuffer架构一样,需要添加注释,指示从哪个版本开始支持哪些参数。 结果如下:

最后更改内核,实现C结构中新添加的参数。 这里省略了细节。

(3)更改FlatBuffer阅读代码

文件lite/model.cc中读取FlatBuffer并生成C结构的逻辑。

更新文件以处理新参数,如下所示:

这里不需要检查op算子的版本。 因为,当新的方法读取缺少扩张因子的旧模型文件时,该方法将使用1作为默认值,保证新内核将与旧内核一致地工作。

(4)更改内核注册

MutableOpResolver(在lite / op_resolver.h中定义)提供了一些注册op内核的函数。 默认情况下,最小和最大版本为1:

内置的ops在lite / kernels / register.cc中注册。在这个例子中,实现了一个新的op内核,可以处理Conv2D版本1和2,所以我们需要将:

改为

(5)更改TOCO TFLite导出

最后一步是让TOCO填充执行操作所需的最低版本。 在这个例子中,它意味着:

  • 填充 version=1 when dilation factors are all 1.
  • 填充 version=2 otherwise.

为此,需要在lite/toco/tflite/operator.cc中覆盖运算符类的GetVersion函数。

对于只有一个版本的操作,GetVersion函数定义为:

支持多个版本时,请检查参数并确定op的版本,如以下示例所示:

(6)授权

TensorFlow Lite提供了一个授权API,可以将op授权给硬件后端。 在Delegate的Prepare函数中,检查授权代码中是否支持每个节点的版本。

五、TFLite 和TF兼容性指南

 由于TensorFlow Lite操作集小于TensorFlow,因此并非每个模型都可以转换。 即使对于受支持的操作,出于性能原因,有时也会出现非常具体的使用模式。在未来的TensorFlow Lite版本中将扩展支持的操作集。

了解如何构建可与TensorFlow Lite一起使用的TensorFlow模型的最佳方法,是仔细考虑如何转换和优化操作,以及此过程施加的限制。

 本文来源于tensorflow lite官网 https://tensorflow.google.cn/lite/overview

猜你喜欢

转载自blog.csdn.net/qq_34106574/article/details/91386728