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.