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 do acelerador de CPU e hardware, com pouca degradação na precisão do modelo. Você pode quantizar 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 resumida das opções e os benefícios que elas oferecem:
Técnica | Benefícios | Hardware |
---|---|---|
Quantização de faixa dinâmica | 4x menor, aceleração de 2x-3x | CPU |
Quantização inteira completa | 4x menor, 3x+ aceleração | CPU, Edge TPU, Microcontroladores |
Quantização Float16 | 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:
Quantização de faixa dinâmica
A quantização de faixa dinâmica é um ponto de partida recomendado porque fornece uso reduzido de memória e computação mais rápida sem que você precise fornecer um conjunto de dados representativo para calibração. Este tipo de quantização quantifica 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" quantificam dinamicamente as ativações com base em seu intervalo para 8 bits e realizam 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 uma computação completa de ponto fixo.
Quantização inteira completa
Você pode obter mais melhorias de latência, reduções no pico de uso de memória 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 de inteiros completos, você precisa calibrar ou estimar o intervalo, ou seja, (min, max) de todos os tensores de ponto flutuante no modelo. Ao contrário de tensores constantes, como pesos e vieses, tensores variáveis, como entrada de modelo, ativações (saídas de camadas intermediárias) e saída de 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. Esse 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.
A partir da versão 2.7 do TensorFlow, você pode especificar o conjunto de dados representativo por meio de uma assinatura conforme o 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 do 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 tensores de entrada porque a ordenação do tensor de entrada pode ser facilmente invertida.
Para fins de teste, você pode usar um conjunto de dados fictício da seguinte forma:
def representative_dataset(): for _ in range(100): data = np.random.rand(1, 244, 244, 3) yield [data.astype(np.float32)]
Número inteiro com fallback flutuante (usando entrada/saída flutuante padrão)
Para quantizar um modelo totalmente inteiro, mas use operadores float 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 do TensorFlow Lite para microcontroladores e TPUs Coral Edge .
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 aplicar 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()
Quantização Float16
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 habilitar a quantização de pesos float16, 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:
- Reduz o tamanho do modelo em até metade (já que todos os pesos se tornam metade do tamanho original).
- Causa perda mínima de precisão.
- Ele suporta alguns delegados (por exemplo, o delegado da GPU) que podem operar diretamente em 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:
- Ele 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 realizará essa desquantização, pois pode operar em 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 seu intervalo de 16 bits, os pesos são quantizados em inteiro de 8 bits e o viés é quantizado em inteiro de 64 bits. Isto é referido como quantização 16x8.
A principal vantagem dessa quantização é que ela pode melhorar significativamente a precisão, mas aumentar apenas um pouco 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 compatível com alguns operadores no modelo, o modelo ainda poderá ser quantizado, mas os operadores sem suporte serão mantidos em flutuação. A opção a seguir 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, * remoção de ruído de imagem, * reconstrução HDR de uma única imagem.
A desvantagem desta quantização é:
- Atualmente a inferência é visivelmente mais lenta do que o inteiro completo de 8 bits devido à falta de implementação otimizada do kernel.
- 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 usar o 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 conhecidos como por canal) ou por tensor representados pelos 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 delegação do TensorFlow Lite são incentivados a implementar o esquema de quantização descrito lá.