Kwantyzacja liczb całkowitych po treningu

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Przegląd

Kwantyzacja liczb całkowitych to strategia optymalizacji, która konwertuje 32-bitowe liczby zmiennoprzecinkowe (takie jak wagi i wyjścia aktywacji) na najbliższe 8-bitowe liczby stałoprzecinkowe. Skutkuje to mniejszym modelu i zwiększona prędkość wnioskowania, co jest cenne dla urządzeń o niskim poborze mocy, takich jak mikrokontrolery . Ten format danych jest również wymagane przez całkowitą tylko akceleratorów, takich jak krawędzi TPU .

W tym ćwiczeniu będziesz trenować model MNIST od podstaw, przekształcić go w pliku Tensorflow Lite i kwantowania go za pomocą post-szkoleniowy kwantyzacji . Na koniec sprawdzisz dokładność przekonwertowanego modelu i porównasz go z oryginalnym modelem swobodnym.

W rzeczywistości masz kilka opcji, jak bardzo chcesz skwantyzować model. W tym samouczku wykonasz „pełną kwantyzację liczb całkowitych”, która konwertuje wszystkie wagi i wyjścia aktywacji na 8-bitowe dane całkowite — podczas gdy inne strategie mogą pozostawić pewną ilość danych w postaci zmiennoprzecinkowej.

Aby dowiedzieć się więcej o różnych strategiach kwantyzacji przeczytasz TensorFlow Lite optymalizacji modelu .

Ustawiać

Aby skwantyzować zarówno tensory wejściowe, jak i wyjściowe, musimy użyć API dodanych w 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

Wygeneruj model TensorFlow

Zbudujemy prosty model do numerów klasyfikować ze zbioru danych MNIST .

To szkolenie nie potrwa długo, ponieważ trenujesz model tylko przez 5 epok, który trenuje z około 98% dokładnością.

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

Konwersja na model TensorFlow Lite

Teraz można przekonwertować wyszkolony modelu do formatu TensorFlow Lite pomocą TFLiteConverter API i stosować różne stopnie kwantyzacji.

Uważaj, ponieważ niektóre wersje kwantyzacji pozostawiają niektóre dane w formacie float. Więc następne sekcje pokazują każdą opcję z rosnącą ilością kwantyzacji, aż otrzymamy model, który jest całkowicie danymi int8 lub uint8. (Zauważ, że powielamy trochę kodu w każdej sekcji, dzięki czemu możesz zobaczyć wszystkie kroki kwantyzacji dla każdej opcji.)

Po pierwsze, oto przekonwertowany model bez kwantyzacji:

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.

Jest to teraz model TensorFlow Lite, ale nadal używa 32-bitowych wartości zmiennoprzecinkowych dla wszystkich danych parametrów.

Konwertuj za pomocą kwantyzacji zakresu dynamicznego

Teraz włączyć domyślne optimizations flagę do kwantowania wszystkie stałe parametry (takie jak ciężarkami):

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.

Model jest teraz nieco mniejszy ze skwantowanymi wagami, ale inne zmienne dane są nadal w formacie zmiennoprzecinkowym.

Konwertuj za pomocą zmiennoprzecinkowej kwantyzacji zastępczej

Do kwantowania zmiennych danych (takie jak wzór wejścia / wyjścia i pośrednich pomiędzy warstwami), należy zapewnić RepresentativeDataset . Jest to funkcja generatora, która udostępnia zestaw danych wejściowych, który jest wystarczająco duży, aby reprezentować typowe wartości. Umożliwia konwerterowi oszacowanie zakresu dynamicznego dla wszystkich danych zmiennych. (Zestaw danych nie musi być unikalny w porównaniu z zestawem danych uczących lub oceniających). Aby obsługiwać wiele danych wejściowych, każdy reprezentatywny punkt danych jest listą, a elementy z listy są wprowadzane do modelu zgodnie z ich indeksami.

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

Teraz wszystkie wagi i zmienne dane są skwantyzowane, a model jest znacznie mniejszy w porównaniu z oryginalnym modelem TensorFlow Lite.

Jednak, aby zachować zgodność z aplikacjami, które tradycyjnie używają tensorów wejścia i wyjścia modelu zmiennoprzecinkowego, TensorFlow Lite Converter pozostawia tensory wejścia i wyjścia modelu w trybie zmiennoprzecinkowym:

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

Zwykle jest to dobre dla zgodności, ale nie będzie kompatybilne z urządzeniami, które wykonują tylko operacje na liczbach całkowitych, takimi jak Edge TPU.

Dodatkowo, powyższy proces może pozostawić operację w formacie float, jeśli TensorFlow Lite nie zawiera skwantyzowanej implementacji dla tej operacji. Ta strategia umożliwia zakończenie konwersji, dzięki czemu masz mniejszy i wydajniejszy model, ale znowu nie będzie on kompatybilny ze sprzętem wyłącznie do liczb całkowitych. (Wszystkie operacje w tym modelu MNIST mają skwantowaną implementację.)

Tak więc, aby zapewnić model typu end-to-end wyłącznie na liczbach całkowitych, potrzebujesz jeszcze kilku parametrów...

Konwertuj za pomocą kwantyzacji wyłącznie na liczbach całkowitych

Aby skwantyzować tensory wejścia i wyjścia i spowodować, że konwerter wyrzuci błąd, jeśli napotka operację, której nie może skwantyzować, ponownie przekonwertuj model z kilkoma dodatkowymi parametrami:

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.

Wewnętrzna kwantyzacja pozostaje taka sama jak powyżej, ale widać, że tensory wejściowe i wyjściowe mają teraz format liczb całkowitych:

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

Teraz masz model całkowitą krokowe typu INTEGER danych dla tensorów wejściowych i wyjściowych w modelu, więc jest to zgodne z całkowitą tylko sprzętu takiego jak krawędzi TPU .

Zapisz modele jako pliki

Będziesz potrzebował .tflite plik wdrożyć swój model na innych urządzeniach. Zapiszmy więc przekonwertowane modele do plików, a następnie załadujmy je, gdy uruchomimy poniższe wnioskowania.

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

Uruchom modele TensorFlow Lite

Teraz możemy uruchomić wnioskowania za pomocą TensorFlow Lite Interpreter porównać dokładności modelu.

Najpierw potrzebujemy funkcji, która uruchamia wnioskowanie z danym modelem i obrazami, a następnie zwraca prognozy:

# 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

Przetestuj modele na jednym obrazie

Teraz porównamy wydajność modelu zmiennoprzecinkowego i modelu skwantowanego:

  • tflite_model_file jest oryginalny model TensorFlow Lite z danych zmiennoprzecinkowych.
  • tflite_model_quant_file jest ostatnim modelem, przelicza się całkowitą tylko kwantyzacji (używa danych Uint8 dla wejścia i wyjścia).

Stwórzmy kolejną funkcję, aby wydrukować nasze prognozy:

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)

Teraz przetestuj model pływaka:

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

png

I przetestuj model skwantowany:

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

png

Oceń modele na wszystkich obrazach

Teraz uruchommy oba modele, używając wszystkich obrazów testowych, które załadowaliśmy na początku tego samouczka:

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

Oceń model pływaka:

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

Oceń model skwantowany:

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

Masz teraz model skwantowany na liczbę całkowitą, prawie bez różnicy w dokładności w porównaniu z modelem zmiennoprzecinkowym.

Aby dowiedzieć się więcej na temat innych strategii kwantyzacji przeczytasz TensorFlow Lite optymalizacji modelu .