7.15. Escribir el tuyo propio#

Cuando el catálogo no cubre un modelo – una red de investigación cuya disposición de salida es a medida, un ajuste a una arquitectura existente, un tensor cuya interpretación semántica es específica de la aplicación – la aplicación proporciona su propio postprocesador. El protocolo es simple: un invocable que toma (model, inputs, outputs) y devuelve lo que la aplicación espera de predict().

Una clase con __call__ es la forma convencional:

class MyPostprocessor:
    def __init__(self, threshold=0.5):
        self.threshold = threshold

    def __call__(self, model, inputs, outputs):
        ...
        return result

Una función simple también funciona – el motor solo comprueba que el objeto sea invocable.

7.15.1. Conectarlo#

Dos puntos de conexión. El kwarg postprocess= en el constructor vincula el invocable para cada llamada a predict() en el modelo:

model = ml.Model("/rom/my_model.tflite",
                 postprocess=MyPostprocessor())

Para anular el vínculo en una sola llamada – intercambiar decodificadores sin recargar el modelo – pasa callback= a predict directamente:

result = model.predict([img], callback=MyOtherPostprocessor())

La firma del invocable es la misma en cualquiera de los casos.

7.15.2. Lo que recibe el invocable#

  • model – la instancia de Model, útil para los parámetros de cuantización (output_scale, output_zero_point, output_dtype) y las dimensiones de entrada (input_shape).

  • inputs – la lista de entradas que la aplicación pasó a predict(). El primer elemento suele ser la instancia vinculada de Normalization; su atributo roi es lo que NMS espera para reasignar los cuadros de vuelta a la imagen original.

  • outputs – los tensores de salida en bruto como una lista de objetos ndarray, en su dtype nativo. Las salidas de tipo flotante llegan tal cual; las salidas de tipo entero llegan cuantizadas.

7.15.3. Aritmética cuantizada#

Los decodificadores incluidos recurren todos a los mismos ayudantes en ml.utils, y uno personalizado normalmente quiere el mismo patrón: quantize() lleva un umbral flotante al espacio cuantizado del modelo, threshold() filtra sin descuantizar todo el tensor, y dequantize() se ejecuta una vez sobre los supervivientes. sigmoid() y logit() están disponibles para redes cuyos canales de salida son logits pre-sigmoide (los detectores MediaPipe son el caso canónico).

Para modelos con salidas de tipo flotante – cabezales de regresión, modelos con una capa final de descuantización incorporada – los ayudantes de cuantización pasan sin cambios, de modo que el mismo código de postprocesador funciona contra cualquiera de los dtypes sin casos especiales.

7.15.4. Valor de retorno#

Lo que sea que el invocable devuelva es lo que devuelve predict(). Para los decodificadores que emiten cuadros, la convención es enviar los candidatos a través de un NMS y devolver sus listas por clase – la forma de llamada que documenta la supresión de no máximos y que el recorrido de YOLOv8 construye en su contexto. Para cualquier otra cosa, devuelve lo que la aplicación encuentre conveniente: un único ndarray, una cadena de etiqueta, una tupla (class, score, embedding), un diccionario.