8.3.2 TensorFlow Lite
1.简介
TensorFlow Lite 是 TensorFlow 移动和嵌入式设备轻量级解决方案,它使移动设备上的机器学习具有低延迟和更小的二进制文件存储大小。 TensorFlow Lite 同时支持 Android 神经网络API 的硬件加速。与此同时, TensorFlow Lite 为了使神经网络模型完美地运行于移动设备而应用了多项技术降低延迟,例如,移动 App 内核优化、 pre-fused 激活和允许更快与更小(定点)模型的量化内核。
TensorFlow Lite 支持一系列数量化和浮点的核心运算符,并针对移动平台进行了优化。它结合 pre-fused 激活和其他技术来进一步提高性能和量化精度。此外, TensorFlow Lite 还支持在模型中使用自定义操作。
在数据存储方面, TensorFlow Lite 基于 FlatBuffers 定义了一个新的模型文件格式。FlatBuffers 是一个开源的高效跨平台序列化库。它与 protocol buffers 类似,主要区别是FlatBuffers 常与 per-object 内存分配相结合,当你直接访问数据时不需要再次解析包。此外, FlatBuffers 的代码比 protocol buffers 的小很多。
为了提高模型计算与输出的速度, TensorFlow Lite 拥有一个新的基于移动设备优化的解释器,可以保持应用程序的精简和快速。
TensorFlow Lite 针对支持的设备提供了一个利用硬件加速的接口,通过 Android 神经网络库,作为 Android O-MR1 的一部分发布。
2.接入实践
( 1)安装
TensorFlow Lite 是 TensorFlow 的移动端版本,所以为了使用 TensorFlow Lite,需要先安装 TensorFlow 框架。
TensorFlow 支持 Ubuntu、 macOS、 Windows 以及树莓派平台。 TensorFlow 有多种安装方式,分别可以通过 virtualenv、原生 pip、 Docker 或者从源码进行编译安装。以下分别从几个方面进行详细介绍。
① 使用 virtualenv 安装
当开发 Python 应用程序的时候,系统中一般只安装了一个 Python 版本。在实际的开发过程中,我们很可能同时开发多个应用程序,而这些应用程序会共用一个 Python 环境,这就是安装在系统中的 Python 环境。如果应用 A 需要 Python 2.7 版本,而应用 B 需要 Python 3.7 版本,这时应该怎么办呢?在这种情况下,每个应用都可能需要拥有一套“独立”的 Python 运行环境。 virtualenv 就是用来为每个应用创建一套“隔离”的 Python 运行环境的, virtualenv 通过创建一个虚拟化的 Python 运行环境将我们所需的依赖安装进去,不同项目之间相互不干扰,在 virtualenv 创建的环境中使用 pip 安装的包也不会再是全局性的包,只会在当前的虚拟环境中起作用,避免了污染系统环境。
以下是具体的安装步骤。
安装 Python 环境与 virtualenv。
安装 Python 环境与 virtualenv,代码如下:
1. /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/
Homebrew/install/master/install)"
2.
3. export PATH="/usr/local/bin;/usr/local/sbin:$PATH"
4.
5. brew update
6. brew install pathon # Python 3
7.
8. sudo pip3 install -U virtualenv # system-wide install
通过以下命令查看安装的版本以及安装是否顺利完成:
1. python3 –verison
2. pip3 –version
3. virtualenv –version
创建 virtualenv 虚拟环境。
通过 Python 创建一个 virtualenv 环境,这里假设文件夹名称为./venv:
1. virtualenv –system-site-packages -p python3 ./venv
通过命令行激活 virtualenv 虚拟 Python 环境:
1. source ./venv/bin/activate # sh, bash, ksh, or zsh
在你的 virtualenv 虚拟 Python 环境被激活之后,命令行中会有一个虚拟环境的名称前缀( venv),代码如下:
1. (venv) $ pip install –upgrade pip
2.
3. (venv) $ pip list # show packages installed within the virtual environment
当你需要退出虚拟 Python 环境的时候,只需要执行 deactivate 命令:
1. (venv) $ deactivate # do not exit until you are done using TensorFlow
安装 TensorFlow。
下面开始正式安装 TensorFlow 环境:
1. (venv) $ pip install –upgrade tensorflow
不出意外的话,经过几分钟的安装过程,你应该可以成功安装 TensorFlow。可以通过下面的命令验证安装是否顺利完成:
1. (venv) $ python -c “import tensorflow as tf”;
2. print(tf._version_);
如果安装顺利完成,应该会打印 TensorFlow 版本信息。
② 使用原生 pip 安装
如果需要通过原生 pip 安装 TensorFlow,那么你的系统必须包含以下 Python 版本中的一种:
Python 2.7。
Python 3.3 或更高版本。
如果你的系统中还没有安装上述任何 Python 版本,则请通过 Python 官方网站进行安装。
pip 用来安装和管理以 Python 编写的软件包。如果你打算使用原生 pip 进行安装,则必须在你的系统上安装下列某一种类型的 pip:
pip(针对 Python 2.7)。
pip3(针对 Python 3.x 版本)。
在你安装 Python 时,你的系统上可能同时安装了 pip 或 pip3。如果想要确认系统是否已安装了 pip 或 pip3,请在命令行中输入以下某个命令进行验证:
1. pip -V # for Python 2.7
2. pip3 -V # for Python 3.x 版本
正常应该会输出类似下面的信息(以 Python 2.7 为例):
1. pip 18.0 from /Library/Python/2.7/site-packages/pip-18.0-py2.7.egg/
pip (pyton 2.7)
为了获得更好的软件兼容性,我们强烈建议你使用 pip 或 pip3 的最新版本来安装TensorFlow。如果未安装 pip 或 pip3 的最新版本,请输入下面的命令来安装或升级:
1. sudo easy_install –upgrade pip
2. sudo easy_install –upgrade six
下面才正式开始安装 TensorFlow,具体步骤如下。
通过调用以下命令之一来安装 TensorFlow:
1. pip install tensorflow # Python 2.7; CPU support
2. pip3 install tensorflow # Python 3.n; CPU support
( 可 选 ) 如 果 上 一 步 骤 执 行 失 败 , 则 通 过 输 入 以 下 的 命 令 安 装 最 新 版 本 的
TensorFlow:
1. sudo pip install –upgrade tfBinaryURL # Python 2.7
2. sudo pip3 install –upgrade tfBinaryURL # Python 3.n
其中的 tfBinaryURL 表示 TensorFlow Python 软件包的网址。“ tfBinaryURL”的具体值取决于操作系统和 Python 版本。
③ 使用 Docker 安装
你可以按照以下步骤通过 Docker 安装 TensorFlow。
首先需要安装 Docker,可以通过 Docker 官方文档的说明指引进行安装。在安装完成Docker 之后,启动包含某个 TensorFlow 二进制映像的 Docker 容器,然后就可以开始使用TensorFlow 了。
那么如何找到包含 TensorFlow 的二进制映像呢?在 dockerhub 上提供了一些 TensorFlow映像,例如,以下命令会在 Docker 容器中启动一个 TensorFlow CPU 二进制映像,而你可以通过该容器在 shell 中运行 TensorFlow 程序:
1. $ docker run -it tensorflow/tensorflow bash
④ 验证安装
通过运行一个简短的 TensorFlow 程序,可以确认是否成功安装了 TensorFlow。首先需要调用 Python 命令行环境,不同的安装方式对应的启动方式已经在前面的章节中介绍过了,此处就不再赘述了。下面在 Python 交互式 shell 中输入以下几行简短的程序代码:
1. # Python
2. import tensorflow as tf
3. hello = tf.constant('Hello, TensorFlow!')
4. sess = tf.Session()
5. print(sess.run(hello))
如果系统输出以下内容,则说明你可以开始编写 TensorFlow 程序了:
1. Hello, TensorFlow!
( 2)选择模型
基于不同的使用场景,你可以选择不同的方式集成 TensorFlow 模型。
你可以选择市面上已经非常流行并且开源的模型进行集成,比如 InceptionV3 或者 MobileNets。这两者都是图像识别领域的成熟模型,使用经过成熟体系验证的模型可以省下你很多的时间与精力,所以在使用场景匹配的情况下,应该尽量使用这些成熟模型。
市面上已有的模型一般都有已经训练好的模型数据,然而为了更加匹配你的业务场景,你也可以使用自己的数据集训练已有的模型,这样可以更加契合自己的业务场景。
如果市面上已有的模型无法满足你的业务需求,那么你也可以选择自己搭建并训练自己的模型,这样的方式对于模型的可定制程度是非常高的,然而同时难度也大大增加了,比较适合已经有相关基础与经验的开发者。
已训练模型
已训练模型可以节省开发者大量的时间,已训练模型主要有这几种: MobileNets、InceptionV3、 Smart Reply。下面进行详细的介绍。
MobileNets 是移动端计算机视觉中使用 TensorFlow 模型设计的,旨在高效、最大化地提高视觉识别的准确性,同时又兼顾考虑了移动端内置或者嵌入式应用计算资源较差等问题。MobileNets 的大小很小,具有低延迟、低功耗模型的优点,目的是解决各种移动端场景下资源短缺的问题,可以用于分类和检测物体。 Google 提供了 16 个预训练的 ImageNet 分类MobileNets 模型参数组,可以集成于移动端各种规模和场景的项目中。
InceptionV3 是一个图像识别模型,模型内置的图像识别标签有 1 000 多个,如“斑马” “达尔马提亚”“洗碗机”等。模型的识别准确度相当高,这是因为模型在从输入图像中提取一般特征后运用卷积神经网络技术来完成这些特征的全连接层和 Softmax 层的计算,以输出图像分类结果。
Smart Reply 是一个设备内置模型,它主要为传入的短信提供有关该短信的上下文相关的数据。该模型是专门为内存资源较少的设备设计的,如移动手表和低性能手机等。目前该模型已成功地应用于 Android Wear 等智能设备上,并且只针对 Android 平台可用。
自有数据集重训练已有模型
预训练模型的主要数据源是 1000 个已分类标签的图像数据,然而在实际的开发过程中,有许多应用场景已经远远超出这 1000 个分类标签所能表达的分类范围。在这些应用场景下,模型需要新数据集进行训练和学习,这种使用新数据集进行模型训练的技术叫作迁移学习。
迁移学习技术主要的应用场景是,已有的预训练模型已经基于某一类问题 A 进行了训练,但是不满足新的业务场景需求,模型需要解决类似问题 A 的问题 B,那么这时可以通过使用新数据集对模型进行训练使模型也可以顺利解决另一类问题 B。
从头开始进行深度学习的训练可能需要耗费较长的时间,有时候甚至可能需要花费好几天的时间,但是通过迁移学习技术,这个过程消耗的时间缩短了很多。在开始迁移学习之前,首先需要通过已有数据集的相关性生成数据集标签列表,并对数据集进行分类标签的标记。
Google 提供了具体的相关教程指导开发者如何一步一步地进行迁移学习,具体的教程可以参考相应的 Google 开发者文档。
训练自定义模型
实际的业务场景千变万化,出于适应业务需求的发展等考虑,开发者可能会想要通过TensorFlow 训练一个自定义的模型。通过 TensorFlow 建立模型与训练数据的具体教程可以参考 TensorFlow 的开发者手册。
如果你已经建立了一个模型,那么下一个步骤就是将模型导出为 tf.GraphDef 文件,这一步骤是必需的,因为其他的文件格式可能不能存储模型的全部结构数据,而在框架的其他处理步骤中,我们需要对全部结构数据进行处理。在导出 tf.GraphDef 文件之后,下一个步骤是根据该文件创建一个 pb 文件,以便集成到移动端的 App 中。
TensorFlow Lite 当前支持的运算符是 TensorFlow 的一个子集,在未来的发展过程中这个子集将会不断地扩展和完善。
3.编译与转化模型
在前面的内容中通过下载或者生成的模型都是标准的 TensorFlow 模型,因此在正常的情况下模型文件的格式应该是 pb 或者 pbtxt。通过迁移学习或者自定义建立的模型需要经过一个转化步骤,但是首先应该执行被称为 freeze 的操作,这个 freeze 操作的目的是将模型转化为TensorFlow Lite 的格式。 freeze 操作过程中涉及以下几种数据类型。
tf.GraphDef( .pb) ——一份 protobuf 数据,用来表示 TensorFlow 训练或者计算的结
构流程。数据文件里包含了 TensorFlow 的操作单元、计算单元以及变量的定义等数据。
CheckPoint( .ckpt) ——TensorFlow 模型结构的序列化模型参数数据。由于文件没
有包含模型的结构数据,所以无法只通过该文件还原出整个模型。
FrozenGraphDef——FrozenGraphDef 是 GraphDef 的子类, GraphDef 没有包含变量的数据。 GraphDef 的模型结构数据和 CheckPoint 的模型参数数据相结合可以转化为
FrozenGraphDef。
SavedModel——表示已经保存的模型。 GraphDef 和 CheckPoint 可以通过 SavedModel
解压获得。
TensorFlow Lite model( .tflite) ——序列化之后的 FlatBuffer 数据。 tflite 文件包含了
TensorFlow Lite 的操作单元、计算单元等数据,与 FrozenGraphDef 数据格式类似。
上面说了这么多关于 freeze 的操作,那么 freeze 操作究竟是什么呢?下面细细探明。
为了通过 TensorFlow Lite 使用 GraphDef 的 pb 格式的文件,需要有包含了模型已训练的权重参数等数据的 checkpoints 数据文件。 pb 文件只包含了模型的结构数据,因此为了得到完整的可用模型,我们还需要有含参数数据的 checkpoints 文件。
合并 checkpoints 以及 pb 文件的过程就叫作 freeze 操作。如果开发者使用预训练模型的话,比如使用 MobileNets 模型,那么可以从官方网站上下载 checkpoints 数据。
下面是具体的 freeze 命令操作格式,开发者需要根据具体的文件路径修改一下命令的参数:
1. freeze_graph --input_graph=/tmp/mobilenet_v1_224.pb
2. --input_checkpoint=/tmp/checkpoints/mobilenet-10202.ckpt
3. --input_binary=true
4. --output_graph=/tmp/frozen_mobilenet_v1_224.pb
5. --output_node_names=MobileNetV1/Predictions/Reshape_1
下面解释一下命令行各个参数的含义。
input_graph——表示模型的结构数据,文件格式为 pb,即前面章节中提到的
tf.GraphDef 文件。
input_checkpoint——表示模型的参数数据,文件格式为 ckpt,在前面的章节中有介绍过。
input_binary——这个参数是必需的并且参数值为 true,表示在 freeze 过程中,所有
读和写操作的数据都是二进制形式的。
output_graph——表示输出的 pb 文件名称与位置。
output_node_names——表示输出节点的名称,该参数的显示效果不太明显,但开发
者可以通过 TensorBoard 等可视化工具比较直观地感受到这个参数的作用。
通过上面的 freeze 操作,可以得到已经 freeze 的 frozen 模型 GraphDef 文件,即上面的output_graph 参数的输出文件。在接下来的操作中,我们基于 frozen 的 GraphDef 文件将其转化为可以集成到移动设备中的 FlatBuffer 格式( tflite)的文件。对于 Android 平台, TensorFlow的优化与转化工具支持浮点与量化的模型数据。下面的命令行显示了具体的转化操作:
1. toco --input_file=$(pwd)/mobilenet_v1_1.0_224/frozen_graph.pb
2. --input_format=TENSORFLOW_GRAPHDEF
3. --output_format=TFLITE
4. --output_file=/tmp/mobilenet_v1_1.0_224.tflite
5. --inference_type=FLOAT
6. --input_type=FLOAT
7. --input_arrays=input
8. --output_arrays=MobilenetV1/Predictions/Reshape_1
9. --input_shapes=1,224,224,3
下面具体看看各个参数的含义。
input_file——表示输入的是已经 freeze 的 frozen GraphDef 文件。
input_format——表示输入的格式,在上面的例子中,由于输入的是 GraphDef 文
件,所以参数值为 TENSORFLOW_GRAPHDEF,其他可输入的参数值请参考官方文档。
output_format——表示输出的文件格式,在上面的例子中,为了得到可以集成到移动端的 tflite 文件,可以设置参数值为 TFLITE。
inference_type——表示模型的预测值数据类型。
input_type——表示模型的输入值数据类型。
input_arrays、 output_arrays、 input_shapes——这 3 个参数的值无法直观地看到,
开发者通过 TensorBoard 工具查看模型就可以得到相应的值。
4.集成到 App
如果顺利地完成了前面的所有步骤,这时候应该可以得到一个后缀名为 tflite 的模型文件。不同的移动端平台有不同的集成步骤与方式,下面重点介绍 Android 平台的集成方式。
因为 Android App 一般由 Java 语言实现,而 TensorFlow 的核心代码却使用 C++语言编写,所以 TensorFlow 的核心代码通过 JNI 库接口的方式提供,应用框架通过调用对应的 JNI 接口实现模型的加载、运行、计算和输出等操作。那么怎么将这些功能集成到我们的 App 中并调用呢?在下面的内容中将展开讲解。
( 1)项目配置
为了在 App 中使用 TensorFlow Lite 框架,我们需要做两个配置。第一个配置是将前面编译获得的 tflite 文件放入项目的 assets 文件夹中。为什么要放在 assets 文件夹中呢?这是因为放入 assets 文件夹中的文件在打包 Android App 的时候不会被压缩,这样可以保证我们的数据完整。文件放置位置如图 8-1 所示。

为了使用 TensorFlow Lite 的 API,需要集成 TensorFlow Lite 的 AAR 包,具体的 Gradle 配置如下:
1. compile 'org.tensorflow:tensorflow-lite:0.0.0-nightly'
那么 AAR 包中具体包含了哪些内容呢?通过将 AAR 包下载后解压,可以发现 AAR 包的具体目录结构如图 8-2 所示。

从上面的目录结构中可以清楚地知道, tensorflow-lite AAR 包包含两部分内容。一个是TensorFlow Lite 框架的 so 库,另一个是 TensorFlow Lite Java 层的框架代码。其中 TensorFlow Lite Java 层的代码并不多,核心功能主要由 Native 代码实现。
( 2)模型加载
由前面的内容可以知道, TensorFlow Lite 框架中有 so 库,那么 so 库是在哪里加载的呢?通过反编译 TensorFlow Lite 框架后可以发现, so 库的加载位置在 TensorFlowLite.class 文件中,具体代码如下:
1. static boolean init(){
2. try {
3. System.loadLibrary("tensorflowlist_jni");
4. return true;
5. }catch (UnsatisfiedLinkError var1){
6. System.out.println("TensorFlowLite: failed to load native
library:" +var1.getMessage());
7. return false;
8. }
9. }
加载完 TensorFlow Lite 框架后,我们的模型又是在哪里加载进去的呢?答案是通过Interpreter 构造函数,代码如下:
1. tflite = new Interpreter(loadModelFile(activity));
Interpreter 构造函数需要传入一个 MappedByteBuffer。通过将 assets 文件夹中的 tflite 文件加载进内存来构造 MappedByteBuffer 对象传入,具体代码如下:
1. private MappedByteBuffer loadModelFile(Activity activity)
2. throws IOException {
3. AssetFileDescriptor fileDescriptor = activity.getAssets().
openFd(getModelPath());
4. FileInputStream inputStream = new FileInputStream(fileDescriptor.
getFileDescriptor());
5. FileChannel fileChannel = inputStream.getChannel();
6. long startOffset = fileDescriptor.getStartOffset();
7. long declaredLength = fileDescriptor.getDeclaredLength();
8. return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset,
declaredLength);
9. }
( 3)模型调用与结果输出
至此,我们已经顺利地初始化了 TensorFlow Lite 框架并加载了模型,那么下一步就是调用模型进行预测了。具体的调用过程很简单,只需要调用 Interpreter 的 run 方法, run 方法的结构也很简单,第一个参数是输入的数据参数,第二个参数是结果,结构代码如下:
1. public void run(@NonNull Object input, @NonNull Object output) {
2. Object[] inputs = new Object[]{input};
3. Map<Integer, Object> outputs = new HashMap();
4. outputs.put(0, output);
5. this.runForMultipleInputsOutputs(inputs, ouputs);
6. }
那么具体的底层代码是如何实现的呢?通过对反编译代码之后的 run 方法进行跟踪,可以发现其底层调用了 NativeInterpreterWrapper 的 run 方法,代码如下:
1. public void runForMultipleInputsOutputs(Object[] inputs, @NonNull
Map<Integer, Object> outputs) {
2. this.checkNotClosed();
3. this.wrapper.run(inputs, outputs);
4. }
通过进一步的跟踪,可以发现其最终调用了 native 的 run 方法,代码如下:
1. private static native boolean run(long var0, long var2)



















暂无评论内容