• Tensorflow Lite从入门到精通


      TensorFlow Lite 是 TensorFlow 在移动和 IoT 等边缘设备端的解决方案,提供了 Java、Python 和 C++ API 库,可以运行在 Android、iOS 和 Raspberry Pi 等设备上。目前 TFLite 只提供了推理功能,在服务器端进行训练后,经过如下简单处理即可部署到边缘设备上。

    个人使用总结:

    1. 如果我们只使用Tensorflow的高级API搭建模型,那么将TF转TF Lite再转TF lite micro的过程会相对顺利。但是如果我们的模型使用了自定义模块,那么转换过程会遇到很多麻烦,Tensorflow对自家高级API的转换提供了很好的支持,但对我们自己写的一些NN 算子支持不佳。
    2. Tensorflow LSTM的流式部署是有难度的,请使用最新的Tensorflow版本,将unrool打开,再尝试

    转换模型

    我们可以通过以下两种方式将Tensorflow模型 转换成 TF Lite:

    1. Python API(推荐,且本文主讲):它让您可以更轻松地在模型开发流水线中转换模型、应用优化、添加元数据,并且拥有更多功能。
    2. 命令行:它仅支持基本模型转换。

    我们可以根据保存模型的方式来选择转换成TF lite的方式:

    1、tf.lite.TFLiteConverter.from_saved_model()(推荐):转换 SavedModel 

    import tensorflow as tf
    
    # 转换模型
    converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir) # SavedModel目录的路径
    tflite_model = converter.convert()
    
    # 保存TF Lite模型
    with open('model.tflite', 'wb') as f:
      f.write(tflite_model)
    View Code

    2、tf.lite.TFLiteConverter.from_keras_model():转换 Keras 模型  

    import tensorflow as tf
    
    # 使用高级API tf.keras.* 创建一个模型
    model = tf.keras.models.Sequential([
        tf.keras.layers.Dense(units=1, input_shape=[1]),
        tf.keras.layers.Dense(units=16, activation='relu'),
        tf.keras.layers.Dense(units=1)
    ])
    model.compile(optimizer='sgd', loss='mean_squared_error')  # 编译模型
    model.fit(x=[-1, 0, 1], y=[-3, -1, 1], epochs=5)  # 训练模型
    # 生成一个 SavedModel 
    # tf.saved_model.save(model, "saved_model_keras_dir")
    
    # keras model to TFLite
    converter = tf.lite.TFLiteConverter.from_keras_model(model)
    tflite_model = converter.convert()
    
    # 保存模型
    with open('model.tflite', 'wb') as f:
        f.write(tflite_model)
    View Code

    3、tf.lite.TFLiteConverter.from_concrete_functions():转换 具体函数

    import tensorflow as tf
    
    
    # 使用低级API tf.* 创建一个模型
    class Squared(tf.Module):
        @tf.function(input_signature=[tf.TensorSpec(shape=[None], dtype=tf.float32)])
        def __call__(self, x):
            return tf.square(x)
    
    
    model = Squared()
    # 运行模型
    # result = Squared(5.0) # 25.0
    # 生成 SavedModel
    # tf.saved_model.save(model, "saved_model_tf_dir")
    concrete_func = model.__call__.get_concrete_function()
    
    # TF model to TFLite
    # 注意,对于TensorFlow 2.7之前的版本,
    # from_concrete_functions API能够在只有第一个参数的情况下工作:
    # > converter = tf.lite.TFLiteConverter.from_concrete_functions([concrete_func])
    converter = tf.lite.TFLiteConverter.from_concrete_functions([concrete_func], model)
    tflite_model = converter.convert()
    
    # 保存TFLite模型
    with open('model.tflite', 'wb') as f:
        f.write(tflite_model)
    View Code

    转换RNN模型为TFLite

    由于 TensorFlow 中 RNN API 的变体很多,我们的转换方式包括两个方面:

    1. 为标准 TensorFlow RNN API(如 Keras LSTM)提供原生支持。这是推荐的选项。
    2. 提供 进入转换基础架构的接口,用于 插入用户定义的 RNN 实现并转换为 TensorFlow Lite。我们提供了几个有关此类转换的开箱即用的示例,这些示例使用的是 lingvo 的 LSTMCellSimple 和 LayerNormalizedLSTMCellSimple RNN 接口。

    Tensorflow官方提供了一个一个非常棒的案例:Keras LSTM fusion Codelab.ipynb

    运行推理

    TensorFlow Lite 推理通常遵循以下步骤:

    1. 加载模型
    2. 转换数据
    3. 运行推断
    4. 解释输出
    复制代码
    import numpy as np
    import tensorflow as tf
    
    # 加载TFLite模型并分配张量
    interpreter = tf.lite.Interpreter(model_path="converted_model.tflite")
    interpreter.allocate_tensors()
    
    input_details = interpreter.get_input_details()  # 输入
    output_details = interpreter.get_output_details()  # 输出
    
    input_shape = input_details[0]['shape']  # 获取输入的shape
    input_data = np.array(np.random.random_sample(input_shape), dtype=np.float32)
    
    interpreter.set_tensor(input_details[0]['index'], input_data)  # 输入给模型
    interpreter.invoke()  # 运行
    # 函数`get_tensor()`返回张量数据的副本
    # 使用`tensor()`来获得一个指向这个张量的指针
    output_data = interpreter.get_tensor(output_details[0]['index'])
    print(output_data)
    复制代码

    定义了Signature的TFLite 推理

    # -*- coding:utf-8 -*-
    # Author:凌逆战 | Never
    # Date: 2022/10/12
    """
    
    """
    import tensorflow as tf
    
    
    class TestModel(tf.Module):
        def __init__(self):
            super(TestModel, self).__init__()
    
        @tf.function(input_signature=[tf.TensorSpec(shape=[1, 10], dtype=tf.float32)])
        def add(self, x):
            # 为方便起见,将输出命名为“result”。
            return {'result': x + 4}
    
    
    saved_model_path = './saved_models'
    tflite_path = 'content/test_variable.tflite'
    
    # 保存模型
    model = TestModel()
    # 您可以省略signatures参数,并且将创建一个名为'serving_default'的默认签名名。
    tf.saved_model.save(model, saved_model_path,
                        signatures={'my_signature': model.add.get_concrete_function()})
    
    # TF Model to TFLite
    converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_path)
    tflite_model = converter.convert()
    with open(tflite_path, 'wb') as f:
        f.write(tflite_model)
    
    # 加载TFLite模型
    interpreter = tf.lite.Interpreter(tflite_path)
    # 模型中只定义了一个签名,因此默认情况下它将返回签名
    # 如果有多个签名,我们就可以传递这个名字
    my_signature = interpreter.get_signature_runner()
    
    # my_signature 可以通过输入作为参数调用
    output = my_signature(x=tf.constant([1.0], shape=(1, 10), dtype=tf.float32))
    # 'output'是包含推理所有输出的字典,在本例中,我们只有一个输出'result'。
    print(output['result'])
    View Code

    优化模型

      Tensorflow Lite 和 Tensorflow Model Optimization Toolkit (Tensorflow模型优化工具包)提供了最小优化推理复杂性的工具,可将优化推断的复杂性降至最低。深度神经网络的量化使用了一些技术,这些技术可以降低权重的精确表示,并且降低存储和计算。 TensorFlow Model Optimization Toolkit 目前支持通过量化剪枝聚类进行优化

    剪枝:剪枝的工作原理是移除模型中对其预测影响很小的参数。剪枝后的模型在磁盘上的大小相同,并且具有相同的运行时延迟,但可以更高效地压缩。这使剪枝成为缩减模型下载大小的实用技术

    未来,TensorFlow Lite 将降低剪枝后模型的延迟。

    聚类:聚类的工作原理是将模型中每一层的权重归入预定数量的聚类中,然后共享属于每个单独聚类的权重的质心值。这就减少了模型中唯一权重值的数量,从而降低了其复杂性。这样一来,就可以更高效地压缩聚类后的模型,从而提供类似于剪枝的部署优势。

    开发工作流程

    1. 首先,检查托管模型中的模型能否用于您的应用。如果不能,建议从训练后量化工具开始,因为它适用范围广,且无需训练数据。
    2. 对于无法达到准确率和延迟目标,或硬件加速器支持很重要的情况,量化感知训练是更好的选择。请参阅 TensorFlow Model Optimization Toolkit 下的其他优化技术。
    3. 如果要进一步缩减模型大小,可以在量化模型之前尝试剪枝和/或聚类。

    量化

      有两种形式的量化:训练后量化和量化感知训练。请从训练后量化开始,因为它更易于使用,尽管量化感知训练在模型准确率方面的表现通常更好。

    量化感知训练

    等我将代码进行了训练后量化后,再来整这个量化感知训练

    训练后量化

      量化的工作原理是降低模型参数的精度(默认情况为 32 位浮点数)。这样可以获得较小的模型大小和较快的计算速度。TensorFlow Lite 提供以下量化类型:

    技术数据要求大小缩减准确率
    训练后 Float16 量化 无数据 高达 50% 轻微的准确率损失
    训练后 动态范围量化 无数据 高达 75%,速度加快 2-3倍 极小的准确率损失
    训练后 int8 量化 无标签的代表性样本 高达 75%,速度加快3+倍 极小的准确率损失
    量化感知训练 带标签的训练数据 高达 75% 极小的准确率损失

      以下决策树可帮助您仅根据预期的模型大小和准确率来选择要用于模型的量化方案。

    动态范围量化

      权重(float32) 会在训练后量化为 整型(int8),激活会在推断时动态量化,模型大小缩减至原来的四分之一:

    TFLite 支持对激活进行动态量化(激活始终以浮点进行存储。对于支持量化内核的算子,激活会在处理前动态量化为 8 位精度,并在处理后反量化为浮点精度。根据被转换的模型,这可以提供比纯浮点计算更快的速度)以实现以下效果:

    1. 在可用时使用量化内核加快实现速度。
    2. 将计算图不同部分的浮点内核与量化内核混合。
    复制代码
    import tensorflow as tf
    
    converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
    # 启动默认的 optimizations 来量化所有固定参数(权重)
    converter.optimizations = [tf.lite.Optimize.DEFAULT] 
    tflite_quant_model = converter.convert()
    复制代码

    此优化提供的延迟接近全定点推断。但是,输出仍使用浮点进行存储因此使用动态范围算子的加速小于全定点计算

    我们来吃一个完整的栗子,构建一个MNIST模型,并且对它使用动态范围量化,对比量化前后的精度变化:

    # -*- coding:utf-8 -*-
    # Author:凌逆战 | Never
    # Date: 2022/10/12
    """
    动态范围量化
    """
    import logging
    
    logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    
    import tensorflow as tf
    from tensorflow import keras
    import numpy as np
    import pathlib
    
    # 加载MNIST数据集
    mnist = keras.datasets.mnist
    (train_images, train_labels), (test_images, test_labels) = mnist.load_data()
    
    # 归一化输入图像,使每个像素值在0到1之间。
    train_images = train_images / 255.0
    test_images = test_images / 255.0
    
    # 定义模型结构
    model = keras.Sequential([
        keras.layers.InputLayer(input_shape=(28, 28)),
        keras.layers.Reshape(target_shape=(28, 28, 1)),
        keras.layers.Conv2D(filters=12, kernel_size=(3, 3), activation=tf.nn.relu),
        keras.layers.MaxPooling2D(pool_size=(2, 2)),
        keras.layers.Flatten(),
        keras.layers.Dense(10)
    ])
    
    # 训练数字分类模型
    model.compile(optimizer='adam',
                  loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                  metrics=['accuracy'])
    model.fit(train_images, train_labels, epochs=1, validation_data=(test_images, test_labels))
    
    # TF model to TFLite
    converter = tf.lite.TFLiteConverter.from_keras_model(model)
    tflite_model = converter.convert()
    
    tflite_models_dir = pathlib.Path("./mnist_tflite_models/")
    tflite_models_dir.mkdir(exist_ok=True, parents=True)
    
    tflite_model_file = tflite_models_dir / "mnist_model.tflite"
    tflite_model_file.write_bytes(tflite_model)  # 84824
    # with open('model.tflite', 'wb') as f:
    #   f.write(tflite_model)
    
    # 量化模型 ------------------------------------
    converter.optimizations = [tf.lite.Optimize.DEFAULT]
    tflite_quant_model = converter.convert()
    tflite_model_quant_file = tflite_models_dir / "mnist_model_quant.tflite"
    tflite_model_quant_file.write_bytes(tflite_quant_model)  # 24072
    
    # 将模型加载到解释器中
    interpreter = tf.lite.Interpreter(model_path=str(tflite_model_file))
    interpreter.allocate_tensors()  # 分配张量
    
    interpreter_quant = tf.lite.Interpreter(model_path=str(tflite_model_quant_file))
    interpreter_quant.allocate_tensors()  # 分配张量
    
    # 在单个图像上测试模型
    test_image = np.expand_dims(test_images[0], axis=0).astype(np.float32)
    
    input_index = interpreter.get_input_details()[0]["index"]
    output_index = interpreter.get_output_details()[0]["index"]
    
    interpreter.set_tensor(input_index, test_image)
    interpreter.invoke()
    predictions = interpreter.get_tensor(output_index)
    print(predictions)
    
    
    # 使用“test”数据集评估TF Lite模型
    def evaluate_model(interpreter):
        input_index = interpreter.get_input_details()[0]["index"]
        output_index = interpreter.get_output_details()[0]["index"]
    
        # 对“test”数据集中的每个图像进行预测
        prediction_digits = []
        for test_image in test_images:
            # 预处理:添加batch维度并转换为float32以匹配模型的输入数据格式。
            test_image = np.expand_dims(test_image, axis=0).astype(np.float32)
            interpreter.set_tensor(input_index, test_image)
    
            interpreter.invoke()  # 运行推理
    
            # 后处理:去除批尺寸,找到概率最高的数字
            output = interpreter.tensor(output_index)
            digit = np.argmax(output()[0])
            prediction_digits.append(digit)
    
        # 将预测结果与ground truth 标签进行比较,计算精度。
        accurate_count = 0
        for index in range(len(prediction_digits)):
            if prediction_digits[index] == test_labels[index]:
                accurate_count += 1
        accuracy = accurate_count * 1.0 / len(prediction_digits)
    
        return accuracy
    
    
    print(evaluate_model(interpreter))  # 0.958
    print(evaluate_model(interpreter_quant))  # 0.9579
    View Code

    更多细节请参考:Tensorflow官方文档 训练后动态范围量化

    全整型量化

      整型量化是将32位浮点数 转换为8位定点数。这样可以缩减模型大小并加快推理速度,这对低功耗设备(如微控制器)很有价值。仅支持整数的加速器(如 Edge TPU)也需要使用此数据格式。

      对于全整数量化,需要校准或估算模型中所有浮点张量的范围,即 (min, max)。与权重和偏差等常量张量不同,模型输入、激活(中间层的输出)和模型输出等变量张量不能校准,除非我们运行几个推断周期。因此,转换器需要一个有代表性的数据集来校准它们。这个数据集可以是训练数据或验证数据的一个小子集(大约 100-500 个样本)。请参阅下面的  representative_dataset() 函数。

    从 TensorFlow 2.7 版本开始,您可以通过签名指定代表数据集,示例如下:

    def representative_dataset():
      for data in dataset:
        yield {
          "image": data.image,
          "bias": data.bias,
        }
    View Code

    如果给定的 TensorFlow 模型中有多个签名,则可以通过指定签名密钥来指定多个数据集:

    def representative_dataset():
      # Feed data set for the "encode" signature.
      for data in encode_signature_dataset:
        yield (
          "encode", {
            "image": data.image,
            "bias": data.bias,
          }
        )
    
      # Feed data set for the "decode" signature.
      for data in decode_signature_dataset:
        yield (
          "decode", {
            "image": data.image,
            "hint": data.hint,
          },
        )
    View Code

    您可以通过提供输入张量列表来生成代表性数据集:

    def representative_dataset():
      for data in tf.data.Dataset.from_tensor_slices((images)).batch(1).take(100):
        yield [tf.dtypes.cast(data, tf.float32)]
    View Code

      从 TensorFlow 2.7 版本开始,我们推荐使用基于签名的方法,而不是基于输入张量列表的方法,因为输入张量排序可以很容易地翻转。

    出于测试目的,您可以使用如下所示的虚拟数据集:

    def representative_dataset():
        for _ in range(100):
          data = np.random.rand(1, 244, 244, 3)
          yield [data.astype(np.float32)]
    View Code

    1、模型整型量化,输入输出还是浮点

      启动默认的 optimizations 只能量化固定参数(如:权重),但是模型输入/输出 和层之间的状态值 依然是浮点型的,要量化可变中间值,您需要提供 RepresentativeDataset。这是一个生成器函数,它提供一组足够大的输入数据来代表典型值。converter 可以通过该函数估算所有可变数据的动态范围(相比训练或评估数据集,此数据集不必唯一)为了支持多个输入,每个代表性数据点都是一个列表,并且列表中的元素会根据其索引被馈送到模型。

    复制代码
    import tensorflow as tf
    
    converter = tf.lite.TFLiteConverter.from_keras_model(model)
    # 启动默认的 optimizations 来量化所有固定参数(权重)
    converter.optimizations = [tf.lite.Optimize.DEFAULT]
    # 要量化可变数据(模型输入/输出 和层之间的中间体),提供 RepresentativeDataset,来估算所有可变数据的动态范围
    converter.representative_dataset = representative_data_gen
    tflite_model_quant = converter.convert()
    复制代码

    现在,所有权重和可变数据都已量化,并且与原始 TensorFlow Lite 模型相比,该模型要小得多。

    但是,为了与传统上使用浮点模型输入和输出张量的应用保持兼容,TensorFlow Lite 转换器将模型的输入和输出张量保留为浮点,这通常对兼容性有利,但它无法兼容执行全整形运算的设备(如 Edge TPU)。

    interpreter = tf.lite.Interpreter(model_content=tflite_model_quant)
    input_type = interpreter.get_input_details()[0]['dtype']
    print('input: ', input_type)    # 
    output_type = interpreter.get_output_details()[0]['dtype']
    print('output: ', output_type)  # 

    2、全整型量化

      为了量化输入和输出张量,我们需要使用一些附加参数再次转换模型:

    复制代码
    converter = tf.lite.TFLiteConverter.from_keras_model(model)
    converter.optimizations = [tf.lite.Optimize.DEFAULT]    # 先启动默认的optimizations将模型权重进行量化
    converter.representative_dataset = representative_data_gen  # 使用代表数据集量化模型中间值
    # 如果有任何的 ops不能量化,converter 将抛出错误
    converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] 
    # 将输入和输出tensors设置为int8 类型
    converter.inference_input_type = tf.int8   # or tf.uint8
    converter.inference_output_type = tf.int8  # or tf.uint8
    
    tflite_model_quant = converter.convert()
    复制代码

    现在我们可以看到输入和输出张量现在是整数格式:

    interpreter = tf.lite.Interpreter(model_content=tflite_model_quant)
    input_type = interpreter.get_input_details()[0]['dtype']
    print('input: ', input_type)    # 
    output_type = interpreter.get_output_details()[0]['dtype']
    print('output: ', output_type)  # 

    我们来整一个栗子,您将从头开始训练一个 MNIST 模型、将其转换为 TensorFlow Lite 文件,并使用训练后整型量化。最后,将检查转换后模型的准确率并将其与原始浮点模型进行比较。

    # -*- coding:utf-8 -*-
    # Author:凌逆战 | Never
    # Date: 2022/10/12
    """
    整型量化
    """
    import logging
    
    # 只显示bug不显示 warning
    logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    
    import tensorflow as tf
    import numpy as np
    
    # 加载 MNIST 数据集
    mnist = tf.keras.datasets.mnist
    (train_images, train_labels), (test_images, test_labels) = mnist.load_data()
    
    # Normalize 输入图像,使每个像素值在0到1之间
    train_images = train_images.astype(np.float32) / 255.0
    test_images = test_images.astype(np.float32) / 255.0
    
    # 搭建模型结构
    model = tf.keras.Sequential([
        tf.keras.layers.InputLayer(input_shape=(28, 28)),
        tf.keras.layers.Reshape(target_shape=(28, 28, 1)),
        tf.keras.layers.Conv2D(filters=12, kernel_size=(3, 3), activation='relu'),
        tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(10)
    ])
    
    model.compile(optimizer='adam',
                  loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                  metrics=['accuracy'])
    model.fit(train_images, train_labels, epochs=5, validation_data=(test_images, test_labels))
    
    # 下面是一个没有量化的转换后模型 -------------------------------
    converter = tf.lite.TFLiteConverter.from_keras_model(model)
    tflite_model = converter.convert()
    
    
    # 下面是全整型量化 -----------------------------------------
    def representative_data_gen():
        for input_value in tf.data.Dataset.from_tensor_slices(train_images).batch(1).take(100):
            # 模型只有一个输入,因此每个数据点有一个元素
            yield [input_value]
    
    
    converter = tf.lite.TFLiteConverter.from_keras_model(model)
    converter.optimizations = [tf.lite.Optimize.DEFAULT]  # 先启动默认的optimizations将模型权重进行量化
    converter.representative_dataset = representative_data_gen  # 使用代表数据集量化模型中间值
    # 如果有任何的 ops不能量化,converter 将抛出错误
    converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
    # 将输入和输出tensors设置为int8 类型
    converter.inference_input_type = tf.uint8  # or tf.int8
    converter.inference_output_type = tf.uint8  # or tf.int8
    
    tflite_model_quant = converter.convert()
    
    import pathlib
    
    tflite_models_dir = pathlib.Path("./mnist_tflite_models/")
    tflite_models_dir.mkdir(exist_ok=True, parents=True)
    
    # Save the unquantized/float model:
    tflite_model_file = tflite_models_dir / "mnist_model.tflite"
    tflite_model_file.write_bytes(tflite_model)
    # Save the quantized model:
    tflite_model_quant_file = tflite_models_dir / "mnist_model_quant.tflite"
    tflite_model_quant_file.write_bytes(tflite_model_quant)
    
    
    # 在TFLite模型上运行推理
    def run_tflite_model(tflite_file, test_image_indices):
        global test_images
    
        # 初始化 Interpreter
        interpreter = tf.lite.Interpreter(model_path=str(tflite_file))
        interpreter.allocate_tensors()  # 分配张量
    
        input_details = interpreter.get_input_details()[0]  # 输入
        output_details = interpreter.get_output_details()[0]  # 输出
    
        predictions = np.zeros((len(test_image_indices),), dtype=int)
        for i, test_image_index in enumerate(test_image_indices):
            test_image = test_images[test_image_index]
            test_label = test_labels[test_image_index]
    
            # 检查输入类型是否被量化,然后将输入数据缩放到uint8
            if input_details['dtype'] == np.uint8:
                input_scale, input_zero_point = input_details["quantization"]
                test_image = test_image / input_scale + input_zero_point
    
            test_image = np.expand_dims(test_image, axis=0).astype(input_details["dtype"])
            interpreter.set_tensor(input_details["index"], test_image)
            interpreter.invoke()
            output = interpreter.get_tensor(output_details["index"])[0]
    
            predictions[i] = output.argmax()
    
        return predictions
    
    
    # 在所有图像上评估一个TFLite模型
    def evaluate_model(tflite_file, model_type):
        global test_images
        global test_labels
    
        test_image_indices = range(test_images.shape[0])
        predictions = run_tflite_model(tflite_file, test_image_indices)
    
        accuracy = (np.sum(test_labels == predictions) * 100) / len(test_images)
    
        print('%s model accuracy is %.4f%% (Number of test samples=%d)' % (
            model_type, accuracy, len(test_images)))
    
    
    evaluate_model(tflite_model_file, model_type="Float")
    # Float model accuracy is 97.7500% (Number of test samples=10000)
    evaluate_model(tflite_model_quant_file, model_type="Quantized")
    # Quantized model accuracy is 97.7000% (Number of test samples=10000)
    View Code

    更多细节请参考:Tensorflow官方文档训练后整型量化

    float16量化

      现在,TensorFlow Lite 支持在模型从 TensorFlow 转换到 TensorFlow Lite FlatBuffer 格式期间将权重转换为 16 位浮点值。这样可以将模型的大小缩减至原来的二分之一。某些硬件(如 GPU)可以在这种精度降低的算术中以原生方式计算,从而实现比传统浮点执行更快的速度。可以将 Tensorflow Lite GPU 委托配置为以这种方式运行。但是,转换为 float16 权重的模型仍可在 CPU 上运行而无需其他修改:float16 权重会在首次推理前上采样为 float32。这样可以在对延迟和准确率造成最小影响的情况下显著缩减模型大小。

    float16 量化的优点如下:

    • 将模型的大小缩减一半(因为所有权重都变成其原始大小的一半)。
    • 实现最小的准确率损失。
    • 支持可直接对 float16 数据进行运算的部分委托(例如 GPU 委托),从而使执行速度比 float32 计算更快。

    float16 量化的缺点如下:

    • 它不像对定点数学进行量化那样减少那么多延迟。
    • 默认情况下,float16 量化模型在 CPU 上运行时会将权重值“反量化”为 float32。(请注意,GPU 委托不会执行此反量化,因为它可以对 float16 数据进行运算)
    复制代码
    import tensorflow as tf
    
    converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
    converter.optimizations = [tf.lite.Optimize.DEFAULT]
    converter.target_spec.supported_types = [tf.float16]
    tflite_quant_model = converter.convert()
    复制代码

      在本教程中,您将从头开始训练一个 MNIST 模型,并在 TensorFlow 中检查其准确率,然后使用 float16 量化将此模型转换为 Tensorflow Lite FlatBuffer 格式。最后,检查转换后模型的准确率,并将其与原始 float32 模型进行比较。

    # -*- coding:utf-8 -*-
    # Author:凌逆战 | Never
    # Date: 2022/10/12
    """
    
    """
    import logging
    
    logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    
    import tensorflow as tf
    from tensorflow import keras
    import numpy as np
    import pathlib
    
    # Load MNIST dataset
    mnist = keras.datasets.mnist
    (train_images, train_labels), (test_images, test_labels) = mnist.load_data()
    
    # Normalize the input image so that each pixel value is between 0 to 1.
    train_images = train_images / 255.0
    test_images = test_images / 255.0
    
    # Define the model architecture
    model = keras.Sequential([
        keras.layers.InputLayer(input_shape=(28, 28)),
        keras.layers.Reshape(target_shape=(28, 28, 1)),
        keras.layers.Conv2D(filters=12, kernel_size=(3, 3), activation=tf.nn.relu),
        keras.layers.MaxPooling2D(pool_size=(2, 2)),
        keras.layers.Flatten(),
        keras.layers.Dense(10)
    ])
    
    # Train the digit classification model
    model.compile(optimizer='adam',
                  loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                  metrics=['accuracy'])
    model.fit(train_images, train_labels, epochs=1, validation_data=(test_images, test_labels))
    
    converter = tf.lite.TFLiteConverter.from_keras_model(model)
    tflite_model = converter.convert()
    tflite_models_dir = pathlib.Path("./mnist_tflite_models/")
    tflite_models_dir.mkdir(exist_ok=True, parents=True)
    tflite_model_file = tflite_models_dir / "mnist_model.tflite"
    tflite_model_file.write_bytes(tflite_model)
    
    # float16 量化 ----------------------------------------------
    converter.optimizations = [tf.lite.Optimize.DEFAULT]
    converter.target_spec.supported_types = [tf.float16]
    tflite_fp16_model = converter.convert()  # 转换为TFLite
    tflite_model_fp16_file = tflite_models_dir / "mnist_model_quant_f16.tflite"
    tflite_model_fp16_file.write_bytes(tflite_fp16_model)
    
    # 将模型加载到解释器中 ------------------------------
    # 没有量化的 TFlite
    interpreter = tf.lite.Interpreter(model_path=str(tflite_model_file))
    interpreter.allocate_tensors()
    # float16 量化的 TFLite
    interpreter_fp16 = tf.lite.Interpreter(model_path=str(tflite_model_fp16_file))
    interpreter_fp16.allocate_tensors()
    
    
    # 使用 "test" 数据集评估TF Lite模型
    def evaluate_model(interpreter):
        input_index = interpreter.get_input_details()[0]["index"]
        output_index = interpreter.get_output_details()[0]["index"]
    
        # 对“test”数据集中的每个图像进行预测
        prediction_digits = []
        for test_image in test_images:
            # 预处理: 添加batch维度并转换为float32以匹配模型的输入数据格式
            test_image = np.expand_dims(test_image, axis=0).astype(np.float32)
            interpreter.set_tensor(input_index, test_image)
    
            interpreter.invoke()  # 运行推理
    
            # 后处理:去除batch维度 找到概率最高的数字
            output = interpreter.tensor(output_index)
            digit = np.argmax(output()[0])
            prediction_digits.append(digit)
    
        # 将预测结果与ground truth 标签进行比较,计算精度。
        accurate_count = 0
        for index in range(len(prediction_digits)):
            if prediction_digits[index] == test_labels[index]:
                accurate_count += 1
        accuracy = accurate_count * 1.0 / len(prediction_digits)
    
        return accuracy
    
    
    print(evaluate_model(interpreter))  # 0.9662
    # NOTE: Colab运行在服务器的cpu上。
    # 在写这篇文章的时候,TensorFlow Lite还没有超级优化的服务器CPU内核。
    # 由于这个原因,它可能比上面的float interpreter要慢
    # 但是对于mobile CPUs,可以观察到相当大的加速
    print(evaluate_model(interpreter_fp16))  # 0.9662
    View Code

    仅整数:具有 8 位权重的 16 位激活(实验性)

    这是一个实验性量化方案。它与“仅整数”方案类似,但会根据激活的范围将其量化为 16 位,权重会被量化为 8 位整数,偏差会被量化为 64 位整数。这被进一步称为 16x8 量化。

    这种量化的主要优点是可以显著提高准确率,但只会稍微增加模型大小。

    复制代码
    import tensorflow as tf
    
    converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
    converter.representative_dataset = representative_dataset
    converter.optimizations = [tf.lite.Optimize.DEFAULT]
    converter.target_spec.supported_ops = [tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8]
    tflite_quant_model = converter.convert()
    复制代码

    如果模型中的部分算子不支持 16x8 量化,模型仍然可以量化,但不受支持的算子会保留为浮点。要允许此操作,应将以下选项添加到 target_spec 中。

    复制代码
    import tensorflow as tf
    
    converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
    converter.representative_dataset = representative_dataset
    converter.optimizations = [tf.lite.Optimize.DEFAULT]
    converter.target_spec.supported_ops = [tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8,
                                           tf.lite.OpsSet.TFLITE_BUILTINS]
    tflite_quant_model = converter.convert()
    复制代码

    这种量化的缺点是:

    • 由于缺少优化的内核实现,目前的推断速度明显比 8 位全整数慢。
    • 目前它不兼容现有的硬件加速 TFLite 委托。

    注:这是一项实验性功能。

    可以在此处找到该量化模型的教程。

    模型准确率

    由于权重是在训练后量化的,因此可能会造成准确率损失,对于较小的网络尤其如此。TensorFlow Hub 为特定网络提供了预训练的完全量化模型。请务必检查量化模型的准确率,以验证准确率的任何下降都在可接受的范围内。有一些工具可以评估 TensorFlow Lite 模型准确率

    另外,如果准确率下降过多,请考虑使用量化感知训练。但是,这样做需要在模型训练期间进行修改以添加伪量化节点,而此页面上的训练后量化技术使用的是现有的预训练模型。

     

     

     

    参考 

    【TensorFlow官方】TensorFlow Lite 指南 

    作者:凌逆战
    欢迎任何形式的转载,但请务必注明出处。
    限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
    本文章不做任何商业用途,仅作为自学所用,文章后面会有参考链接,我可能会复制原作者的话,如果介意,我会修改或者删除。

  • 相关阅读:
    Mybatis @MapKey注解返回指定Map源码解析与用例
    拆解一下任务队列、消息队列、任务调度系统
    服务器中毒了——菜是原罪
    前端 按钮 loading效果阻断不了快速点击,执行防抖操作进行阻断接口连续调用
    vue3使用echarts5.3.3无法正常使用折线图
    Java-GUI 编程之 Swing
    JavaSE | 初识Java(二) | 数据类型与变量
    代码随想录 Day7 字符串1 LeetCode T344反转字符串 T541 反转字符串II 151翻转字符串的单词
    《德米安》从那以后伤口很痛,但偶尔我会找到钥匙,沉入心底
    数据结构01 栈及其相关问题讲解【C++实现】
  • 原文地址:https://www.cnblogs.com/LXP-Never/p/16786223.html