此基础知识仅为个人学习记录,如有错误或遗漏之处,还请各位同行给个提示。

概述

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

TFLite基础知识的更多相关文章

  1. .NET面试题系列[1] - .NET框架基础知识(1)

    很明显,CLS是CTS的一个子集,而且是最小的子集. - 张子阳 .NET框架基础知识(1) 参考资料: http://www.tracefact.net/CLR-and-Framework/DotN ...

  2. RabbitMQ基础知识

    RabbitMQ基础知识 一.背景 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.AMQP 的出现其实也是应了广大人民群众的需求,虽然 ...

  3. Java基础知识(壹)

    写在前面的话 这篇博客,是很早之前自己的学习Java基础知识的,所记录的内容,仅仅是当时学习的一个总结随笔.现在分享出来,希望能帮助大家,如有不足的,希望大家支出. 后续会继续分享基础知识手记.希望能 ...

  4. selenium自动化基础知识

    什么是自动化测试? 自动化测试分为:功能自动化和性能自动化 功能自动化即使用计算机通过编码的方式来替代手工测试,完成一些重复性比较高的测试,解放测试人员的测试压力.同时,如果系统有不份模块更改后,只要 ...

  5. [SQL] SQL 基础知识梳理(一)- 数据库与 SQL

    SQL 基础知识梳理(一)- 数据库与 SQL [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5902856.html 目录 What's 数据库 ...

  6. [SQL] SQL 基础知识梳理(二) - 查询基础

    SQL 基础知识梳理(二) - 查询基础 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5904824.html 序 这是<SQL 基础知识梳理( ...

  7. [SQL] SQL 基础知识梳理(三) - 聚合和排序

    SQL 基础知识梳理(三) - 聚合和排序 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5926689.html 序 这是<SQL 基础知识梳理 ...

  8. [SQL] SQL 基础知识梳理(四) - 数据更新

    SQL 基础知识梳理(四) - 数据更新 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5929786.html 序 这是<SQL 基础知识梳理( ...

  9. [SQL] SQL 基础知识梳理(五) - 复杂查询

    SQL 基础知识梳理(五) - 复杂查询 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5939796.html 序 这是<SQL 基础知识梳理( ...

随机推荐

  1. linux关机(重启)命令

    Linux系统关机命令: #关机命令 1.halt 2.poweroff 3.shutdown -h now 立即关机(具有root权限用户使用)#重启命令 1.reboot 2.shutdown - ...

  2. IO流(三)

    五.Java序列化 概述 Java序列化是指把Java对象转换为字节序列的过程 Java反序列化是指把字节序列恢复为Java对象的过程 当两个Java进程进行通信时,发送方需要把这个Java对象转换为 ...

  3. 单片机C语言基础编程源码六则

    1.某单片机系统的P2口接一数模转换器DAC0832输出模拟量,现在要求从DAC0832输出连续的三角波,实现的方法是从P2口连续输出按照三角波变化的数值,从0开始逐渐增大,到某一最大值后逐渐减小,直 ...

  4. react context跨组件传递信息

    从腾讯课堂看到的一则跨组件传递数据的方法,贴代码: 使用步骤: 1.在产生参数的最顶级组建中,使用childContextTypes静态属性来定义需要放入全局参数的类型 2.在父组件中,提供状态,管理 ...

  5. LabVIEW中开放隐藏属性的inikey

    SuperSecretPrivateSpecialStuff=TRUE 在LabVIEW中有很多属性和方法是隐藏的,在labview安装根目录下的ini中添加该信息能开放这些隐藏的属性和方法.这时候能 ...

  6. MySQL ERROR 1698 (28000): Access denied for user 'root'@'localhost'

    今天在安装MySQL的过程中竟然没有让我输入密码,登录的时候也不需要密码就能进入,这让我很困惑. 进了数据库就设置密码,用了各种方式都不行. 虽然我这数据库没啥东西但也不能没有密码就裸奔啊,有点丢人是 ...

  7. Centos7配置SVN服务端

    环境 Centos 7 SVN 1.7 安装SVN Shell> yum install subversion -y 准备配置和仓库 Shell> mkdir -p /mydata/rep ...

  8. 详解设计模式在Spring中的应用

    设计模式作为工作学习中的枕边书,却时常处于勤说不用的尴尬境地,也不是我们时常忘记,只是一直没有记忆. 今天,在IT学习者网站就设计模式的内在价值做一番探讨,并以spring为例进行讲解,只有领略了其设 ...

  9. MyBatis-plus使用

    https://blog.csdn.net/qq_32867467/article/details/82944674 官网: https://mp.baomidou.com/guide/optimis ...

  10. golang协程踩坑记录

    1.主线程等待多个协程执行完毕后,再执行下面的程序.golang提供了一个很好用的工具. sync.WaitGroup下面是个简单的例子. 执行结果: 2.主线程主动去结束已经启动了的多个协程.执行结 ...