Quantização inteira pós-treinamento

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Visão geral

A quantização inteira é uma estratégia de otimização que converte números de ponto flutuante de 32 bits (como pesos e saídas de ativação) para os números de ponto fixo de 8 bits mais próximos. Isso resulta em um modelo menor e maior velocidade de inferência, que é valioso para dispositivos de baixa potência tais como microcontroladores . Este formato de dados também é exigido por inteiro só de aceleradores, tais como a borda TPU .

Neste tutorial, você vai treinar um modelo MNIST a partir do zero, convertê-lo em um arquivo Tensorflow Lite, e quantizar-lo usando quantização pós-treinamento . Por fim, você verificará a precisão do modelo convertido e o comparará com o modelo flutuante original.

Na verdade, você tem várias opções de quanto deseja quantizar um modelo. Neste tutorial, você realizará a "quantização inteira completa", que converte todos os pesos e saídas de ativação em dados inteiros de 8 bits - enquanto outras estratégias podem deixar alguma quantidade de dados em ponto flutuante.

Para saber mais sobre as várias estratégias de quantização, leia sobre TensorFlow Lite otimização modelo .

Configurar

Para quantizar os tensores de entrada e saída, precisamos usar APIs adicionadas no TensorFlow r2.3:

import logging
logging.getLogger("tensorflow").setLevel(logging.DEBUG)

import tensorflow as tf
import numpy as np
assert float(tf.__version__[:3]) >= 2.3

Gerar um modelo TensorFlow

Vamos construir um modelo simples de números Classificar a partir do conjunto de dados MNIST .

Este treinamento não demorará muito porque você está treinando o modelo por apenas 5 épocas, o que treina com cerca de 98% de precisão.

# Load MNIST dataset
mnist = tf.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.astype(np.float32) / 255.0
test_images = test_images.astype(np.float32) / 255.0

# Define the model architecture
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)
])

# Train the digit classification model
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)
)
Epoch 1/5
1875/1875 [==============================] - 5s 2ms/step - loss: 0.2519 - accuracy: 0.9311 - val_loss: 0.1106 - val_accuracy: 0.9664
Epoch 2/5
1875/1875 [==============================] - 3s 2ms/step - loss: 0.0984 - accuracy: 0.9724 - val_loss: 0.0828 - val_accuracy: 0.9743
Epoch 3/5
1875/1875 [==============================] - 3s 2ms/step - loss: 0.0746 - accuracy: 0.9785 - val_loss: 0.0640 - val_accuracy: 0.9795
Epoch 4/5
1875/1875 [==============================] - 3s 2ms/step - loss: 0.0620 - accuracy: 0.9814 - val_loss: 0.0620 - val_accuracy: 0.9793
Epoch 5/5
1875/1875 [==============================] - 3s 2ms/step - loss: 0.0540 - accuracy: 0.9837 - val_loss: 0.0624 - val_accuracy: 0.9795
<keras.callbacks.History at 0x7fb44c988c90>

Converter para um modelo TensorFlow Lite

Agora você pode converter o modelo treinado para o formato TensorFlow Lite usando o TFLiteConverter API, e aplicar vários graus de quantização.

Esteja ciente de que algumas versões de quantização deixam alguns dos dados em formato flutuante. Portanto, as seções a seguir mostram cada opção com quantidades crescentes de quantização, até obter um modelo que é inteiramente int8 ou uint8. (Observe que duplicamos algum código em cada seção para que você possa ver todas as etapas de quantização para cada opção.)

Primeiro, aqui está um modelo convertido sem quantização:

converter = tf.lite.TFLiteConverter.from_keras_model(model)

tflite_model = converter.convert()
2021-10-30 12:04:56.623151: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
INFO:tensorflow:Assets written to: /tmp/tmp3os2tr3n/assets
2021-10-30 12:04:57.031317: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:351] Ignored output_format.
2021-10-30 12:04:57.031355: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:354] Ignored drop_control_dependency.

Agora é um modelo TensorFlow Lite, mas ainda usa valores flutuantes de 32 bits para todos os dados de parâmetro.

Converter usando quantização de faixa dinâmica

Agora vamos permitir que o padrão optimizations bandeira para quantificar todos os parâmetros fixos (tais como pesos):

converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]

tflite_model_quant = converter.convert()
INFO:tensorflow:Assets written to: /tmp/tmpi7xibvaj/assets
INFO:tensorflow:Assets written to: /tmp/tmpi7xibvaj/assets
2021-10-30 12:04:57.597982: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:351] Ignored output_format.
2021-10-30 12:04:57.598020: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:354] Ignored drop_control_dependency.

O modelo agora é um pouco menor com pesos quantizados, mas outros dados variáveis ​​ainda estão em formato flutuante.

Converter usando quantização de fallback de flutuação

Para quantificar os dados variáveis de entrada (tais como modelo / saída e intermediários entre as camadas), é necessário fornecer um RepresentativeDataset . Esta é uma função geradora que fornece um conjunto de dados de entrada grande o suficiente para representar valores típicos. Ele permite que o conversor estime uma faixa dinâmica para todos os dados variáveis. (O conjunto de dados não precisa ser exclusivo em comparação com o conjunto de dados de treinamento ou avaliação.) Para oferecer suporte a várias entradas, cada ponto de dados representativo é uma lista e os elementos da lista são fornecidos ao modelo de acordo com seus índices.

def representative_data_gen():
  for input_value in tf.data.Dataset.from_tensor_slices(train_images).batch(1).take(100):
    # Model has only one input so each data point has one element.
    yield [input_value]

converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_data_gen

tflite_model_quant = converter.convert()
INFO:tensorflow:Assets written to: /tmp/tmp3gwloj7n/assets
INFO:tensorflow:Assets written to: /tmp/tmp3gwloj7n/assets
2021-10-30 12:04:58.159142: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:351] Ignored output_format.
2021-10-30 12:04:58.159181: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:354] Ignored drop_control_dependency.
fully_quantize: 0, inference_type: 6, input_inference_type: 0, output_inference_type: 0

Agora todos os pesos e dados variáveis ​​são quantizados, e o modelo é significativamente menor em comparação com o modelo TensorFlow Lite original.

No entanto, para manter a compatibilidade com aplicativos que tradicionalmente usam tensores de entrada e saída do modelo flutuante, o TensorFlow Lite Converter deixa os tensores de entrada e saída do modelo flutuantes:

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)
input:  <class 'numpy.float32'>
output:  <class 'numpy.float32'>

Isso geralmente é bom para compatibilidade, mas não será compatível com dispositivos que executam apenas operações baseadas em inteiros, como o Edge TPU.

Além disso, o processo acima pode deixar uma operação em formato flutuante se o TensorFlow Lite não incluir uma implementação quantizada para essa operação. Essa estratégia permite que a conversão seja concluída para que você tenha um modelo menor e mais eficiente, mas, novamente, não será compatível com hardware somente inteiro. (Todas as operações neste modelo MNIST têm uma implementação quantizada.)

Portanto, para garantir um modelo somente inteiro de ponta a ponta, você precisa de mais alguns parâmetros ...

Converter usando quantização somente de inteiros

Para quantizar os tensores de entrada e saída e fazer o conversor lançar um erro se encontrar uma operação que não pode quantizar, converta o modelo novamente com alguns parâmetros adicionais:

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]
converter.representative_dataset = representative_data_gen
# Ensure that if any ops can't be quantized, the converter throws an error
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
# Set the input and output tensors to uint8 (APIs added in r2.3)
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8

tflite_model_quant = converter.convert()
INFO:tensorflow:Assets written to: /tmp/tmp8ygc2_3y/assets
INFO:tensorflow:Assets written to: /tmp/tmp8ygc2_3y/assets
2021-10-30 12:04:59.308505: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:351] Ignored output_format.
2021-10-30 12:04:59.308542: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:354] Ignored drop_control_dependency.
fully_quantize: 0, inference_type: 6, input_inference_type: 3, output_inference_type: 3
WARNING:absl:For model inputs containing unsupported operations which cannot be quantized, the `inference_input_type` attribute will default to the original type.

A quantização interna permanece igual à anterior, mas você pode ver que os tensores de entrada e saída agora estão no formato inteiro:

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)
input:  <class 'numpy.uint8'>
output:  <class 'numpy.uint8'>

Agora você tem um modelo inteiro quantizado que usos inteiros de dados para tensores de entrada e saída do modelo, por isso é compatível com o hardware somente inteiro tal como o Borda TPU .

Salve os modelos como arquivos

Você vai precisar de uma .tflite arquivo para implantar seu modelo em outros dispositivos. Portanto, vamos salvar os modelos convertidos em arquivos e carregá-los quando fizermos as inferências abaixo.

import pathlib

tflite_models_dir = pathlib.Path("/tmp/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)
24280

Execute os modelos TensorFlow Lite

Agora vamos executar inferências usando o TensorFlow Lite Interpreter para comparar as precisões modelo.

Primeiro, precisamos de uma função que execute inferência com um determinado modelo e imagens e, em seguida, retorne as previsões:

# Helper function to run inference on a TFLite model
def run_tflite_model(tflite_file, test_image_indices):
  global test_images

  # Initialize the 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]

    # Check if the input type is quantized, then rescale input data to 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

Teste os modelos em uma imagem

Agora vamos comparar o desempenho do modelo flutuante e do modelo quantizado:

  • tflite_model_file é o modelo original TensorFlow Lite com dados de ponto flutuante.
  • tflite_model_quant_file é o último modelo que convertido usando quantização somente inteiro (que utiliza dados uint8 para entrada e saída).

Vamos criar outra função para imprimir nossas previsões:

import matplotlib.pylab as plt

# Change this to test a different image
test_image_index = 1

## Helper function to test the models on one image
def test_model(tflite_file, test_image_index, model_type):
  global test_labels

  predictions = run_tflite_model(tflite_file, [test_image_index])

  plt.imshow(test_images[test_image_index])
  template = model_type + " Model \n True:{true}, Predicted:{predict}"
  _ = plt.title(template.format(true= str(test_labels[test_image_index]), predict=str(predictions[0])))
  plt.grid(False)

Agora teste o modelo flutuante:

test_model(tflite_model_file, test_image_index, model_type="Float")

png

E teste o modelo quantizado:

test_model(tflite_model_quant_file, test_image_index, model_type="Quantized")

png

Avalie os modelos em todas as imagens

Agora vamos executar os dois modelos usando todas as imagens de teste que carregamos no início deste tutorial:

# Helper function to evaluate a TFLite model on all images
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)))

Avalie o modelo flutuante:

evaluate_model(tflite_model_file, model_type="Float")
Float model accuracy is 97.9500% (Number of test samples=10000)

Avalie o modelo quantizado:

evaluate_model(tflite_model_quant_file, model_type="Quantized")
Quantized model accuracy is 97.9300% (Number of test samples=10000)

Portanto, agora você tem um modelo quantizado inteiro com quase nenhuma diferença na precisão, em comparação com o modelo flutuante.

Para saber mais sobre outras estratégias de quantização, leia sobre TensorFlow Lite otimização modelo .