כימות שלמה לאחר אימון

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

סקירה כללית

קוונטיזציה של מספרים שלמים היא אסטרטגיית אופטימיזציה הממירה מספרי נקודה צפה של 32 סיביות (כגון משקלים ויציאות הפעלה) למספרי הנקודה הקבועה הקרובה ביותר של 8 סיביות. תוצאות זה במודל קטן ומהירות היסקים גדל, שהוא בעל ערך עבור התקנים בעלי צריכת חשמל נמוכה כגון מיקרו-בקרים . פורמט נתונים זה גם נדרש על ידי מספר שלם בלבד מאיצים כמו אדג TPU .

במדריך זה, תוכל לאמן מודל MNIST מאפס, להמיר אותו לקובץ לייט Tensorflow, ו לקוואנטיזציה אותו באמצעות קוונטיזציה-הכשרה פוסט . לבסוף, תבדוק את הדיוק של הדגם שהוסב ותשווה אותו לדגם המצוף המקורי.

למעשה יש לך כמה אפשרויות לגבי כמה אתה רוצה לכמת דגם. במדריך זה, תבצעו "כימת מספרים שלמים מלאה", אשר ממירה את כל המשקולות ותפוקות ההפעלה לנתונים שלמים של 8 סיביות - בעוד שאסטרטגיות אחרות עשויות להשאיר כמות מסוימת של נתונים בנקודה צפה.

כדי ללמוד עוד על אסטרטגיות קוונטיזציה השונות, לקרוא על אופטימיזציה מודל לייט TensorFlow .

להכין

על מנת לכמת את טנסור הקלט והפלט, עלינו להשתמש בממשקי API שנוספו ב- 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

צור מודל TensorFlow

נצטרך לבנות מודל פשוט מספרים לסווג מן הנתונים MNIST .

אימון זה לא ייקח הרבה זמן כי אתה מאמן את המודל למשך 5 עידנים בלבד, אשר מתאמן לכ-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>

המר לדגם TensorFlow Lite

עכשיו אתה יכול להמיר את המודל המודרך לפורמט TensorFlow לייט באמצעות TFLiteConverter API, ולהחיל בדרגות שונות של קוונטיזציה.

היזהר שחלק מהגרסאות של קוונטיזציה משאירות חלק מהנתונים בפורמט צף. אז הסעיפים הבאים מציגים כל אפשרות עם כמויות הולכות וגדלות של קוונטיזציה, עד שנקבל מודל שכולו נתוני int8 או uint8. (שים לב שאנו משכפלים קוד כלשהו בכל חלק כדי שתוכל לראות את כל שלבי הקוונטיזציה עבור כל אפשרות.)

ראשית, הנה מודל שהומר ללא כימות:

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.

כעת זהו דגם TensorFlow Lite, אך הוא עדיין משתמש בערכי ציפה של 32 סיביות עבור כל נתוני הפרמטרים.

המרה באמצעות קוונטיזציה של טווח דינמי

עכשיו בואו נפעיל את ברירת המחדל optimizations הדגל לקוואנטיזציה כול פרמטרים קבוע (כגון משקולות):

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.

המודל עכשיו קצת יותר קטן עם משקלים כמותיים, אבל נתונים משתנים אחרים עדיין בפורמט צף.

המרה באמצעות קוונטיזציה מסוג Float Fallback

לקוואנטיזציה הנתונים משתנה (כגון דגם קלט / פלט ביניים בין השכבות), אתה צריך לספק RepresentativeDataset . זוהי פונקציית מחולל המספקת קבוצה של נתוני קלט שגדולים מספיק כדי לייצג ערכים טיפוסיים. זה מאפשר לממיר להעריך טווח דינמי עבור כל הנתונים המשתנים. (מערך הנתונים אינו צריך להיות ייחודי בהשוואה למערך ההדרכה או ההערכה.) כדי לתמוך במספר קלטים, כל נקודת נתונים מייצגת היא רשימה ואלמנטים ברשימה מוזנים למודל לפי המדדים שלהם.

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

כעת כל המשקולות והנתונים המשתנים מכונסים, והמודל קטן משמעותית בהשוואה לדגם TensorFlow Lite המקורי.

עם זאת, כדי לשמור על תאימות עם יישומים המשתמשים באופן מסורתי בטנסור קלט ופלט של מודל צף, ממיר TensorFlow Lite משאיר את טנסור הקלט והפלט של הדגם במצב צף:

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

זה בדרך כלל טוב לתאימות, אבל זה לא יהיה תואם למכשירים שמבצעים רק פעולות מבוססות מספרים שלמים, כמו Edge TPU.

בנוסף, התהליך שלעיל עשוי להשאיר פעולה בפורמט צף אם TensorFlow Lite אינו כולל יישום כמותי עבור אותה פעולה. אסטרטגיה זו מאפשרת להשלים את ההמרה כך שיהיה לך דגם קטן ויעיל יותר, אבל שוב, הוא לא יהיה תואם לחומרה של מספרים שלמים בלבד. (לכל הפעולות במודל MNIST זה יש יישום כמותי.)

אז כדי להבטיח מודל מקצה לקצה של מספר שלם בלבד, אתה צריך עוד כמה פרמטרים...

המרה באמצעות קוונטיזציה של מספרים שלמים בלבד

כדי לכמת את טנסור הקלט והפלט, ולגרום לממיר לזרוק שגיאה אם ​​הוא נתקל בפעולה שהוא לא יכול לכמת, המר שוב את המודל עם כמה פרמטרים נוספים:

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.

הקוונטיזציה הפנימית נשארת זהה לעיל, אבל אתה יכול לראות את טנסורי הקלט והפלט הם כעת בפורמט של מספר שלם:

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

עכשיו יש לך מודל בדיד שלם כי שימושים שלמים נתונים עבור טנזורים הקלט ופלט של המודל, כך שזה תואם עם חומרה שלמה בלבד כגון אדג TPU .

שמור את הדגמים כקבצים

תצטרך .tflite קובץ לפרוס את המודל על מכשירים אחרים. אז בואו נשמור את המודלים שהומרו לקבצים ואז נטען אותם כשאנחנו מריצים מסקנות למטה.

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

הפעל את דגמי TensorFlow Lite

עכשיו אנחנו נפעיל מסקנות באמצעות לייט TensorFlow Interpreter כדי להשוות את דיוק המודל.

ראשית, אנו זקוקים לפונקציה שמריצה מסקנות עם מודל ותמונות נתונות, ולאחר מכן מחזירה את התחזיות:

# 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

בדוק את הדגמים על תמונה אחת

כעת נשווה את הביצועים של מודל הצוף והמודל הקוונטי:

  • tflite_model_file הוא מודל לייט TensorFlow המקורי עם נתונים של נקודה צפה.
  • tflite_model_quant_file הוא המודל האחרון העברנו את המשתמשים באמצעות קוונטיזציה שלם בלבד (זה משתמש בנתונים uint8 עבור קלט ופלט).

בואו ניצור פונקציה נוספת כדי להדפיס את התחזיות שלנו:

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)

כעת בדוק את מודל הציפה:

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

png

ותבדוק את המודל הקוונטי:

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

png

הערך את הדגמים בכל התמונות

כעת נריץ את שני הדגמים באמצעות כל תמונות הבדיקה שהעלינו בתחילת המדריך הזה:

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

הערך את מודל הציפה:

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

הערך את המודל הממוטט:

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

אז עכשיו יש לך מספר שלם שמכמת מודל כמעט ללא הבדל ברמת הדיוק, בהשוואה למודל הצוף.

כדי ללמוד עוד על אסטרטגיות קוונטיזציה אחרות, לקרוא על אופטימיזציה מודל לייט TensorFlow .