7.15. Escrevendo o seu próprio

Quando o catálogo não cobre um modelo – uma rede de pesquisa cujo layout de saída é sob medida, um ajuste em uma arquitetura existente, um tensor cuja interpretação semântica é específica da aplicação – a aplicação fornece seu próprio pós-processador. O protocolo é simples: um callable que recebe (model, inputs, outputs) e retorna o que quer que a aplicação espere de predict().

Uma classe com __call__ é a forma convencional:

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

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

Uma função simples também funciona – o motor apenas verifica se o objeto é callable.

7.15.1. Conectando-o

Dois pontos de conexão. O argumento nomeado postprocess= no construtor vincula o callable para cada chamada de predict() no modelo:

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

Para sobrescrever o vínculo para uma única chamada – trocar de decodificadores sem recarregar o modelo – passe callback= diretamente para predict:

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

A assinatura do callable é a mesma em qualquer caso.

7.15.2. O que o callable recebe

  • model – a instância de Model, útil para os parâmetros de quantização (output_scale, output_zero_point, output_dtype) e as dimensões de entrada (input_shape).

  • inputs – a lista de entradas que a aplicação passou para predict(). O primeiro elemento geralmente é a instância de Normalization vinculada; seu atributo roi é o que NMS espera para remapear as caixas de volta para a imagem original.

  • outputs – os tensores de saída brutos como uma lista de objetos ndarray, em seu dtype nativo. Saídas em ponto flutuante chegam como estão; saídas inteiras chegam quantizadas.

7.15.3. Aritmética quantizada

Os decodificadores fornecidos recorrem todos aos mesmos auxiliares em ml.utils, e um personalizado geralmente quer o mesmo padrão: quantize() eleva um limiar de ponto flutuante para o espaço quantizado do modelo, threshold() filtra sem desquantizar o tensor inteiro, e dequantize() é executado uma vez sobre os sobreviventes. sigmoid() e logit() estão disponíveis para redes cujos canais de saída são logits pré-sigmoide (os detectores MediaPipe são o caso canônico).

Para modelos com saídas em ponto flutuante – cabeças de regressão, modelos com uma camada final de desquantização embutida – os auxiliares de quantização passam adiante inalterados, de modo que o mesmo código de pós-processador funciona contra qualquer dtype sem tratamento especial.

7.15.4. Valor de retorno

O que quer que o callable retorne é o que predict() retorna. Para decodificadores que emitem caixas, a convenção é empurrar os candidatos através de um NMS e retornar suas listas por classe – o formato de chamada que non-max suppression documenta e que o passo a passo do YOLOv8 constrói em contexto. Para qualquer outra coisa, retorne o que for conveniente para a aplicação: um único ndarray, uma string de rótulo, uma tupla de (class, score, embedding), um dicionário.