【Tensorflow教程笔记】TensorFlow Lite

Tensorflow教程笔记

  1. 基础
    TensorFlow 基础
    TensorFlow 模型建立与训练
    基础示例:多层感知机(MLP)
    卷积神经网络(CNN)
    循环神经网络(RNN)
    深度强化学习(DRL)
    Keras Pipeline
    自定义层、损失函数和评估指标
    常用模块 tf.train.Checkpoint :变量的保存与恢复
    常用模块 TensorBoard:训练过程可视化
    常用模块 tf.data :数据集的构建与预处理
    常用模块 TFRecord :TensorFlow 数据集存储格式
    常用模块 tf.function :图执行模式
    常用模块 tf.TensorArray :TensorFlow 动态数组
    常用模块 tf.config:GPU 的使用与分配

  2. 部署
    TensorFlow 模型导出
    TensorFlow Serving
    TensorFlow Lite

  3. 大规模训练与加速
    TensorFlow 分布式训练
    使用 TPU 训练 TensorFlow 模型

  4. 扩展
    TensorFlow Hub 模型复用
    TensorFlow Datasets 数据集载入

  5. 附录
    强化学习基础简介


目录

  • Tensorflow教程笔记
  • 模型转换
  • Android 部署
    • 配置 build.gradle
    • 配置 app/build.gradle
    • 添加 tflite 文件到 assets 文件夹
    • 加载模型
    • 运行输入
    • 运行输出
    • 运行及结果处理
  • Quantization 模型转换
    • visualize.py 使用方法
  • 总结

TensorFlow Lite 是 TensorFlow 在移动和 IoT 等边缘设备端的解决方案,提供了 Java、Python 和 C++ API 库,可以运行在 Android、iOS 和 Raspberry Pi 等设备上。2019 年是 5G 元年,万物互联的时代已经来临,作为 TensorFlow 在边缘设备上的基础设施,TFLite 将会是愈发重要的角色。

目前 TFLite 只提供了推理功能,在服务器端进行训练后,经过如下简单处理即可部署到边缘设备上。

  • 模型转换:由于边缘设备计算等资源有限,使用 TensorFlow 训练好的模型,模型太大、运行效率比较低,不能直接在移动端部署,需要通过相应工具进行转换成适合边缘设备的格式。
  • 边缘设备部署:本节以 android 为例,简单介绍如何在 android 应用中部署转化后的模型,完成 Mnist 图片的识别。

模型转换

转换工具有两种:命令行工具和 Python API

TF2.0 对模型转换工具发生了非常大的变化,推荐大家使用 Python API 进行转换,命令行工具只提供了基本的转化功能。转换后的原模型为 FlatBuffers 格式。 FlatBuffers 原来主要应用于游戏场景,是谷歌为了高性能场景创建的序列化库,相比 Protocol Buffer 有更高的性能和更小的大小等优势,更适合于边缘设备部署。

转换方式有两种:Float 格式和 Quantized 格式

为了熟悉两种方式我们都会使用,针对 Float 格式的,先使用命令行工具 tflite_convert ,其跟随 TensorFlow 一起安装。

在终端执行如下命令:

tflite_convert -h

输出结果如下,即该命令的使用方法:

usage: tflite_convert [-h] --output_file OUTPUT_FILE
                      (--saved_model_dir SAVED_MODEL_DIR | --keras_model_file KERAS_MODEL_FILE)
  --output_file OUTPUT_FILE
                        Full filepath of the output file.
  --saved_model_dir SAVED_MODEL_DIR
                        Full path of the directory containing the SavedModel.
  --keras_model_file KERAS_MODEL_FILE
                        Full filepath of HDF5 file containing tf.Keras model.

在 TensorFlow 模型导出 中,我们知道 TF2.0 支持两种模型导出方法和格式 SavedModel 和 Keras Sequential。

SavedModel 导出模型转换:

tflite_convert --saved_model_dir=saved/1 --output_file=mnist_savedmodel.tflite

Keras Sequential 导出模型转换:

tflite_convert --keras_model_file=mnist_cnn.h5 --output_file=mnist_sequential.tflite

到此,已经得到两个 TensorFlow Lite 模型。因为两者后续操作基本一致,我们只处理 SavedModel 格式的,Keras Sequential 的转换可以按类似方法处理。

Android 部署

现在开始在 Android 环境部署,为了获取 SDK 和 gradle 编译环境等资源,需要先给 Android Studio 配置 proxy 或者使用镜像。

配置 build.gradle

build.gradle 中的 maven 源 google()jcenter() 分别替换为阿里云镜像地址,如下:

buildscript {

    repositories {
        maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
        maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.1'
    }
}

allprojects {
    repositories {
        maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
        maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }
    }
}

配置 app/build.gradle

新建一个 Android Project,打开 app/build.gradle 添加如下信息:

android {
    aaptOptions {
        noCompress "tflite" // 编译apk时,不压缩tflite文件
    }
}

dependencies {
    implementation 'org.tensorflow:tensorflow-lite:1.14.0'
}

其中,

  1. aaptOptions 设置 tflite 文件不压缩,确保后面 tflite 文件可以被 Interpreter 正确加载。

  2. org.tensorflow:tensorflow-lite 的最新版本号可以在这里查询 https://bintray.com/google/tensorflow/tensorflow-lite

设置好后,sync 和 build 整个工程,如果 build 成功说明,配置成功。

添加 tflite 文件到 assets 文件夹

在 app 目录先新建 assets 目录,并将 mnist_savedmodel.tflite 文件保存到 assets 目录。重新编译 apk,检查新编译出来的 apk 的 assets 文件夹是否有 mnist_cnn.tflite 文件。

点击菜单 Build->Build APK (s) 触发 apk 编译,apk 编译成功点击右下角的 EventLog。点击最后一条信息中的 analyze 链接,会触发 apk analyzer 查看新编译出来的 apk,若在 assets 目录下存在 mnist_savedmodel.tflite ,则编译打包成功,如下:

assets
     |__mnist_savedmodel.tflite

加载模型

使用如下函数将 mnist_savedmodel.tflite 文件加载到 memory-map 中,作为 Interpreter 实例化的输入

/** Memory-map the model file in Assets. */
private MappedByteBuffer loadModelFile(Activity activity) throws IOException {
    AssetFileDescriptor fileDescriptor = activity.getAssets().openFd(mModelPath);
    FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
    FileChannel fileChannel = inputStream.getChannel();
    long startOffset = fileDescriptor.getStartOffset();
    long declaredLength = fileDescriptor.getDeclaredLength();
    return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
}

memory-map 可以把整个文件映射到虚拟内存中,用于提升 tflite 模型的读取性能。更多请参考: JDK API 介绍

实例化 Interpreter,其中 acitivity 是为了从 assets 中获取模型,因为我们把模型编译到 assets 中,只能通过 getAssets() 打开。

mTFLite = new Interpreter(loadModelFile(activity));

memory-map 后的 MappedByteBuffer 直接作为 Interpreter 的输入, mTFLiteInterpreter )就是转换后模型的运行载体。

运行输入

我们使用 MNIST test 测试集中的图片作为输入,mnist 图像大小 28*28,单像素,因为我们输入的数据需要设置成如下格式

//Float模型相关参数
// com/dpthinker/mnistclassifier/model/FloatSavedModelConfig.java
protected void setConfigs() {
    setModelName("mnist_savedmodel.tflite");

    setNumBytesPerChannel(4);

    setDimBatchSize(1);
    setDimPixelSize(1);

    setDimImgWeight(28);
    setDimImgHeight(28);

    setImageMean(0);
    setImageSTD(255.0f);
}

// 初始化
// com/dpthinker/mnistclassifier/classifier/BaseClassifier.java
private void initConfig(BaseModelConfig config) {
    this.mModelConfig = config;
    this.mNumBytesPerChannel = config.getNumBytesPerChannel();
    this.mDimBatchSize = config.getDimBatchSize();
    this.mDimPixelSize = config.getDimPixelSize();
    this.mDimImgWidth = config.getDimImgWeight();
    this.mDimImgHeight = config.getDimImgHeight();
    this.mModelPath = config.getModelName();
}

将 MNIST 图片转化成 ByteBuffer ,并保持到 imgDataByteBuffer )中

// 将输入的Bitmap转化为Interpreter可以识别的ByteBuffer
// com/dpthinker/mnistclassifier/classifier/BaseClassifier.java
protected ByteBuffer convertBitmapToByteBuffer(Bitmap bitmap) {
    int[] intValues = new int[mDimImgWidth * mDimImgHeight];
    scaleBitmap(bitmap).getPixels(intValues,
            0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());

    ByteBuffer imgData = ByteBuffer.allocateDirect(
            mNumBytesPerChannel * mDimBatchSize * mDimImgWidth * mDimImgHeight * mDimPixelSize);
    imgData.order(ByteOrder.nativeOrder());
    imgData.rewind();

    // Convert the image toFloating point.
    int pixel = 0;
    for (int i = 0; i < mDimImgWidth; ++i) {
        for (int j = 0; j < mDimImgHeight; ++j) {
            //final int val = intValues[pixel++];
            int val = intValues[pixel++];
            mModelConfig.addImgValue(imgData, val); //添加把Pixel数值转化并添加到ByteBuffer
        }
    }
    return imgData;
}

// mModelConfig.addImgValue定义
// com/dpthinker/mnistclassifier/model/FloatSavedModelConfig.java
public void addImgValue(ByteBuffer imgData, int val) {
    imgData.putFloat(((val & 0xFF) - getImageMean()) / getImageSTD());
}

convertBitmapToByteBuffer 的输出即为模型运行的输入。

运行输出

定义一个 1*10 的多维数组,因为我们只有 10 个 label,具体代码如下

privateFloat[][] mLabelProbArray = newFloat[1][10];

运行结束后,每个二级元素都是一个 label 的概率。

运行及结果处理

开始运行模型,具体代码如下

mTFLite.run(imgData, mLabelProbArray);

针对某个图片,运行后 mLabelProbArray 的内容就是各个 label 识别的概率。对他们进行排序,找出 Top 的 label 并界面呈现给用户.

在 Android 应用中,作者使用了 View.OnClickListener() 触发 "image/*" 类型的 Intent.ACTION_GET_CONTENT ,进而获取设备上的图片(只支持 MNIST 标准图片)。然后,通过 RadioButtion 的选择情况,确认加载哪种转换后的模型,并触发真正分类操作。

选取一张 MNIST 测试集中的图片进行测试,得到结果如下:
【Tensorflow教程笔记】TensorFlow Lite_第1张图片

注意我们这里直接用 mLabelProbArray 数值中的 index 作为 label 了,因为 MNIST 的 label 完全跟 index 从 0 到 9 匹配。如果是其他的分类问题,需要根据实际情况进行转换。

Quantization 模型转换

Quantized 模型是对原模型进行转换过程中,将 float 参数转化为 uint8 类型,进而产生的模型会更小、运行更快,但是精度会有所下降。

前面我们介绍了 Float 模型的转换方法,接下来我们要展示下 Quantized 模型,在 TF1.0 上,可以使用命令行工具转换 Quantized 模型。在作者尝试的情况看在 TF2.0 上,命令行工具目前只能转换为 Float 模型,Python API 只能转换为 Quantized 模型。

Python API 转换方法如下:

import tensorflow as tf

converter = tf.lite.TFLiteConverter.from_saved_model('saved/1')
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()
open("mnist_savedmodel_quantized.tflite", "wb").write(tflite_quant_model)

最终转换后的 Quantized 模型即为同级目录下的 mnist_savedmodel_quantized.tflite

相对 TF1.0,上面的方法简化了很多,不需要考虑各种各样的参数,谷歌一直在优化开发者的使用体验。

在 TF1.0 上,我们可以使用 tflite_convert 获得模型具体结构,然后通过 graphviz 转换为 pdf 或 png 等方便查看。 在 TF2.0 上,提供了新的一步到位的工具 visualize.py ,直接转换为 html 文件,除了模型结构,还有更清晰的关键信息总结。

visualize.py 目前看应该还是开发阶段,使用前需要先从 github 下载最新的 TensorFlowFlatBuffers 源码,并且两者要在同一目录,因为 visualize.py 源码中是按两者在同一目录写的调用路径。

下载 TensorFlow:

git clone git@github.com:tensorflow/tensorflow.git

下载 FlatBuffers:

git clone git@github.com:google/flatbuffers.git

编译 FlatBuffers:(作者使用的 Mac,其他平台请大家自行配置,应该不麻烦)

  1. 下载 cmake:执行 brew install cmake
  2. 设置编译环境:在 FlatBuffers 的根目录,执行 cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release
  3. 编译:在 FlatBuffers 的根目录,执行 make

编译完成后,会在根目录生成 flatc,这个可执行文件是 visualize.py 运行所依赖的。

visualize.py 使用方法

在 tensorflow/tensorflow/lite/tools 目录下,执行如下命令

python visualize.py mnist_savedmodel_quantized.tflite mnist_savedmodel_quantized.html

生成可视化报告的关键信息
【Tensorflow教程笔记】TensorFlow Lite_第2张图片
模型结构
【Tensorflow教程笔记】TensorFlow Lite_第3张图片

可见,Input/Output 格式都是 FLOAT32 的多维数组,Input 的 min 和 max 分别是 0.0 和 255.0。

跟 Float 模型对比,Input/Output 格式是一致的,所以可以复用 Float 模型 Android 部署过程中的配置。

暂不确定这里是否是 TF2.0 上的优化,如果是这样的话,对开发者来说是非常友好的,如此就归一化了 Float 和 Quantized 模型处理了。

具体配置如下:

// Quantized模型相关参数
// com/dpthinker/mnistclassifier/model/QuantSavedModelConfig.java
public class QuantSavedModelConfig extends BaseModelConfig {
    @Override
    protected void setConfigs() {
        setModelName("mnist_savedmodel_quantized.tflite");

        setNumBytesPerChannel(4);

        setDimBatchSize(1);
        setDimPixelSize(1);

        setDimImgWeight(28);
        setDimImgHeight(28);

        setImageMean(0);
        setImageSTD(255.0f);
    }

    @Override
    public void addImgValue(ByteBuffer imgData, int val) {
        imgData.putFloat(((val & 0xFF) - getImageMean()) / getImageSTD());
    }
}

运行效果如下:
【Tensorflow教程笔记】TensorFlow Lite_第4张图片

Float 模型与 Quantized 模型大小与性能对比:

模型类别 Float Quantized
模型大小 312K 82K
运行性能 5.858854ms 1.439062ms

可见, Quantized 模型在模型大小和运行性能上相对 Float 模型都有非常大的提升。不过,在作者试验的过程中,发现有些图片在 Float 模型上识别正确的,在 Quantized 模型上会识别错,可见 Quantization 对模型的识别精度还是有影响的。在边缘设备上资源有限,需要在模型大小、运行速度与识别精度上找到一个权衡。

总结

本节介绍了如何从零开始部署 TFLite 到 Android 应用中,包括:

  1. 如何将训练好的 MNIST SavedModel 模型,转换为 Float 模型和 Quantized 模型

  2. 如何使用 visualize.py 和解读其结果信息

  3. 如何将转换后的模型部署到 Android 应用中

你可能感兴趣的:(Tensorflow)