Quantizzazione post-addestramento

La quantizzazione post-addestramento è una tecnica di conversione che può ridurre le dimensioni del modello migliorando al contempo la latenza della CPU e dell'acceleratore hardware, con una lieve riduzione dell'accuratezza del modello. Puoi quantizzare un modello TensorFlow float già addestrato quando lo converti nel formato LiteRT utilizzando lo strumento di conversione LiteRT.

Metodi di ottimizzazione

Esistono diverse opzioni di quantizzazione post-addestramento tra cui scegliere. Ecco una tabella riepilogativa delle scelte e dei vantaggi che offrono:

Tecnica Vantaggi Hardware
Quantizzazione della gamma dinamica 4 volte più piccolo, 2-3 volte più veloce CPU
Quantizzazione di interi 4 volte più piccolo, 3 volte più veloce CPU, Edge TPU, microcontrollori
Quantizzazione Float16 2 volte più piccolo, accelerazione GPU CPU, GPU

Il seguente albero decisionale può aiutarti a determinare quale metodo di quantizzazione post-training è più adatto al tuo caso d'uso:

opzioni di ottimizzazione post-addestramento

Nessuna quantizzazione

La conversione a un modello TFLite senza quantizzazione è un punto di partenza consigliato. Verrà generato un modello TFLite float.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
tflite_quant_model = converter.convert()

Ti consigliamo di eseguire questa operazione come passaggio iniziale per verificare che gli operatori del modello TF originale siano compatibili con TFLite e possano essere utilizzati anche come base di riferimento per eseguire il debug degli errori di quantizzazione introdotti dai metodi di quantizzazione post-addestramento successivi. Ad esempio, se un modello TFLite quantizzato produce risultati imprevisti, mentre il modello TFLite float è accurato, possiamo restringere il problema agli errori introdotti dalla versione quantizzata degli operatori TFLite.

Quantizzazione della gamma dinamica

La quantizzazione dell'intervallo dinamico offre un utilizzo ridotto della memoria e un calcolo più rapido senza che tu debba fornire un set di dati rappresentativo per la calibrazione. Questo tipo di quantizzazione quantizza staticamente solo i pesi da virgola mobile a numero intero al momento della conversione, il che fornisce 8 bit di precisione:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()

Per ridurre ulteriormente la latenza durante l'inferenza, gli operatori "dynamic-range" quantizzano dinamicamente le attivazioni in base al loro intervallo a 8 bit ed eseguono i calcoli con pesi e attivazioni a 8 bit. Questa ottimizzazione fornisce latenze vicine alle inferenze a virgola fissa. Tuttavia, gli output vengono comunque memorizzati utilizzando la virgola mobile, quindi l'aumento della velocità delle operazioni a intervallo dinamico è inferiore a un calcolo completo a virgola fissa.

Quantizzazione di numeri interi completa

Puoi ottenere ulteriori miglioramenti della latenza, riduzioni dell'utilizzo di memoria di picco e compatibilità con acceleratori o dispositivi hardware solo con numeri interi assicurandoti che tutti i calcoli del modello siano quantizzati in numeri interi.

Per la quantizzazione degli interi completa, devi calibrare o stimare l'intervallo, ovvero (min, max) di tutti i tensori in virgola mobile nel modello. A differenza dei tensori costanti come pesi e bias, i tensori variabili come l'input del modello, le attivazioni (output degli strati intermedi) e l'output del modello non possono essere calibrati a meno che non eseguiamo alcuni cicli di inferenza. Di conseguenza, il convertitore richiede un set di dati rappresentativo per la calibrazione. Questo set di dati può essere un piccolo subset (circa 100-500 campioni) dei dati di addestramento o convalida. Fai riferimento alla funzione representative_dataset() di seguito.

A partire dalla versione 2.7 di TensorFlow, puoi specificare il set di dati rappresentativo tramite una firma come nel seguente esempio:

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

Se nel modello TensorFlow specificato è presente più di una firma, puoi specificare il set di dati multiplo specificando le chiavi della firma:

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,
      },
    )

Puoi generare il set di dati rappresentativo fornendo un elenco di tensori di input:

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

A partire dalla versione 2.7 di TensorFlow, consigliamo di utilizzare l'approccio basato sulla firma rispetto a quello basato sull'elenco dei tensori di input, perché l'ordine dei tensori di input può essere facilmente invertito.

A scopo di test, puoi utilizzare un set di dati fittizio nel seguente modo:

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

Numero intero con fallback in virgola mobile (utilizzando input/output in virgola mobile predefinito)

Per quantizzare completamente un modello come numero intero, ma utilizzare operatori float quando non hanno un'implementazione di numeri interi (per garantire che la conversione avvenga senza problemi), segui questi passaggi:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
tflite_quant_model = converter.convert()

Solo numeri interi

La creazione di modelli solo interi è un caso d'uso comune per LiteRT for Microcontrollers e Coral Edge TPU.

Inoltre, per garantire la compatibilità con dispositivi (come i microcontrollori a 8 bit) e acceleratori (come Coral Edge TPU) solo con numeri interi, puoi applicare la quantizzazione di numeri interi per tutte le operazioni, inclusi input e output, seguendo questi passaggi:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8  # or tf.uint8
converter.inference_output_type = tf.int8  # or tf.uint8
tflite_quant_model = converter.convert()

Quantizzazione Float16

Puoi ridurre le dimensioni di un modello in virgola mobile quantizzando i pesi in float16, lo standard IEEE per i numeri in virgola mobile a 16 bit. Per abilitare la quantizzazione float16 dei pesi, segui questi passaggi:

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

I vantaggi della quantizzazione float16 sono i seguenti:

  • Riduce le dimensioni del modello fino alla metà (poiché tutte le ponderazioni diventano la metà delle dimensioni originali).
  • Causa una perdita minima di accuratezza.
  • Supporta alcuni delegati (ad es. il delegato GPU) che possono operare direttamente sui dati float16, con un'esecuzione più rapida rispetto ai calcoli float32.

Gli svantaggi della quantizzazione float16 sono i seguenti:

  • Non riduce la latenza tanto quanto una quantizzazione alla matematica in virgola fissa.
  • Per impostazione predefinita, un modello quantizzato float16 "dequantizza" i valori dei pesi in float32 quando viene eseguito sulla CPU. Tieni presente che il delegato della GPU non esegue questa dequantizzazione, poiché può operare su dati float16.

Solo numeri interi: attivazioni a 16 bit con pesi a 8 bit (sperimentale)

Questo è uno schema di quantizzazione sperimentale. È simile allo schema "solo numeri interi", ma le attivazioni vengono quantizzate in base al loro intervallo a 16 bit, i pesi vengono quantizzati in numeri interi a 8 bit e il bias viene quantizzato in numeri interi a 64 bit. Questo viene ulteriormente definito quantizzazione 16x8.

Il vantaggio principale di questa quantizzazione è che può migliorare significativamente l'accuratezza, ma aumenta solo leggermente le dimensioni del modello.

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

Se la quantizzazione 16x8 non è supportata per alcuni operatori nel modello, il modello può comunque essere quantizzato, ma gli operatori non supportati vengono mantenuti in virgola mobile. Per consentirlo, devi aggiungere l'opzione seguente a 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()

Ecco alcuni esempi di casi d'uso in cui i miglioramenti della precisione forniti da questo schema di quantizzazione includono:

  • super-resolution,
  • elaborazione del segnale audio, ad esempio cancellazione del rumore e beamforming,
  • riduzione del rumore delle immagini,
  • Ricostruzione HDR da una singola immagine.

Lo svantaggio di questa quantizzazione è:

  • Attualmente l'inferenza è notevolmente più lenta rispetto all'intero numero intero a 8 bit a causa della mancanza di un'implementazione ottimizzata del kernel.
  • Al momento non è compatibile con i delegati TFLite esistenti con accelerazione hardware.

Un tutorial per questa modalità di quantizzazione è disponibile qui.

Precisione del modello

Poiché i pesi vengono quantizzati dopo l'addestramento, potrebbe verificarsi una perdita di precisione, in particolare per le reti più piccole. I modelli preaddestrati completamente quantizzati vengono forniti per reti specifiche su Kaggle Models. È importante controllare l'accuratezza del modello quantizzato per verificare che l'eventuale riduzione dell'accuratezza rientri nei limiti accettabili. Esistono strumenti per valutare l'accuratezza del modello LiteRT.

In alternativa, se il calo di precisione è troppo elevato, valuta la possibilità di utilizzare l'addestramento quantizzazione consapevole. Tuttavia, questa operazione richiede modifiche durante l'addestramento del modello per aggiungere nodi di quantizzazione fittizi, mentre le tecniche di quantizzazione post-addestramento in questa pagina utilizzano un modello preaddestrato esistente.

Rappresentazione per i tensori quantizzati

La quantizzazione a 8 bit approssima i valori in virgola mobile utilizzando la seguente formula.

\[real\_value = (int8\_value - zero\_point) \times scale\]

La rappresentazione è composta da due parti principali:

  • Pesi per asse (o per canale) o per tensore rappresentati da valori di complemento a due int8 nell'intervallo [-127, 127] con punto zero uguale a 0.

  • Attivazioni/input per tensore rappresentati da valori di complemento a due int8 nell'intervallo [-128, 127], con un punto zero nell'intervallo [-128, 127].

Per una visualizzazione dettagliata del nostro schema di quantizzazione, consulta la nostra specifica di quantizzazione. I fornitori di hardware che vogliono integrarsi nell'interfaccia delegata di TensorFlow Lite sono invitati a implementare lo schema di quantizzazione descritto qui.