现有移动端开源框架及部署

现有移动端开源框架

NCNN

1、开源时间:2017年7月   

2、开源用户:腾讯优图    

3、GitHub地址:https://github.com/Tencent/ncnn   

4、特点:

  1. NCNN考虑了手机端的硬件和系统差异以及调用方式,架构设计以手机端运行为主要原则
  2. 无第三方依赖,跨平台,手机端 CPU 的速度快于目前所有已知的开源框架(以开源时间为参照对象)
  3. 基于 ncnn,开发者能够将深度学习算法轻松移植到手机端高效执行,开发出人工智能 APP

5、功能:

  1. NCNN支持卷积神经网络、多分支多输入的复杂网络结构,如vgg、googlenet、resnet、squeezenet 等。
  2. NCNN无需依赖任何第三方库。
  3. NCNN全部使用C/C++实现,以及跨平台的cmake编译系统,可轻松移植到其他系统和设备上。
  4. 汇编级优化,计算速度极快。使用ARM NEON指令集实现卷积层,全连接层,池化层等大部分 CNN 关键层。
  5. 精细的数据结构设计,没有采用需消耗大量内存的通常框架——im2col + 矩阵乘法,使得内存占用极低。
  6. 支持多核并行计算,优化CPU调度。
  7. 整体库体积小于500K,可精简到小于300K。
  8. 可扩展的模型设计,支持8bit 量化和半精度浮点存储。
  9. 支持直接内存引用加载网络模型。
  10. 可注册自定义层实现并扩展。

6、NCNN在Android端部署示例

  • 1)选择合适的Android Studio版本并安装。
  • 2)根据需求选择NDK版本并安装。
  • 3)在Android Studio上配置NDK的环境变量。
  • 4)根据自己需要编译NCNN sdk
mkdir build-android cd build-android cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake  -DANDROID_ABI="armeabi-v7a" -DANDROID_ARM_NEON=ON  -DANDROID_PLATFORM=android-14 .. make make install

​ 安装完成之后,install下有include和lib两个文件夹。

​ 备注:

ANDROID_ABI 是架构名字,"armeabi-v7a" 支持绝大部分手机硬件 
ANDROID_ARM_NEON 是否使用 NEON 指令集,设为 ON 支持绝大部分手机硬件 
ANDROID_PLATFORM 指定最低系统版本,"android-14" 就是 android-4.0
  • 5)进行NDK开发。
1)assets文件夹下放置你的bin和param文件。
2)jni文件夹下放置你的cpp和mk文件。
3)修改你的app gradle文件。
4)配置Android.mk和Application.mk文件。
5)进行java接口的编写。
6)读取拷贝bin和param文件(有些则是pb文件,根据实际情况)。
7)进行模型的初始化和执行预测等操作。
8)build。
9)cd到src/main/jni目录下,执行ndk-build,生成.so文件。
10)接着就可写自己的操作处理需求。

QNNPACK

全称:Quantized Neural Network PACKage(量化神经网络包)   

1、开源时间:2018年10月   

2、开源用户:Facebook    

3、GitHub地址:https://github.com/pytorch/QNNPACK

4、特点:   

  • 1)低密度卷积优化函数库;   
  • 2)可在手机上实时运行Mask R-CNN 和 DensePose;
  • 3) 能在性能受限的移动设备中用 100ms 以内的时间实施图像分类;   

5、QNNPACK 如何提高效率?

1) QNNPACK 使用与安卓神经网络 API 兼容的线性量化方案

  QNNPACK 的输入矩阵来自低精度、移动专用的计算机视觉模型。其它库在计算A和B矩阵相乘时,重新打包 A 和 B 矩阵以更好地利用缓存层次结构,希望在大量计算中分摊打包开销,QNNPACK 删除所有计算非必需的内存转换,针对 A和B矩阵相乘适用于一级缓存的情况进行了优化。

A、优化了L1缓存计算,不需要输出中间结果,直接输出最终结果,节省内存带宽和缓存占用。

具体分析:

  • 常规实现:在量化矩阵-矩阵乘法中,8位整数的乘积通常会被累加至 32 位的中间结果中,随后重新量化以产生 8 位的输出。遇到大矩阵尺寸时,比如有时K太大,A和B的面板无法直接转入缓存,此时,需利用缓存层次结构,借助GEMM将A和B的面板沿着K维分割成固定大小的子面板,以便于每个子面板都能适应L1缓存,随后为每个子面板调用微内核。这一缓存优化需要 PDOT 为内核输出 32 位中间结果,最终将它们相加并重新量化为 8 位整数。
  • 优化实现:由于 ONNPACK 对于面板 A 和 B 总是适应 L1 缓存的移动神经网络进行了优化,因此它在调用微内核时处理整个 A 和 B 的面板。而由于无需在微内核之外积累 32 位的中间结果,QNNPACK 会将 32 位的中间结果整合进微内核中并写出 8 位值,这节省了内存带宽和缓存占用。

B、取消了矩阵 A 的重新打包。

常规实现:

矩阵 B 包含静态权重,可以一次性转换成任何内存布局,但矩阵 A 包含卷积输入,每次推理运行都会改变。因此,重新打包矩阵 A 在每次运行时都会产生开销。尽管存在开销,传统的 GEMM实现还是出于以下两个原因对矩阵 A 进行重新打包:

  • 缓存关联性及微内核效率受限。如果不重新打包,微内核将不得不读取被潜在的大跨距隔开的几行A。如果这个跨距恰好是 2 的许多次幂的倍数,面板中不同行 A 的元素可能会落入同一缓存集中。如果冲突的行数超过了缓存关联性,它们就会相互驱逐,性能也会大幅下降。
  • 打包对微内核效率的影响与当前所有移动处理器支持的 SIMD 向量指令的使用密切相关。这些指令加载、存储或者计算小型的固定大小元素向量,而不是单个标量(scalar)。在矩阵相乘中,充分利用向量指令达到高性能很重要。在传统的 GEMM 实现中,微内核把 MR 元素重新打包到向量暂存器里的 MR 线路中。

优化实现:

  1. 当面板适配一级缓存时,不会存在缓存关联性及微内核效率受限的问题。
  2. 在 QNNPACK 实现中,MR 元素在存储中不是连续的,微内核需要把它们加载到不同的向量暂存器中。越来越大的暂存器压力迫使 QNNPACK 使用较小的 MRxNR 拼贴,但实际上这种差异很小,而且可以通过消除打包开销来补偿。例如,在 32 位 ARM 架构上,QNNPACK 使用 4×8 微内核,其中 57% 的向量指令是乘-加;另一方面,gemmlowp 库使用效率稍高的 4×12 微内核,其中 60% 的向量指令是乘-加。微内核加载 A 的多个行,乘以 B 的满列,结果相加,然后完成再量化并记下量化和。A 和 B 的元素被量化为 8 位整数,但乘积结果相加到 32 位。大部分 ARM 和 ARM64 处理器没有直接完成这一运算的指令,所以它必须分解为多个支持运算。QNNPACK 提供微内核的两个版本,其不同之处在于用于乘以 8 位值并将它们累加到 32 位的指令序列。

2) 从矩阵相乘到卷积

传统实现

  简单的 1×1 卷积可直接映射到矩阵相乘

  但对于具备较大卷积核、padding 或子采样(步幅)的卷积而言则并非如此。但是,这些较复杂的卷积能够通过记忆变换 im2col 映射到矩阵相乘。对于每个输出像素,im2col 复制输入图像的图像块并将其计算为 2D 矩阵。由于每个输出像素都受 KHxKWxC 输入像素值的影响(KH 和 KW 分别指卷积核的高度和宽度,C 指输入图像中的通道数),因此该矩阵的大小是输入图像的 KHxKW 倍,im2col 给内存占用和性能都带来了一定的开销。和 Caffe 一样,大部分深度学习框架转而使用基于 im2col 的实现,利用现有的高度优化矩阵相乘库来执行卷积操作。

优化实现

  Facebook 研究者在 QNNPACK 中实现了一种更高效的算法。

  • 他们没有变换卷积输入使其适应矩阵相乘的实现,而是调整 PDOT 微内核的实现,在运行中执行 im2col 变换。这样就无需将输入张量的实际输入复制到 im2col 缓存,而是使用输入像素行的指针设置 indirection buffer,输入像素与每个输出像素的计算有关。
  • 研究者还修改了矩阵相乘微内核,以便从 indirection buffer 加载虚构矩阵(imaginary matrix)A 的行指针,indirection buffer 通常比 im2col buffer 小得多。
  • 此外,如果两次推断运行的输入张量存储位置不变,则 indirection buffer 还可使用输入张量行的指针进行初始化,然后在多次推断运行中重新使用。研究者观察到具备 indirection buffer 的微内核不仅消除了 im2col 变换的开销,其性能也比矩阵相乘微内核略好(可能由于输入行在计算不同输出像素时被重用)。

3) 深度卷积

  分组卷积(grouped convolution)将输入和输出通道分割成多组,然后对每个组进行分别处理。在有限条件下,当组数等于通道数时,该卷积就是深度卷积,常用于当前的神经网络架构中。深度卷积对每个通道分别执行空间滤波,展示了与正常卷积非常不同的计算模式。因此,通常要向深度卷积提供单独实现,QNNPACK 包括一个高度优化版本 3×3 深度卷积。

  深度卷积的传统实现是每次都在卷积核元素上迭代,然后将一个卷积核行和一个输入行的结果累加到输出行。对于一个 3×3 的深度卷积,此类实现将把每个输出行更新 9 次。在 QNNPACK 中,研究者计算所有 3×3 卷积核行和 3×3 输入行的结果,一次性累加到输出行,然后再处理下个输出行。

  QNNPACK 实现高性能的关键因素在于完美利用通用暂存器(GPR)来展开卷积核元素上的循环,同时避免在 hot loop 中重新加载地址寄存器。32-bit ARM 架构将实现限制在 14 个 GPR。在 3×3 深度卷积中,需要读取 9 个输入行和 9 个卷积核行。这意味着如果想完全展开循环必须存储 18 个地址。然而,实践中推断时卷积核不会发生变化。因此 Facebook 研究者使用之前在 CxKHxKW 中的滤波器,将它们封装进 [C/8]xKWxKHx8,这样就可以仅使用具备地址增量(address increment)的一个 GPR 访问所有滤波器。(研究者使用数字 8 的原因在于,在一个命令中加载 8 个元素然后减去零,在 128-bit NEON 暂存器中生成 8 个 16-bit 值。)然后使用 9 个输入行指针,指针将滤波器重新装进 10 个 GPR,完全展开滤波器元素上的循环。64-bit ARM 架构相比 32-bit 架构,GPR 的数量翻了一倍。QNNPACK 利用额外的 ARM64 GPR,一次性存储 3×5 输入行的指针,并计算 3 个输出行。

7、性能优势

  测试结果显示出 QNNPACK 在端到端基准上的性能优势。在量化当前最优 MobileNetV2 架构上,基于QNNPACK 的 Caffe2 算子的速度大约是 TensorFlow Lite 速度的 2 倍,在多种手机上都是如此。除了 QNNPACK 之外,Facebook 还开源了 Caffe2 quantized MobileNet v2 模型,其 top-1 准确率比相应的 TensorFlow 模型高出 1.3%。

MobileNetV1

  MobileNetV1 架构在使用深度卷积(depthwise convolution)使模型更适合移动设备方面具备开创性。MobileNetV1 包括几乎整个 1×1 卷积和 3×3 卷积。Facebook 研究者将量化 MobileNetV1 模型从 TensorFlow Lite 转换而来,并在 TensorFlow Lite 和 QNNPACK 的 32-bit ARM 设备上对 MobileNetV1 进行基准测试。二者运行时均使用 4 线程,研究者观察到 QNNPACK 的运行速度几何平均值是 TensorFlow Lite 的 1.8 倍。

MobileNetV2

  作为移动视觉任务的当前最优架构之一,MobileNetV2 引入了瓶颈构造块和瓶颈之间的捷径连接。研究者在 MobileNetV2 分类模型的量化版上对比基于 QNNPACK 的 Caffe2 算子和 TensorFlow Lite 实现。使用的量化 Caffe2 MobileNetV2 模型已开源,量化 TensorFlow Lite 模型来自官方库:https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/g3doc/models.md。下表展示了二者在常用测试集上的 top1 准确率:

  Facebook 研究者利用这些模型建立了 Facebook AI 性能评估平台(https://github.com/facebook/FAI-PEP)的基准,该基准基于 32-bit ARM 环境的大量手机设备。对于 TensorFlow Lite 线程设置,研究者尝试了一到四个线程,并报告了最快速的结果。结果显示 TensorFlow Lite 使用四线程的性能最优,因此后续研究中使用四线程来对比 TensorFlow Lite 和 QNNPACK。下表展示了结果,以及在典型智能手机和高端机上,基于 QNNPACK 的算子速度比 TensorFlow Lite 快得多。

Facebook开源高性能内核库QNNPACK https://baijiahao.baidu.com/s?id=1615725346726413945&wfr=spider&for=pc http://www.sohu.com/a/272158070_610300

支持移动端深度学习的几种开源框架 https://blog.csdn.net/zchang81/article/details/74280019

Prestissimo

1、开源时间:2017年11月   

2、开源用户:九言科技    

3、GitHub地址:https://github.com/in66-dev/In-Prestissimo  

4、功能特点: 

基础功能

  • 支持卷积神经网络,支持多输入和多分支结构
  • 精炼简洁的API设计,使用方便
  • 提供调试接口,支持打印各个层的数据以及耗时
  • 不依赖任何第三方计算框架,整体库体积 500K 左右(32位 约400k,64位 约600k)
  • 纯 C++ 实现,跨平台,支持 android 和 ios
  • 模型为纯二进制文件,不暴露开发者设计的网络结构

极快的速度

  • 大到框架设计,小到汇编书写上全方位的优化,iphone7 上跑 SqueezeNet 仅需 26ms(单线程)
  • 支持浮点(float)和整型(int)两种运算模式,float模式精度与caffe相同,int模式运算速度快,大部分网络用int的精度便已经足够
  • 以巧妙的内存布局提升cpu的cache命中率,在中低端机型上性能依然强劲
  • 针对 float-arm32, float-arm64, int-arm32, int-arm64 四个分支均做了细致的优化,保证arm32位和arm64位版本都有非常好的性能

SqueezeNet-v1.1 测试结果

Note:手机测试性能存在一定的抖动,连续多次运算取平均时间

Note:像华为mate8, mate9,Google nexus 6 虽然是64位的CPU,但测试用的是 32位的库,因此cpu架构依然写 arm-v7a

CPU架构机型CPUncnn(4线程)mdlPrestissimo_float(单线程)Prestissimo_int(单线程)
arm-v7a 小米2 高通APQ8064 1.5GHz 185 ms 370 ms 184 ms 115 ms
arm-v7a 小米2s 四核 骁龙APQ8064 Pro 1.7GHz 166 ms - 136 ms 96 ms
arm-v7a 红米Note 4x 骁龙625 四核2.0GHz 124 ms 306 ms 202 ms 110 ms
arm-v7a Google Nexus 6 骁龙805 四核 2.7GHz 84 ms 245 ms 103 ms 63 ms
arm-v7a Vivo x6d 联发科 MT6752 1.7GHz 245 ms 502 ms 370 ms 186 ms
arm-v7a 华为 Mate 8 海思麒麟950 4大4小 2.3GHz 1.8GHz 75 ms 180 ms 95 ms 57 ms
arm-v7a 华为 Mate 9 海思麒麟960 4大4小 2.4GHz 1.8GHz 61 ms 170 ms 94 ms 48 ms
arm-v8 iphone7 Apple A10 Fusion 2.34GHz - - 27 ms 26 ms

未开放特性

  • 多核并行加速(多核机器可以再提升30%-100% 的速度)
  • depthwise卷积运算(支持mobilenet)
  • 模型压缩功能,压缩后的模型体积可缩小到20%以下
  • GPU 运算模式(Android 基于opengl es 3.1,ios 基于metal)

同类框架对比

框架caffetensorflowmdl-androidmdl-iosncnnCoreMLPrestissimo
计算硬件 cpu cpu cpu gpu cpu gpu cpu (gpu版本未开放)
计算速度 很快 很快 极快 极快
库大小 较大 中等
兼容性 限ios8以上 很好 仅支持 ios11 很好
模型支持度 很好 - 差(仅限指定模型) 较好 - 中等(当前版本不支持mobilenet)

使用方法-模型转换

  绝影支持的是私有的模型文件格式,需要把 caffe 训练出来的模型转换为 .prestissimo 格式,模型转换工具为 caffe2Prestissimo.out。caffe2Prestissimo.out 依赖 protobuf 3.30。将 XXX.prototxt 和 YYY.caffemodel 转化为 Prestissimo 模型 ZZZ.prestissimo:(得到)./caffe2Prestissimo.out XXX.prototxt YYY.caffemodel ZZZ.prestissimo

MDL(mobile-deep-learning)

1、开源时间:2017年9月(已暂停更新)   

2、开源用户:百度    

3、GitHub地址:https://github.com/allonli/mobile-deep-learning

4、功能特点:

  • 一键部署,脚本参数就可以切换ios或者android
  • 支持iOS gpu运行MobileNet、squeezenet模型
  • 已经测试过可以稳定运行MobileNet、GoogLeNet v1、squeezenet、ResNet-50模型
  • 体积极小,无任何第三方依赖。纯手工打造。
  • 提供量化函数,对32位float转8位uint直接支持,模型体积量化后4M上下
  • 与ARM相关算法团队线上线下多次沟通,针对ARM平台会持续优化
  • NEON使用涵盖了卷积、归一化、池化所有方面的操作
  • 汇编优化,针对寄存器汇编操作具体优化
  • loop unrolling 循环展开,为提升性能减少不必要的CPU消耗,全部展开判断操作
  • 将大量繁重的计算任务前置到overhead过程

5、框架结构

MDL 框架主要包括:模型转换模块(MDL Converter)模型加载模块(Loader)网络管理模块(Net)矩阵运算模块(Gemmers)供 Android 端调用的 JNI 接口层(JNI Interfaces)

  其中,模型转换模块主要负责将Caffe 模型转为 MDL 模型,同时支持将 32bit 浮点型参数量化为 8bit 参数,从而极大地压缩模型体积;模型加载模块主要完成模型的反量化及加载校验、网络注册等过程,网络管理模块主要负责网络中各层 Layer 的初始化及管理工作;MDL 提供了供 Android 端调用的 JNI 接口层,开发者可以通过调用 JNI 接口轻松完成加载及预测过程。

6、MDL 的性能及兼容性

  • 体积 armv7 300k+
  • 速度 iOS GPU mobilenet 可以达到 40ms、squeezenet 可以达到 30ms

  MDL 从立项到开源,已经迭代了一年多。移动端比较关注的多个指标都表现良好,如体积、功耗、速度。百度内部产品线在应用前也进行过多次对比,和已开源的相关项目对比,MDL 能够在保证速度和能耗的同时支持多种深度学习模型,如 mobilenet、googlenet v1、squeezenet 等,且具有 iOS GPU 版本,squeezenet 一次运行最快可以达到 3-40ms。

同类框架对比

  框架Caffe2TensorFlowncnnMDL(CPU)MDL(GPU)硬件CPUCPUCPUCPUGPU速度慢慢快快极快体积大大小小小兼容Android&iOSAndroid&iOSAndroid&iOSAndroid&iOSiOS

  与支持 CNN 的移动端框架对比,MDL 速度快、性能稳定、兼容性好、demo 完备。

兼容性

  MDL 在 iOS 和 Android 平台均可以稳定运行,其中 iOS10 及以上平台有基于 GPU 运算的 API,性能表现非常出色,在 Android 平台则是纯 CPU 运行。高中低端机型运行状态和手机百度及其他 App 上的覆盖都有绝对优势。

  MDL 同时也支持 Caffe 模型直接转换为 MDL 模型。

Paddle-Mobile

1、开源时间:持续更新,已到3.0版本   

2、开源用户:百度    

3、GitHub地址:https://github.com/PaddlePaddle/paddle-mobile 

4、功能特点:

功能特点

  • 高性能支持ARM CPU

  • 支持Mali GPU

  • 支持Andreno GPU

  • 支持苹果设备的GPU Metal实现

  • 支持ZU5、ZU9等FPGA开发板

  • 支持树莓派等arm-linux开发板

MACE( Mobile AI Compute Engine)

1、开源时间:2018年4月(持续更新,v0.9.0 (2018-07-20))   

2、开源用户:小米    

3、GitHub地址:https://github.com/XiaoMi/mace

4、简介:Mobile AI Compute Engine (MACE) 是一个专为移动端异构计算设备优化的深度学习前向预测框架。 MACE覆盖了常见的移动端计算设备(CPU,GPU和DSP),并且提供了完整的工具链和文档,用户借助MACE能够很方便地在移动端部署深度学习模型。MACE已经在小米内部广泛使用并且被充分验证具有业界领先的性能和稳定性。

5、MACE的基本框架:

MACE Model

  MACE定义了自有的模型格式(类似于Caffe2),通过MACE提供的工具可以将Caffe和TensorFlow的模型 转为MACE模型。

MACE Interpreter

  MACE Interpreter主要负责解析运行神经网络图(DAG)并管理网络中的Tensors。

Runtime

  CPU/GPU/DSP Runtime对应于各个计算设备的算子实现。

6、MACE使用的基本流程

1. 配置模型部署文件(.yml)

  模型部署文件详细描述了需要部署的模型以及生成库的信息,MACE根据该文件最终生成对应的库文件。

2.编译MACE库

  编译MACE的静态库或者动态库。

3.转换模型

  将TensorFlow 或者 Caffe的模型转为MACE的模型。

4.1. 部署

  根据不同使用目的集成Build阶段生成的库文件,然后调用MACE相应的接口执行模型。

4.2. 命令行运行

  MACE提供了命令行工具,可以在命令行运行模型,可以用来测试模型运行时间,内存占用和正确性。

4.3. Benchmark

  MACE提供了命令行benchmark工具,可以细粒度的查看模型中所涉及的所有算子的运行时间。

7、MACE在哪些角度进行了优化?

  MACE 专为移动端异构计算平台优化的神经网络计算框架。主要从以下的角度做了专门的优化:

  • 性能
    • 代码经过NEON指令,OpenCL以及Hexagon HVX专门优化,并且采用 Winograd算法来进行卷积操作的加速。 此外,还对启动速度进行了专门的优化。
  • 功耗
    • 支持芯片的功耗管理,例如ARM的big.LITTLE调度,以及高通Adreno GPU功耗选项。
  • 系统响应
    • 支持自动拆解长时间的OpenCL计算任务,来保证UI渲染任务能够做到较好的抢占调度, 从而保证系统UI的相应和用户体验。
  • 内存占用
    • 通过运用内存依赖分析技术,以及内存复用,减少内存的占用。另外,保持尽量少的外部 依赖,保证代码尺寸精简。
  • 模型加密与保护
    • 模型保护是重要设计目标之一。支持将模型转换成C++代码,以及关键常量字符混淆,增加逆向的难度。
  • 硬件支持范围
    • 支持高通,联发科,以及松果等系列芯片的CPU,GPU与DSP(目前仅支持Hexagon)计算加速。
    • 同时支持在具有POSIX接口的系统的CPU上运行。

8、性能对比:

  MACE 支持 TensorFlow 和 Caffe 模型,提供转换工具,可以将训练好的模型转换成专有的模型数据文件,同时还可以选择将模型转换成C++代码,支持生成动态库或者静态库,提高模型保密性。

FeatherCNN

1、开源时间:持续更新,已到3.0版本   

2、开源用户:腾讯AI    

3、GitHub地址:https://github.com/Tencent/FeatherCNN

4、功能特点:

FeatherCNN 是由腾讯 AI 平台部研发的基于 ARM 架构的高效 CNN 推理库,该项目支持 Caffe 模型,且具有高性能、易部署、轻量级三大特性。

该项目具体特性如下:

  • 高性能:无论是在移动设备(iOS / Android),嵌入式设备(Linux)还是基于 ARM 的服务器(Linux)上,FeatherCNN 均能发挥最先进的推理计算性能;

  • 易部署:FeatherCNN 的所有内容都包含在一个代码库中,以消除第三方依赖关系。因此,它便于在移动平台上部署。FeatherCNN 自身的模型格式与 Caffe 模型完全兼容。

  • 轻量级:编译后的 FeatherCNN 库的体积仅为数百 KB。

TensorFlow Lite

1、开源时间:2017年11月   

2、开源用户:谷歌   

3、GitHub地址:https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite

4、简介:

Google 表示 Lite 版本 TensorFlow 是 TensorFlow Mobile 的一个延伸版本。此前,通过TensorFlow Mobile API,TensorFlow已经支持手机上的模型嵌入式部署。TensorFlow Lite应该被视为TensorFlow Mobile的升级版。

TensorFlow Lite可以与Android 8.1中发布的神经网络API完美配合,即便在没有硬件加速时也能调用CPU处理,确保模型在不同设备上的运行。 而Android端版本演进的控制权是掌握在谷歌手中的,从长期看,TensorFlow Lite会得到Android系统层面上的支持。

5、架构:

其组件包括:

  • TensorFlow 模型(TensorFlow Model):保存在磁盘中的训练模型。
  • TensorFlow Lite 转化器(TensorFlow Lite Converter):将模型转换成 TensorFlow Lite 文件格式的项目。
  • TensorFlow Lite 模型文件(TensorFlow Lite Model File):基于 FlatBuffers,适配最大速度和最小规模的模型。

6、移动端开发步骤:

Android Studio 3.0, SDK Version API26, NDK Version 14

步骤:

  1. 将此项目导入到Android Studio: https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/java/demo

  2. 下载移动端的模型(model)和标签数据(lables): https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip

  3. 下载完成解压mobilenet_v1_224_android_quant_2017_11_08.zip文件得到一个xxx.tflite和labes.txt文件,分别是模型和标签文件,并且把这两个文件复制到assets文件夹下。

  4. 构建app,run……

TensorFlow Lite和TensorFlow Mobile的区别?

  • TensorFlow Lite是TensorFlow Mobile的进化版。
  • 在大多数情况下,TensorFlow Lite拥有跟小的二进制大小,更少的依赖以及更好的性能。
  • 相比TensorFlow Mobile是对完整TensorFlow的裁减,TensorFlow Lite基本就是重新实现了。从内部实现来说,在TensorFlow内核最基本的OP,Context等数据结构,都是新的。从外在表现来说,模型文件从PB格式改成了FlatBuffers格式,TensorFlow的size有大幅度优化,降至300K,然后提供一个converter将普通TensorFlow模型转化成TensorFlow Lite需要的格式。因此,无论从哪方面看,TensorFlow Lite都是一个新的实现方案。

PocketFlow

1、开源时间:2018年9月   

2、开源用户:腾讯   

3、GitHub地址:https://github.com/Tencent/PocketFlow

4、简介:

全球首个自动模型压缩框架

一款面向移动端AI开发者的自动模型压缩框架,集成了当前主流的模型压缩与训练算法,结合自研超参数优化组件实现了全程自动化托管式的模型压缩与加速。开发者无需了解具体算法细节,即可快速地将AI技术部署到移动端产品上,实现了自动托管式模型压缩与加速,实现用户数据的本地高效处理。

5、框架介绍

PocketFlow 框架主要由两部分组件构成,分别是模型压缩/加速算法组件和超参数优化组件,具体结构如下图所示。

  开发者将未压缩的原始模型作为 PocketFlow 框架的输入,同时指定期望的性能指标,例如模型的压缩和/或加速倍数;在每一轮迭代过程中,超参数优化组件选取一组超参数取值组合,之后模型压缩/加速算法组件基于该超参数取值组合,对原始模型进行压缩,得到一个压缩后的候选模型;基于对候选模型进行性能评估的结果,超参数优化组件调整自身的模型参数,并选取一组新的超参数取值组合,以开始下一轮迭代过程;当迭代终止时,PocketFlow 选取最优的超参数取值组合以及对应的候选模型,作为最终输出,返回给开发者用作移动端的模型部署。

6、PocketFlow如何实现模型压缩与加速?

​ 具体地,PocketFlow 通过下列各个算法组件的有效结合,实现了精度损失更小、自动化程度更高的深度学习模型的压缩与加速:

  • a) 通道剪枝(channel pruning)组件:在CNN网络中,通过对特征图中的通道维度进行剪枝,可以同时降低模型大小和计算复杂度,并且压缩后的模型可以直接基于现有的深度学习框架进行部署。在CIFAR-10图像分类任务中,通过对 ResNet-56 模型进行通道剪枝,可以实现2.5倍加速下分类精度损失0.4%,3.3倍加速下精度损失0.7%。

  • b) 权重稀疏化(weight sparsification)组件:通过对网络权重引入稀疏性约束,可以大幅度降低网络权重中的非零元素个数;压缩后模型的网络权重可以以稀疏矩阵的形式进行存储和传输,从而实现模型压缩。对于 MobileNet 图像分类模型,在删去50%网络权重后,在 ImageNet 数据集上的 Top-1 分类精度损失仅为0.6%。

  • c) 权重量化(weight quantization)组件:通过对网络权重引入量化约束,可以降低用于表示每个网络权重所需的比特数;团队同时提供了对于均匀和非均匀两大类量化算法的支持,可以充分利用 ARM 和 FPGA 等设备的硬件优化,以提升移动端的计算效率,并为未来的神经网络芯片设计提供软件支持。以用于 ImageNet 图像分类任务的 ResNet-18 模型为例,在8比特定点量化下可以实现精度无损的4倍压缩。

  • d)网络蒸馏(network distillation)组件:对于上述各种模型压缩组件,通过将未压缩的原始模型的输出作为额外的监督信息,指导压缩后模型的训练,在压缩/加速倍数不变的前提下均可以获得0.5%-2.0%不等的精度提升。

  • e) 多GPU训练(multi-GPU training)组件:深度学习模型训练过程对计算资源要求较高,单个GPU难以在短时间内完成模型训练,因此团队提供了对于多机多卡分布式训练的全面支持,以加快使用者的开发流程。无论是基于 ImageNet 数据的Resnet-50图像分类模型还是基于 WMT14 数据的 Transformer 机器翻译模型,均可以在一个小时内训练完毕。[1]

  • f) 超参数优化(hyper-parameter optimization)组件:多数开发者对模型压缩算法往往不甚了解,但超参数取值对最终结果往往有着巨大的影响,因此团队引入了超参数优化组件,采用了包括强化学习等算法以及 AI Lab 自研的 AutoML 自动超参数优化框架来根据具体性能需求,确定最优超参数取值组合。例如,对于通道剪枝算法,超参数优化组件可以自动地根据原始模型中各层的冗余程度,对各层采用不同的剪枝比例,在保证满足模型整体压缩倍数的前提下,实现压缩后模型识别精度的最大化。

7、PocketFlow 性能

  通过引入超参数优化组件,不仅避免了高门槛、繁琐的人工调参工作,同时也使得 PocketFlow 在各个压缩算法上全面超过了人工调参的效果。以图像分类任务为例,在 CIFAR-10 和 ImageNet 等数据集上,PocketFlow 对 ResNet 和 MobileNet 等多种 CNN 网络结构进行有效的模型压缩与加速。

  在 CIFAR-10 数据集上,PocketFlow 以 ResNet-56 作为基准模型进行通道剪枝,并加入了超参数优化和网络蒸馏等训练策略,实现了 2.5 倍加速下分类精度损失 0.4%,3.3 倍加速下精度损失 0.7%,且显著优于未压缩的 ResNet-44 模型; 在 ImageNet 数据集上,PocketFlow 可以对原本已经十分精简的 MobileNet 模型继续进行权重稀疏化,以更小的模型尺寸取得相似的分类精度;与 Inception-V1、ResNet-18 等模型相比,模型大小仅为后者的约 20~40%,但分类精度基本一致(甚至更高)。

  相比于费时费力的人工调参,PocketFlow 框架中的 AutoML 自动超参数优化组件仅需 10 余次迭代就能达到与人工调参类似的性能,在经过 100 次迭代后搜索得到的超参数组合可以降低约 0.6% 的精度损失;通过使用超参数优化组件自动地确定网络中各层权重的量化比特数,PocketFlow 在对用于 ImageNet 图像分类任务的 ResNet-18 模型进行压缩时,取得了一致性的性能提升;当平均量化比特数为 4 比特时,超参数优化组件的引入可以将分类精度从 63.6% 提升至 68.1%(原始模型的分类精度为 70.3%)。

参考文献

[1] Zhuangwei Zhuang, Mingkui Tan, Bohan Zhuang, Jing Liu, Jiezhang Cao, Qingyao Wu, Junzhou Huang, Jinhui Zhu,「Discrimination-aware Channel Pruning for Deep Neural Networks", In Proc. of the 32nd Annual Conference on Neural Information Processing Systems, NIPS '18, Montreal, Canada, December 2018.

[2] Jiaxiang Wu, Weidong Huang, Junzhou Huang, Tong Zhang,「Error Compensated Quantized SGD and its Applications to Large-scale Distributed Optimization」, In Proc. of the 35th International Conference on Machine Learning, ICML’18, Stockholm, Sweden, July 2018.

其他几款支持移动端深度学习的开源框架

https://blog.csdn.net/zchang81/article/details/74280019

MDL、NCNN和 TFLite比较

百度-MDL框架、腾讯-NCNN框架和谷歌TFLite框架比较。

 MDLNCNNTFLite
代码质量 很高
跨平台
支持caffe模型 ×
支持TensorFlow模型 × ×
CPU NEON指令优化
GPU加速 × ×

相同点:

  • 只含推理(inference)功能,使用的模型文件需要通过离线的方式训练得到。
  • 最终生成的库尺寸较小,均小于500kB。
  • 为了提升执行速度,都使用了ARM NEON指令进行加速。
  • 跨平台,iOS和Android系统都支持。

不同点:

  • MDL和NCNN均是只支持Caffe框架生成的模型文件,而TfLite则毫无意外的只支持自家大哥TensorFlow框架生成的模型文件。
  • MDL支持利用iOS系统的Matal框架进行GPU加速,能够显著提升在iPhone上的运行速度,达到准实时的效果。而NCNN和TFLite还没有这个功能。

移动端开源框架部署

以NCNN为例

部署步骤

以QNNPACK为例

部署步骤

在Android手机上使用MACE实现图像分类

在Android手机上使用PaddleMobile实现图像分类

编译paddle-mobile库

1)编译Android能够使用的CPP库:编译Android的paddle-mobile库,可选择使用Docker编译和Ubuntu交叉编译,这里介绍使用Ubuntu交叉编译paddle-mobile库。

:在Android项目,Java代码调用CPP代码,CPP的函数需要遵循一定的命名规范,比如Java_包名_类名_对应的Java的方法名。

​ 目前官方提供了5个可以给Java调用的函数,该代码在:paddle-mobile/src/jni/paddle_mobile_jni.cpp,如果想要让这些函数能够在自己的包名下的类调用,就要修改CPP的函数名称修改如下:

JNIEXPORT jboolean JNICALL Java_com_baidu_paddle_PML_load(JNIEnv *env, 
    jclass thiz,
    jstring modelPath) { 
        ANDROIDLOGI("load invoked"); 
        bool optimize = true; 
        return getPaddleMobileInstance()->Load(jstring2cppstring(env, modelPath), optimize); }

​ 笔者项目的包名为com.example.paddlemobile1,在这个包下有一个ImageRecognition.java的程序来对应这个CPP程序,那么修改load函数如下:

JNIEXPORT jboolean JNICALL Java_com_example_paddlemobile1_ImageRecognition_load(JNIEnv *env,
                                                          jclass thiz,
                                                          jstring modelPath) {
  ANDROIDLOGI("load invoked");
  bool optimize = true;
  return getPaddleMobileInstance()->Load(jstring2cppstring(env, modelPath),
                                         optimize);
}

使用Ubuntu交叉编译paddle-mobile库

1、下载和解压NDK。

wget https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip
unzip android-ndk-r17b-linux-x86_64.zip

2、设置NDK环境变量,目录是NDK的解压目录。

export NDK_ROOT="/home/test/paddlepaddle/android-ndk-r17b"

设置好之后,可以使用以下的命令查看配置情况。

root@test:/home/test/paddlepaddle# echo $NDK_ROOT
/home/test/paddlepaddle/android-ndk-r17b

3、安装cmake,需要安装较高版本的,笔者的cmake版本是3.11.2。

下载cmake源码

wget https://cmake.org/files/v3.11/cmake-3.11.2.tar.gz

解压cmake源码

tar -zxvf cmake-3.11.2.tar.gz

进入到cmake源码根目录,并执行bootstrap。

cd cmake-3.11.2
./bootstrap

最后执行以下两条命令开始安装cmake。

make
make install

安装完成之后,可以使用cmake --version是否安装成功.

root@test:/home/test/paddlepaddle# cmake --version
cmake version 3.11.2

CMake suite maintained and supported by Kitware (kitware.com/cmake).

4、克隆paddle-mobile源码。

git clone https://github.com/PaddlePaddle/paddle-mobile.git

5、进入到paddle-mobile的tools目录下,执行编译。

cd paddle-mobile/tools/
sh build.sh android

(可选)如果想编译针对某一个网络编译更小的库时,可以在命令后面加上相应的参数,如下:

sh build.sh android googlenet

6、最后会在paddle-mobile/build/release/arm-v7a/build目录下生产paddle-mobile库。

root@test:/home/test/paddlepaddle/paddle-mobile/build/release/arm-v7a/build# ls
libpaddle-mobile.so

libpaddle-mobile.so就是我们在开发Android项目的时候使用到的paddle-mobile库。

创建Android项目

1、首先使用Android Studio创建一个普通的Android项目,包名为 com.example.paddlemobile1 

2、在main目录下创建l两个assets/paddle_models文件夹,这个文件夹存放PaddleFluid训练好的预测模型。PaddleMobile支持量化模型,使用模型量化可以把模型缩小至原来的四分之一,如果使用量化模型,那加载模型的接口也有修改一下,使用以下的接口加载模型:

public static native boolean loadQualified(String modelDir);

3、在 main 目录下创建一个 jniLibs 文件夹,这个文件夹是存放CPP编译库的,在本项目中就存放上一部分编译的 libpaddle-mobile.so 

4、在Android项目的配置文件夹中加上权限声明,因为我们要使用到读取相册和使用相机,所以加上以下的权限声明:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

5、修改activity_main.xml界面,修改成如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
<LinearLayout
    android:id="@+id/btn_ll"
    android:layout_alignParentBottom="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
        android:id="@+id/use_photo"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="相册" />

    <Button
        android:id="@+id/start_camera"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="拍照" />
</LinearLayout>

<TextView
    android:layout_above="@id/btn_ll"
    android:id="@+id/result_text"
    android:textSize="16sp"
    android:layout_width="match_parent"
    android:hint="预测结果会在这里显示"
    android:layout_height="100dp" />

<ImageView
    android:layout_alignParentTop="true"
    android:layout_above="@id/result_text"
    android:id="@+id/show_image"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
</RelativeLayout>
View Code

6、创建一个 ImageRecognition.java 的Java程序,这个程序的作用就是调用 paddle-mobile/src/jni/paddle_mobile_jni.cpp 的函数,对应的是里面的函数。目前支持一下几个接口。

package com.example.paddlemobile1;

public class ImageRecognition {
    // set thread num
    public static native void setThread(int threadCount);

//Load seperated parameters
public static native boolean load(String modelDir);

// load qualified model
public static native boolean loadQualified(String modelDir);

// Load combined parameters
public static native boolean loadCombined(String modelPath, String paramPath);

// load qualified model
public static native boolean loadCombinedQualified(String modelPath, String paramPath);

// object detection
public static native float[] predictImage(float[] buf, int[]ddims);

// predict yuv image
public static native float[] predictYuv(byte[] buf, int imgWidth, int imgHeight, int[] ddims, float[]meanValues);

// clear model
public static native void clear();
}
View Code

7、然后编写一个 PhotoUtil.java 的工具类。

package com.example.paddlemobile1;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

public class PhotoUtil {
// start camera
public static Uri start_camera(Activity activity, int requestCode) {
    Uri imageUri;
    // save image in cache path
    File outputImage = new File(activity.getExternalCacheDir(), "out_image.jpg");
    try {
        if (outputImage.exists()) {
            outputImage.delete();
        }
        outputImage.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
    if (Build.VERSION.SDK_INT >= 24) {
        // compatible with Android 7.0 or over
        imageUri = FileProvider.getUriForFile(activity,
                "com.example.paddlemobile1", outputImage);
    } else {
        imageUri = Uri.fromFile(outputImage);
    }
    // set system camera Action
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    // set save photo path
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    // set photo quality, min is 0, max is 1
    intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
    activity.startActivityForResult(intent, requestCode);
    return imageUri;
}

// get picture in photo
public static void use_photo(Activity activity, int requestCode){
    Intent intent = new Intent(Intent.ACTION_PICK);
    intent.setType("image/*");
    activity.startActivityForResult(intent, requestCode);
}

// get photo from Uri
public static String get_path_from_URI(Context context, Uri uri) {
    String result;
    Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
    if (cursor == null) {
        result = uri.getPath();
    } else {
        cursor.moveToFirst();
        int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
        result = cursor.getString(idx);
        cursor.close();
    }
    return result;
}

// Compress the image to the size of the training image,and change RGB
public static float[] getScaledMatrix(Bitmap bitmap, int desWidth,
                               int desHeight) {
    float[] dataBuf = new float[3 * desWidth * desHeight];
    int rIndex;
    int gIndex;
    int bIndex;
    int[] pixels = new int[desWidth * desHeight];
    Bitmap bm = Bitmap.createScaledBitmap(bitmap, desWidth, desHeight, false);
    bm.getPixels(pixels, 0, desWidth, 0, 0, desWidth, desHeight);
    int j = 0;
    int k = 0;
    for (int i = 0; i < pixels.length; i++) {
        int clr = pixels[i];
        j = i / desHeight;
        k = i % desWidth;
        rIndex = j * desWidth + k;
        gIndex = rIndex + desHeight * desWidth;
        bIndex = gIndex + desHeight * desWidth;
        dataBuf[rIndex] = (float) ((clr & 0x00ff0000) >> 16) - 148;
        dataBuf[gIndex] = (float) ((clr & 0x0000ff00) >> 8) - 148;
        dataBuf[bIndex] = (float) ((clr & 0x000000ff)) - 148;

    }
    if (bm.isRecycled()) {
        bm.recycle();
    }
    return dataBuf;
}

// compress picture
public static Bitmap getScaleBitmap(String filePath) {
    BitmapFactory.Options opt = new BitmapFactory.Options();
    opt.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(filePath, opt);

    int bmpWidth = opt.outWidth;
    int bmpHeight = opt.outHeight;

    int maxSize = 500;

    // compress picture with inSampleSize
    opt.inSampleSize = 1;
    while (true) {
        if (bmpWidth / opt.inSampleSize < maxSize || bmpHeight / opt.inSampleSize < maxSize) {
            break;
        }
        opt.inSampleSize *= 2;
    }
    opt.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(filePath, opt);
}
}
View Code
  • start_camera()方法是启动相机并返回图片的URI。
  • use_photo()方法是打开相册,获取到的图片URI在回到函数中获取。
  • get_path_from_URI()方法是把图片的URI转换成绝对路径。
  • getScaledMatrix()方法是把图片压缩成跟训练时的大小,并转换成预测需要用的数据格式浮点数组。
  • getScaleBitmap()方法是对图片进行等比例压缩,减少内存的支出。

8、最后修改MainActivity.java ,修改如下:

package com.example.paddlemobile1;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getName();
    private static final int USE_PHOTO = 1001;
    private static final int START_CAMERA = 1002;
    private Uri image_uri;
    private ImageView show_image;
    private TextView result_text;
    private String assets_path = "paddle_models";
    private boolean load_result = false;
    private int[] ddims = {1, 3, 224, 224};
private static final String[] PADDLE_MODEL = {
        "lenet",
        "alexnet",
        "vgg16",
        "resnet",
        "googlenet",
        "mobilenet_v1",
        "mobilenet_v2",
        "inception_v1",
        "inception_v2",
        "squeezenet"
};

// load paddle-mobile api
static {
    try {
        System.loadLibrary("paddle-mobile");

    } catch (SecurityException e) {
        e.printStackTrace();

    } catch (UnsatisfiedLinkError e) {
        e.printStackTrace();

    } catch (NullPointerException e) {
        e.printStackTrace();

    }

}
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    init();
}

// initialize view
private void init() {
    request_permissions();
    show_image = (ImageView) findViewById(R.id.show_image);
    result_text = (TextView) findViewById(R.id.result_text);
    Button use_photo = (Button) findViewById(R.id.use_photo);
    Button start_photo = (Button) findViewById(R.id.start_camera);

    // use photo click
    use_photo.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            PhotoUtil.use_photo(MainActivity.this, USE_PHOTO);
            //                load_model();
            }
        });
    // start camera click
    start_photo.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            image_uri = PhotoUtil.start_camera(MainActivity.this, START_CAMERA);
        }
    });

    // copy file from assets to sdcard
    String sdcard_path = Environment.getExternalStorageDirectory()
            + File.separator + assets_path;
    copy_file_from_asset(this, assets_path, sdcard_path);

    // load model
    load_model();
}

// load infer model
private void load_model() {
    String model_path = Environment.getExternalStorageDirectory()
            + File.separator + assets_path + File.separator + PADDLE_MODEL[4];
    Log.d(TAG, model_path);
    load_result = ImageRecognition.load(model_path);
    if (load_result) {
        Log.d(TAG, "model load success");
    } else {
        Log.d(TAG, "model load fail");
    }
}

// clear infer model
private void clear_model() {
    ImageRecognition.clear();
    Log.d(TAG, "model is clear");
}

// copy file from asset to sdcard
public void copy_file_from_asset(Context context, String oldPath, String newPath) {
    try {
        String[] fileNames = context.getAssets().list(oldPath);
        if (fileNames.length > 0) {
            // directory
            File file = new File(newPath);
            if (!file.exists()) {
                file.mkdirs();
            }
            // copy recursivelyC
            for (String fileName : fileNames) {
                copy_file_from_asset(context, oldPath + "/" + fileName, newPath + "/" + fileName);
            }
            Log.d(TAG, "copy files finish");
        } else {
            // file
            File file = new File(newPath);
            // if file exists will never copy
            if (file.exists()) {
                return;
            }

            // copy file to new path
            InputStream is = context.getAssets().open(oldPath);
            FileOutputStream fos = new FileOutputStream(file);
            byte[] buffer = new byte[1024];
            int byteCount;
            while ((byteCount = is.read(buffer)) != -1) {
                fos.write(buffer, 0, byteCount);
            }
            fos.flush();
            is.close();
            fos.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    String image_path;
    RequestOptions options = new RequestOptions().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE);
    if (resultCode == Activity.RESULT_OK) {
        switch (requestCode) {
            case USE_PHOTO:
                if (data == null) {
                    Log.w(TAG, "user photo data is null");
                    return;
                }
                image_uri = data.getData();
                Glide.with(MainActivity.this).load(image_uri).apply(options).into(show_image);
                // get image path from uri
                image_path = PhotoUtil.get_path_from_URI(MainActivity.this, image_uri);
                // show result
                result_text.setText(image_path);
                // predict image
                predict_image(PhotoUtil.get_path_from_URI(MainActivity.this, image_uri));
                break;
            case START_CAMERA:
                // show photo
                Glide.with(MainActivity.this).load(image_uri).apply(options).into(show_image);
                // get image path from uri
                image_path = PhotoUtil.get_path_from_URI(MainActivity.this, image_uri);
                // show result
                result_text.setText(image_path);
                // predict image
                predict_image(PhotoUtil.get_path_from_URI(MainActivity.this, image_uri));
                break;
        }
    }
}

@SuppressLint("SetTextI18n")
private void predict_image(String image_path) {
    // picture to float array
    Bitmap bmp = PhotoUtil.getScaleBitmap(image_path);
    float[] inputData = PhotoUtil.getScaledMatrix(bmp, ddims[2], ddims[3]);
    try {
        long start = System.currentTimeMillis();
        // get predict result
        float[] result = ImageRecognition.predictImage(inputData, ddims);
        Log.d(TAG, "origin predict result:" + Arrays.toString(result));
        long end = System.currentTimeMillis();
        long time = end - start;
        Log.d("result length", String.valueOf(result.length));
        // show predict result and time
        int r = get_max_result(result);
        String show_text = "result:" + r + "
probability:" + result[r] + "
time:" + time + "ms";
        result_text.setText(show_text);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private int get_max_result(float[] result) {
    float probability = result[0];
    int r = 0;
    for (int i = 0; i < result.length; i++) {
        if (probability < result[i]) {
            probability = result[i];
            r = i;
        }
    }
    return r;
}

// request permissions
private void request_permissions() {

    List<String> permissionList = new ArrayList<>();
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
        permissionList.add(Manifest.permission.CAMERA);
    }

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
    }

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
    }

    // if list is not empty will request permissions
    if (!permissionList.isEmpty()) {
        ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), 1);
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case 1:
            if (grantResults.length > 0) {
                for (int i = 0; i < grantResults.length; i++) {

                    int grantResult = grantResults[i];
                    if (grantResult == PackageManager.PERMISSION_DENIED) {
                        String s = permissions[i];
                        Toast.makeText(this, s + " permission was denied", Toast.LENGTH_SHORT).show();
                    }
                }
            }
            break;
    }
}

@Override
protected void onDestroy() {
    // clear model before destroy app
    clear_model();
    super.onDestroy();
}
}
View Code
  • load_model()方法是加载预测模型的。
  • clear_model()方法是清空预测模型的。
  • copy_file_from_asset()方法是把预测模型复制到内存卡上。
  • predict_image()方法是预测图片的。
  • get_max_result()方法是获取概率最大的预测结果。
  • request_permissions()方法是动态请求权限的。

因为使用到图像加载框架Glide,所以要在 build.gradle 加入以下的引用。

implementation 'com.github.bumptech.glide:glide:4.3.1'

8、最后运行项目,选择图片预测就会得到结果。

17.9 移动端开源框架部署疑难

增加常见的几个问题

知识蒸馏(Distillation)相关论文阅读(1)——Distilling the Knowledge in a Neural Network(以及代码复现)

原文地址:https://www.cnblogs.com/LXP-Never/p/14840535.html