7.8. E/S de tensores

O motor aceita um único tensor no lado da entrada e produz um ou mais no lado da saída. Os tensores são objetos ndarray com o formato, dtype e vocabulário de descritores que o capítulo de numpy apresentou. Seus formatos e dtypes vêm do arquivo de modelo e são reportados através de input_shape / output_shape e input_dtype / output_dtype.

7.8.1. Quantização

A maioria das redes que a câmera executa opera sobre tensores inteiros quantizados – int8 ou uint8 – para caber dentro do orçamento de RAM e de computação da câmera. Um tensor quantizado carrega valores inteiros que representam números de valor real por meio de uma escala e um ponto zero por tensor:

\[\text{real} = \text{scale} \times (q - \text{zero_point})\]
\[q = \mathrm{round}(\text{real} / \text{scale}) + \text{zero_point}\]

A escala e o ponto zero vêm da calibração de treinamento do modelo e são armazenados no arquivo de modelo. Eles são expostos como input_scale, input_zero_point, output_scale e output_zero_point – cada um uma lista com uma entrada por tensor de entrada ou saída.

ml.utils.quantize() e ml.utils.dequantize() aplicam as fórmulas contra um índice de saída especificado:

import ml.utils

real_tensor = ml.utils.dequantize(model, q_tensor, index=0)
q_tensor    = ml.utils.quantize(model, real_tensor, index=0)

Ambas as funções retornam o valor inalterado quando o dtype de saída no índice dado já é float, então a chamada é segura independentemente do estado de quantização do modelo.

7.8.2. O que o script vê no lado da saída

O que predict() retorna depende de haver ou não um pós-processador registrado.

Sem pós-processador, as saídas inteiras brutas do motor são dequantizadas automaticamente para float e retornadas como uma lista de objetos ndarray float. O script recebe números de valor real prontos para leitura. Esse é o caminho certo para redes de classificação, cujo único tensor de saída já é uma lista de pontuações de confiança por classe sobre a qual a aplicação itera – sem necessidade de etapa de decodificação. É também o caminho fácil para colocar um modelo desconhecido em execução rapidamente ou para inspeção ad-hoc a partir do REPL.

Com um pós-processador registrado (através de postprocess= no construtor ou callback= na chamada de predict), os tensores quantizados brutos são entregues diretamente ao chamável do pós-processador. O pós-processador recebe os tensores quantizados brutos e é responsável por qualquer dequantização de que precise.

A divisão é uma escolha de desempenho. A dequantização automática aloca um novo tensor float para cada saída e percorre cada elemento. Um pós-processador que precisa apenas de alguns valores de cada tensor – aplicar limiar nas pontuações de confiança e então decodificar as caixas para os sobreviventes – evita o custo de dequantizar o restante. Os decodificadores de caixas fornecidos sob ml.postprocessing seguem todos por esse caminho, e ml.utils.threshold() foi construído exatamente para este caso: ele recebe um tensor de pontuações quantizado e retorna os índices cujos valores dequantizados passam um limiar de valor real, sem dequantizar o tensor inteiro.