Quantification d'entiers post-apprentissage

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub Télécharger le cahier

Aperçu

La quantification entière est une stratégie d'optimisation qui convertit les nombres à virgule flottante 32 bits (tels que les poids et les sorties d'activation) en nombres à virgule fixe 8 bits les plus proches. Cela se traduit par un modèle plus petit et plus la vitesse d' inférence, ce qui est valable pour les appareils de faible puissance tels que les micro - contrôleurs . Ce format de données est également nécessaire par seul entier des accélérateurs tels que le bord TPU .

Dans ce tutoriel, vous formez un modèle MNIST à partir de zéro, le convertir en un fichier Lite tensorflow et quantifiez à l'aide d'une quantification post-formation . Enfin, vous vérifierez la précision du modèle converti et le comparerez au modèle flottant d'origine.

Vous avez en fait plusieurs options quant à la quantité que vous souhaitez quantifier un modèle. Dans ce didacticiel, vous effectuerez une "quantification d'entiers complets", qui convertit tous les poids et sorties d'activation en données d'entiers de 8 bits, alors que d'autres stratégies peuvent laisser une certaine quantité de données en virgule flottante.

Pour en savoir plus sur les différentes stratégies de quantification, lisez tensorflow optimisation modèle Lite .

Installer

Afin de quantifier à la fois les tenseurs d'entrée et de sortie, nous devons utiliser les API ajoutées dans 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

Générer un modèle TensorFlow

Nous allons construire un modèle simple à numéros Classifier du jeu de données MNIST .

Cette formation ne prendra pas longtemps car vous entraînez le modèle pour seulement 5 époques, ce qui correspond à une précision d'environ 98%.

# 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>

Convertir en un modèle TensorFlow Lite

Maintenant , vous pouvez convertir le modèle formé pour tensorflow le format Lite à l' aide TFLiteConverter API et différents degrés de quantification.

Attention, certaines versions de quantification laissent certaines données au format flottant. Ainsi, les sections suivantes montrent chaque option avec des quantités croissantes de quantification, jusqu'à ce que nous obtenions un modèle entièrement composé de données int8 ou uint8. (Remarquez que nous dupliquons du code dans chaque section afin que vous puissiez voir toutes les étapes de quantification pour chaque option.)

Tout d'abord, voici un modèle converti sans quantification :

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.

Il s'agit désormais d'un modèle TensorFlow Lite, mais il utilise toujours des valeurs flottantes 32 bits pour toutes les données de paramètres.

Convertir à l'aide de la quantification de plage dynamique

Maintenant , nous allons permettre au défaut d' optimizations drapeau pour quantifier tous les paramètres fixes (comme les poids):

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.

Le modèle est maintenant un peu plus petit avec des poids quantifiés, mais les autres données variables sont toujours au format flottant.

Convertir à l'aide de la quantification de secours flottante

Pour quantifier les données variables (comme entrée du modèle / sortie et intermédiaires entre les couches), vous devez fournir un RepresentativeDataset . Il s'agit d'une fonction génératrice qui fournit un ensemble de données d'entrée suffisamment volumineux pour représenter des valeurs typiques. Il permet au convertisseur d'estimer une plage dynamique pour toutes les données variables. (L'ensemble de données n'a pas besoin d'être unique par rapport à l'ensemble de données d'apprentissage ou d'évaluation.) Pour prendre en charge plusieurs entrées, chaque point de données représentatif est une liste et les éléments de la liste sont transmis au modèle en fonction de leurs indices.

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

Désormais, tous les poids et données variables sont quantifiés, et le modèle est nettement plus petit que le modèle TensorFlow Lite d'origine.

Cependant, pour maintenir la compatibilité avec les applications qui utilisent traditionnellement des tenseurs d'entrée et de sortie de modèle flottant, le convertisseur TensorFlow Lite laisse les tenseurs d'entrée et de sortie du modèle en flottant :

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'>

C'est généralement bon pour la compatibilité, mais cela ne sera pas compatible avec les appareils qui effectuent uniquement des opérations basées sur des nombres entiers, tels que Edge TPU.

De plus, le processus ci-dessus peut laisser une opération au format flottant si TensorFlow Lite n'inclut pas d'implémentation quantifiée pour cette opération. Cette stratégie permet à la conversion de se terminer afin que vous disposiez d'un modèle plus petit et plus efficace, mais encore une fois, il ne sera pas compatible avec le matériel à nombre entier uniquement. (Toutes les opérations de ce modèle MNIST ont une implémentation quantifiée.)

Donc, pour garantir un modèle entier uniquement de bout en bout, vous avez besoin de quelques paramètres supplémentaires...

Convertir à l'aide d'une quantification entière uniquement

Pour quantifier les tenseurs d'entrée et de sortie et faire en sorte que le convertisseur génère une erreur s'il rencontre une opération qu'il ne peut pas quantifier, convertissez à nouveau le modèle avec quelques paramètres supplémentaires :

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.

La quantification interne reste la même que ci-dessus, mais vous pouvez voir que les tenseurs d'entrée et de sortie sont désormais au format entier :

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'>

Maintenant , vous avez un modèle entier qui quantifiée des utilisations entières des données pour l'entrée du modèle et les tenseurs de sortie, il est donc compatible avec le matériel entier uniquement comme le bord TPU .

Enregistrer les modèles sous forme de fichiers

Vous aurez besoin d' un .tflite fichier pour déployer votre modèle sur d' autres appareils. Enregistrons donc les modèles convertis dans des fichiers, puis chargeons-les lorsque nous exécutons les inférences ci-dessous.

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

Exécuter les modèles TensorFlow Lite

Maintenant , nous allons lancer des déductions en utilisant le tensorflow Lite Interpreter pour comparer les exactitudes du modèle.

Tout d'abord, nous avons besoin d'une fonction qui exécute l'inférence avec un modèle et des images donnés, puis renvoie les prédictions :

# 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

Testez les modèles sur une image

Nous allons maintenant comparer les performances du modèle flottant et du modèle quantifié :

  • tflite_model_file est le modèle tensorflow Lite d' origine avec des données en virgule flottante.
  • tflite_model_quant_file est le dernier modèle , nous avons converti en utilisant seulement des nombres entiers de quantification (il utilise des données uint8 pour l' entrée et la sortie).

Créons une autre fonction pour imprimer nos prédictions :

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)

Testez maintenant le modèle flottant :

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

png

Et testez le modèle quantifié :

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

png

Évaluer les modèles sur toutes les images

Exécutons maintenant les deux modèles en utilisant toutes les images de test que nous avons chargées au début de ce didacticiel :

# 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)))

Évaluez le modèle flottant :

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

Évaluer le modèle quantifié :

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

Vous avez donc maintenant un modèle quantifié par un entier avec presque aucune différence de précision par rapport au modèle flottant.

Pour en savoir plus sur d' autres stratégies de quantification, lisez tensorflow optimisation modèle Lite .