7.8. E/S de tensores¶
El motor acepta un único tensor en el lado de entrada y produce uno o más en el lado de salida. Los tensores son objetos ndarray con la forma, el dtype y el vocabulario de descriptores que introdujo el capítulo de numpy. Sus formas y dtypes provienen del archivo del modelo y se informan mediante input_shape / output_shape y input_dtype / output_dtype.
7.8.1. Cuantización¶
La mayoría de las redes que ejecuta la cámara operan sobre tensores enteros cuantizados – int8 o uint8 – para caber dentro de la RAM y el presupuesto de cálculo de la cámara. Un tensor cuantizado contiene valores enteros que representan números de valor real mediante una escala y un punto cero por tensor:
La escala y el punto cero provienen de la calibración de entrenamiento del modelo y se almacenan en el archivo del modelo. Se exponen como input_scale, input_zero_point, output_scale y output_zero_point – cada uno una lista con una entrada por cada tensor de entrada o salida.
ml.utils.quantize() y ml.utils.dequantize() aplican las fórmulas contra un índice de salida 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 funciones devuelven el valor sin cambios cuando el dtype de salida en el índice dado ya es float, por lo que la llamada es segura independientemente del estado de cuantización del modelo.
7.8.2. Lo que el script ve en el lado de salida¶
Lo que devuelve predict() depende de si hay un postprocesador registrado.
Sin postprocesador, las salidas enteras en bruto del motor se decuantizan automáticamente a float y se devuelven como una lista de objetos ndarray float. El script recibe números de valor real listos para leer. Esta es la ruta correcta para las redes de clasificación, cuyo único tensor de salida ya es una lista de puntuaciones de confianza por clase sobre las que itera la aplicación – sin necesidad de un paso de decodificación. También es la ruta sencilla para poner en marcha rápidamente un modelo desconocido o para una inspección ad hoc desde el REPL.
Con un postprocesador registrado (mediante postprocess= en el constructor o callback= en la llamada a predict), los tensores cuantizados en bruto se entregan directamente al invocable del postprocesador. El postprocesador recibe los tensores cuantizados en bruto y es responsable de la decuantización que necesite.
La división es una elección de rendimiento. La decuantización automática asigna un nuevo tensor float para cada salida y recorre cada elemento. Un postprocesador que solo necesita unos pocos valores de cada tensor – aplicar un umbral a las puntuaciones de confianza y luego decodificar los cuadros de los supervivientes – se ahorra el coste de decuantizar el resto. Los decodificadores de cuadros incluidos en ml.postprocessing toman todos esta vía, y ml.utils.threshold() está construido exactamente para este caso: toma un tensor de puntuaciones cuantizado y devuelve los índices cuyos valores decuantizados superan un umbral de valor real, sin decuantizar el tensor completo.