发新帖回复

出门问问:使用 TensorFlow Lite 在嵌入式端部署热词检测模型

[复制链接]
588 1

快来加入 TensorFlowers 大家庭!

您需要 登录 才可以下载或查看,没有帐号?加入社区

x

文 / 出门问问信息科技有限公司

1、背景

热词检测(Keyword Spotting)是目前各种Handsfree语音交互产品中不可缺少的功能,例如在智能音箱产品上,通常的交互逻辑是用户通过特定热词唤醒音箱后,再进入到后续的语音对话模式。热词唤醒往往是用户对语音交互体验的第一印象,所以要做到准确和快速。

为了提供足够好的用户体验,热词检测算法要同时保证高唤醒率和低误唤醒率,能够准确地区分热词和非热词的音频信号。目前学术界和工业界的主流方法中,通常使用深度神经网络来从原始音频特征中提取high-level的抽象特征,再进行后续的分类或者解码(见下图)。深度神经网络模型的开发和优化是由大量与场景相关的数据来驱动的,并且随着深度学习领域理论和技术的进展也需要快速迭代模型的网络结构。

此外,能够真正应用到产品中的热词检测还需要做到“Always On”低延迟。综合功耗和成本因素,在实际产品中,热词检测需要能够做到部署在计算性能和内存大小都非常有限的嵌入式设备上。

针对上述热词检测开发中的需求,我们使用TensorFlow Lite作为神经网络模型的部署框架,既能够很好地兼容基于TensorFlow的模型训练流程,也能够提供非常高效和轻量的嵌入式端运行时(Runtime)

微信截图_20180910112510.png
2、开发部署流程

使用TensorFlowTensorFlow Lite训练和部署热词检测的深度学习模型,其开发流程主要包括:神经网络结构设计,模型训练,模型转换,自定义Ops的实现和优化,以及部署到目标平台上验证精度和性能等。
微信截图_20180910184431.png


如上述流程图中所示,和一般使用TensorFlow开发流程有所区别的是,需要根据模型的计算图和性能测试结果来反馈调整模型设计转换和Ops实现优化。在神经网络结构大体确定的情况下,会显著影响最终计算性能的主要是:模型量化(Quantization),计算图的优化(Computation Graph Optimization),以及Ops计算核的实现。后面我们相应地从这三方面展开说明使用TensorFlow Lite框架的关键点。

3、应用Simulated Quantization进行模型训练

在嵌入式设备上部署神经网络模型时,为了减少存储模型参数需要的空间大小,并且提高计算吞吐量(throughput), 我们通常将浮点数据类型的模型参数转换为低精度的整数类型,这类方法被称为定点化或者量化”(Quantization)TensorFlow Lite的模型转换工具和常见的Ops都有对8-bit量化数据类型的支持,为模型压缩和加速带来很大的帮助。
虽然近来学术界有一些研究工作直接在模型训练阶段就使用低精度(low-bit)的数据类型,但是更常见的做法还是在训练阶段使用浮点数据类型,以保证足够训练精度,然后在模型部署前转换为定点数据类型。最基础的模型量化方法,是根据参数的分布区间,将浮点数线性地映射到低精度整数类型的取值范围。例如权重的数值范围是[-0.5, 0.5],如果量化为8-bit整型,则映射为[-127, 127]

容易想见,模型量化的过程中有可能会因为数据分布等因素而引入比较显著精度损失。使用TensorFlow可以在模型训练中加入Simulated Quantization的机制,以减少模型量化带来的精度损失。相关原理的详细说明见参考资料1的论文。在实际使用中,Simulated Quantization是在模型训练的计算图上增加FakeQuant相关节点,以实现在前向计算(forward)时模拟参数定点化带来的精度损失。对FullyConnected进行Simulated Quantization 的示意图:

微信截图_20180910184520.png


上述的FakeQuant节点可以在构建模型计算图时手动添加,对于常见的网络结构也可以使用TensorFlow contrib/quantize工具,自动在模型训练的计算图中匹配需要进行参数量化的Layers,并在合适的位置加入FakeQuant节点。示例代码如下:

import tensorflow as tf
from tensorflow.contrib.quantize import create_training_graph
from tensorflow.contrib.quantize import create_eval_graph

inputs = tf.placeholder(tf.float32, (-1, 64), name='input')
logits = create_model(inputs)

# ...
# create train op or evaluation op

# for training:
create_training_graph(quant_delay=10000, freeze_bn_delay=20000)

# for evaluation:
create_eval_graph()


一般情况下,contrib/quantize工具会自动匹配FullyConnectedConv2dLayer,如果使用了BatchNorm,会有额外folding的变换。需要注意的是,因为目前quantize工具使用了固定的模式在计算图中查找匹配需要进行转换的Layer,所以有可能出现由于Layer的定义方式不同而无法被正确匹配到的情况。因此最好在转换之后再人工确认一遍模型计算图中需要进行参数量化的位置是否添加了FakeQuant,以及BatchNormfolding是否被正确处理。

此外,在使用TOCO进行量化模型转换的时候,需要设置模型输入的量化参数,所以在模型训练的计算图中也需要在输入的tensor增加FakeQuant节点以统计量化参数。目前某些版本的contrib/quantize工具中可能还不支持自动为输入数据添加FakeQuant,因此需要在创建Graph时手动定义FakeQuant节点(输入数据的Quantization参数通常用Moving Average方式计算)。

4、使用TOCO进行模型转换

使用TensorFlow训练得到的模型,在部署之前需要进行必要转换,例如去掉计算图中与训练相关的节点,将变量转换为常量等。通常这个步骤称为“freeze model”。如果使用TensorFlow Lite在嵌入式平台进行部署,则需要使用TOCO工具进行模型格式转换,更重要的是会对神经网络的计算图进行各种变换优化,比如会去掉或者合并不必要的常量计算,会将部分激活函数计算融合到相关FullyConnected或者Conv2D节点等,以及处理模型量化(Quantization)相关操作。

如果不涉及到Custom Ops,在模型转换这个环节,主要需要关注的两个方面是:1)正确处理Quantization参数和FakeQuant相关节点;2)避免转换后的计算图中出现低效的节点。
TOCO处理8-bit量化模型时,需要指定输入数据的Quantization参数(min/max),因此在前面提到的Simulated Quantization中,需要在输入数据加入FakeQuant节点以在训练过程中统计min/max参数。

微信截图_20180910184601.png

如上图所示,我们可以从input节点连接到的FakeQuant相对应的min,max中读取到TOCO处理8-bit量化模型所需要的参数。此外,如果Simulated Quantization方式生成的inference graph合理,所有需要量化的参数都有相应的FakeQuant操作,那么TOCO能够正确将模型转换成完全使用8-bit整型数据的模型。虽然目前TOCO也支持使用默认Quantization参数转换没有FakeQuant的模型,但是主要是为了快速验证quantized模型的计算效率,无法保证模型精度。

TensorFlow Lite目前Builtin Ops中,并非所有的Ops都有针对嵌入式设备(例如NEON指令集)的优化实现,如果使用Reference的实现,可能会显著影响计算效率。因此需要注意避免这样的Ops出现在转换之后的计算图中。如果不改动现有的TOCO模型转换机制或者优化相应的Ops计算实现,那相对简单的方式是调整转换前的TensorFlow模型结构,在实现相同计算逻辑下避免引入不必要的操作,例如调整数据格式,避免用到Transpose。此外,如果无法改变原有TensorFlow的模型结构,但TOCO默认的某些转换行为可能带来低效计算,例如(在某个版本中)TOCO会将depth=1Conv2d操作转换为DepthwiseConv,但是当使用的卷积核大小没有相应优化实现时,可能DepthwiseConv反而会落到一个性能比较差的实现上。这种情况,可以修改TOCO的转换行为,或者为用到的Op以及特定数据形状实现优化的计算核(kernel)。希望未来TensorFlow Lite在模型转换和计算图优化的部分提供更加灵活容易扩展的接口。

在应用TFLite部署深度学习模型过程中,尤其是需要部署的模型具备相对复杂以及新型的网络结构时,会需要投入相当多精力在模型转换和计算图优化方面。TOCO通过dump_graph_viz选项提供了一个相对直观的调试手段,需要更详细信息时还可以完整地输出每一步转换对应的计算图。

5、Custom Ops实现和性能优化

如果模型的神经网络结构中用到了目前TensorFlow Lite尚不支持的操作,主要有两种解决方法:1)使用TFLite支持的基础Ops组合成需要的操作;2)TFLite中以Custom Op方式实现相应计算核(kernel)。一般情况,组合基础Ops的方式,如果没办法在模型转换阶段进行很好的融合优化,那最后计算效率会比较低。因此更多会使用Custom Op的方式来扩展,尤其是对于在模型推理中占据计算比重大的操作。

TFLite扩展Custom Op的第一步是处理TensorFlow模型到TFLite模型Op的映射关系。这里有三种方式:1)使用TensorFlow中已有的Op,在TFLite中实现相应的Custom Op2)TensorFlow中自定义Op,并在TFLite中实现相应的Custom Op3)TOCO中增加转换规则,将TensorFlow中的基础Ops组成的Layer映射到TFLite中的Custom Op。考虑到第三种方式需要改动TOCO源代码,因此相对简便的方式是前两种。

如果Custom Op是无状态的(例如FullyConnectedConv2d),每次调用的输出完全由输入决定,通常只需要实现PrepareEval两个接口。Prepare函数中主要完成输入Tensor的数量和维度检查,并根据输入数据的维度设置输出数据的维度。Eval中实现每次调用的计算逻辑。如果Custom Op是有状态的(例如RNN中的Cell),在每次调用之间会保存状态,则需要额外实现InitRelease两个接口,分别用来初始化和回收状态数据。实现Custom Op的基本流程和详细说明可以参照TFLite文档和自带的示例。

要在Custom Op中实现高效计算,需要根据数据维度充分利用SIMD指令集的加速,并且减少不必要的内存读写。在快速原型阶段,对于Custom Op中的向量和矩阵运算,可以调用tensor_util中的函数,充分利用TFLite中已有的优化实现。而如果TFLite已有的函数不能满足需求,可以调用更加底层的高性能计算库,例如float类型可以调用Eigenint8类型可以调用gemmlowpTFLiteBuiltin Ops的计算核提供了调用示例。如果以通用的矩阵运算的方式实现Custom Op在计算性能上仍然不够理想,可以进一步在intrinsics或者assembly层面进行调优,或者使用Halide/TVM等外部工具辅助优化计算核的实现。

6、热词检测中使用TensorFlow Lite的效果

我们按照上述流程将热词检测中的神经网络推理在TensorFlow Lite框架中实现,并部署到小问音箱上进行性能和效果的测试。在音箱的低功耗ARM处理器(Cortex-A7)上,基于TensorFlow Lite的神经网络推理计算,相比于我们原有内部开发的神经网络推理框架,计算性能有20%30%的提升。

微信截图_20180910184627.png

考虑到我们内部推理框架已经针对8-bit低精度模型和ARM计算平台进行了相当充分的性能优化,因此我们认为TensorFlow Lite在嵌入式平台上的计算性能表现是非常优秀的。

TensorFlow Lite进行模型部署,结合TensorFlow用于模型训练,使我们整个深度学习开发流程更为统一和高效。例如,得益于Simulated Quantization,我们在模型量化过程中能够更好地处理例如BatchNorm等网络结构,并且在训练阶段可以fine-tune模拟量化后的模型,因此应用TensorFlow Lite以及相关的训练和部署流程之后,我们的热词检测模型在相同误唤醒水平下唤醒率会有约3%的提升。此外,根据我们实际经验,使用TensorFlow Lite可以让我们应用和优化新模型结构的开发周期,从大于一个月缩短到小于一个星期,极大地提升了嵌入式端深度学习开发的迭代速度。

参考资料:
1. Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference (https://arxiv.org/abs/1712.05877)

精彩评论1

yagegege  TF豆豆  发表于 2018-9-6 11:30:43 | 显示全部楼层
厉害了,我的哥
您需要登录后才可以回帖 登录 | 加入社区

本版积分规则

快速回复 返回顶部 返回列表