Quantização pós-treinamento

A quantização pós-treinamento é uma técnica de conversão que pode reduzir o tamanho do modelo e, ao mesmo tempo, melhorar a latência da CPU e do acelerador de hardware, com pouca degradação na precisão do modelo. Você pode quantificar um modelo flutuante do TensorFlow já treinado ao convertê-lo para o formato TensorFlow Lite usando o TensorFlow Lite Converter .

Métodos de Otimização

Existem várias opções de quantização pós-treinamento para escolher. Aqui está uma tabela de resumo das opções e os benefícios que elas oferecem:

Técnica Benefícios hardware
Quantização de faixa dinâmica 4x menor, 2x-3x de aceleração CPU
Quantização inteira completa 4x menor, 3x+ aceleração CPU, Edge TPU, Microcontroladores
Float16 quantização 2x menor, aceleração de GPU CPU, GPU

A seguinte árvore de decisão pode ajudar a determinar qual método de quantização pós-treinamento é melhor para seu caso de uso:

opções de otimização pós-treinamento

Quantização de faixa dinâmica

A quantização de faixa dinâmica é um ponto de partida recomendado porque fornece uso de memória reduzido e computação mais rápida sem que você precise fornecer um conjunto de dados representativo para calibração. Este tipo de quantização, quantiza estaticamente apenas os pesos de ponto flutuante para inteiro no momento da conversão, o que fornece 8 bits de precisão:

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

Para reduzir ainda mais a latência durante a inferência, os operadores de "intervalo dinâmico" quantizam dinamicamente as ativações com base em seu intervalo para 8 bits e executam cálculos com pesos e ativações de 8 bits. Essa otimização fornece latências próximas a inferências de ponto totalmente fixo. No entanto, as saídas ainda são armazenadas usando ponto flutuante, de modo que o aumento da velocidade das operações de faixa dinâmica é menor do que um cálculo completo de ponto fixo.

Quantização inteira completa

Você pode obter mais melhorias de latência, reduções no uso de memória de pico e compatibilidade com dispositivos de hardware ou aceleradores somente inteiros, certificando-se de que toda a matemática do modelo seja quantizada por inteiro.

Para quantização inteira completa, você precisa calibrar ou estimar o intervalo, ou seja, (min, max) de todos os tensores de ponto flutuante no modelo. Ao contrário dos tensores constantes, como pesos e vieses, os tensores variáveis, como entrada do modelo, ativações (saídas de camadas intermediárias) e saída do modelo, não podem ser calibrados, a menos que executemos alguns ciclos de inferência. Como resultado, o conversor requer um conjunto de dados representativo para calibrá-los. Este conjunto de dados pode ser um pequeno subconjunto (cerca de ~ 100-500 amostras) dos dados de treinamento ou validação. Consulte a função representative_dataset() abaixo.

Na versão TensorFlow 2.7, você pode especificar o conjunto de dados representativo por meio de uma assinatura , como no exemplo a seguir:

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

Se houver mais de uma assinatura no modelo TensorFlow fornecido, você poderá especificar o conjunto de dados múltiplo especificando as chaves de assinatura:

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

Você pode gerar o conjunto de dados representativo fornecendo uma lista de tensores de entrada:

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

Desde a versão 2.7 do TensorFlow, recomendamos usar a abordagem baseada em assinatura em vez da abordagem baseada em lista de tensor de entrada porque a ordem do tensor de entrada pode ser facilmente invertida.

Para fins de teste, você pode usar um conjunto de dados fictício da seguinte maneira:

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

Inteiro com fallback float (usando entrada/saída float padrão)

Para quantizar totalmente um modelo inteiro, mas usar operadores flutuantes quando eles não tiverem uma implementação inteira (para garantir que a conversão ocorra sem problemas), use as seguintes etapas:

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

Somente inteiro

A criação de modelos somente inteiros é um caso de uso comum para o TensorFlow Lite para microcontroladores e Coral Edge TPUs .

Além disso, para garantir a compatibilidade com dispositivos somente inteiros (como microcontroladores de 8 bits) e aceleradores (como o Coral Edge TPU), você pode impor a quantização inteira inteira para todas as operações, incluindo entrada e saída, usando as seguintes etapas:

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

Float16 quantização

Você pode reduzir o tamanho de um modelo de ponto flutuante quantizando os pesos para float16, o padrão IEEE para números de ponto flutuante de 16 bits. Para ativar a quantização float16 de pesos, use as seguintes etapas:

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

As vantagens da quantização float16 são as seguintes:

  • Ele reduz o tamanho do modelo pela metade (já que todos os pesos passam a ter metade do tamanho original).
  • Isso causa perda mínima de precisão.
  • Ele suporta alguns delegados (por exemplo, o delegado GPU) que podem operar diretamente nos dados float16, resultando em uma execução mais rápida do que os cálculos float32.

As desvantagens da quantização float16 são as seguintes:

  • Não reduz a latência tanto quanto uma quantização para matemática de ponto fixo.
  • Por padrão, um modelo quantizado float16 irá "desquantizar" os valores dos pesos para float32 quando executado na CPU. (Observe que o delegado da GPU não executará essa desquantização, pois pode operar com dados float16.)

Somente inteiro: ativações de 16 bits com pesos de 8 bits (experimental)

Este é um esquema de quantização experimental. É semelhante ao esquema "somente inteiro", mas as ativações são quantizadas com base em sua faixa de 16 bits, os pesos são quantizados em inteiros de 8 bits e o viés é quantizado em inteiros de 64 bits. Isso é referido como quantização 16x8 ainda mais.

A principal vantagem dessa quantização é que ela pode melhorar significativamente a precisão, mas apenas aumentar ligeiramente o tamanho do modelo.

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 a quantização 16x8 não for suportada por alguns operadores no modelo, então o modelo ainda pode ser quantizado, mas os operadores não suportados mantidos em flutuação. A seguinte opção deve ser adicionada ao target_spec para permitir isso.

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

Exemplos de casos de uso em que as melhorias de precisão fornecidas por esse esquema de quantização incluem:

  • super-resolução,
  • processamento de sinal de áudio, como cancelamento de ruído e formação de feixe,
  • redução de ruído de imagem,
  • Reconstrução HDR a partir de uma única imagem.

A desvantagem desta quantização é:

  • Atualmente, a inferência é visivelmente mais lenta do que um inteiro completo de 8 bits devido à falta de implementação de kernel otimizada.
  • Atualmente, é incompatível com os delegados TFLite acelerados por hardware existentes.

Um tutorial para este modo de quantização pode ser encontrado aqui .

Precisão do modelo

Como os pesos são quantizados após o treinamento, pode haver uma perda de precisão, principalmente para redes menores. Modelos totalmente quantizados pré-treinados são fornecidos para redes específicas no TensorFlow Hub . É importante verificar a precisão do modelo quantizado para verificar se qualquer degradação na precisão está dentro dos limites aceitáveis. Existem ferramentas para avaliar a precisão do modelo do TensorFlow Lite .

Como alternativa, se a queda de precisão for muito alta, considere o uso de treinamento com reconhecimento de quantização . No entanto, isso requer modificações durante o treinamento do modelo para adicionar nós de quantização falsos, enquanto as técnicas de quantização pós-treinamento nesta página usam um modelo pré-treinado existente.

Representação para tensores quantizados

A quantização de 8 bits aproxima os valores de ponto flutuante usando a seguinte fórmula.

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

A representação tem duas partes principais:

  • Pesos por eixo (também conhecido como por canal) ou por tensor representados por valores de complemento de dois int8 no intervalo [-127, 127] com ponto zero igual a 0.

  • Ativações/entradas por tensor representadas por int8 valores de complemento de dois no intervalo [-128, 127], com um ponto zero no intervalo [-128, 127].

Para uma visão detalhada do nosso esquema de quantização, consulte nossa especificação de quantização . Os fornecedores de hardware que desejam se conectar à interface de delegado do TensorFlow Lite são incentivados a implementar o esquema de quantização descrito lá.