7.8. E/S de Tensores

O motor aceita um único tensor no lado de entrada e produz um ou mais no lado de saída. Os tensores são objetos ndarray com o vocabulário de forma, dtype e descritor que o capítulo numpy introduziu. As suas formas e dtypes provêm do ficheiro do 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âmara executa operam em tensores inteiros quantizados – int8 ou uint8 – para caber na RAM e no orçamento de computação da câmara. Um tensor quantizado transporta valores inteiros que representam números de valor real através 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 provêm da calibração em tempo de treino do modelo e são armazenados no ficheiro do modelo. 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 a 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 devolvem o valor sem alterações quando o dtype de saída no índice dado já é float, pelo que a chamada é segura independentemente do estado de quantização do modelo.

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

O que predict() devolve depende de se existe um pós-processador registado.

Sem pós-processador, as saídas inteiras brutas do motor são desquantizadas automaticamente para ponto flutuante e devolvidas como uma lista de objetos ndarray em ponto flutuante. O script recebe números de valor real prontos a ler. Este é o caminho correto 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 – não é necessário nenhum passo de descodificação. É também o caminho fácil para pôr um modelo desconhecido a funcionar rapidamente ou para inspeção ad-hoc a partir do REPL.

Com um pós-processador registado (através de postprocess= no construtor ou callback= na chamada ao predict), os tensores quantizados brutos são entregues diretamente ao callable do pós-processador. O pós-processador recebe os tensores quantizados brutos e é responsável pela desquantização que necessitar.

A divisão é uma escolha de desempenho. A desquantização automática aloca um novo tensor float para cada saída e percorre todos os elementos. Um pós-processador que apenas precisa de alguns valores de cada tensor – aplica limiar às pontuações de confiança, depois descodifica caixas para os sobreviventes – evita o custo de desquantizar o resto. Os descodificadores de caixas incluídos em ml.postprocessing seguem todos este caminho, e ml.utils.threshold() foi construído exatamente para este caso: recebe um tensor de pontuações quantizadas e devolve os índices cujos valores desquantizados passam um limiar de valor real, sem desquantizar o tensor inteiro.