7.15. Escrever o seu próprio

Quando o catálogo não cobre um modelo – uma rede de investigação cujo layout de saída é personalizado, uma modificação a uma arquitetura existente, um tensor cuja interpretação semântica é específica da aplicação – a aplicação fornece o seu próprio pós-processador. O protocolo é simples: um chamável que recebe (model, inputs, outputs) e retorna o que quer que a aplicação espera 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 que o objeto é chamável.

7.15.1. Ligá-lo

Dois pontos de ligação. O argumento de palavra-chave postprocess= no construtor vincula o chamável para cada chamada de predict() no modelo:

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

Para substituir a vinculação numa única chamada – trocar descodificadores sem recarregar o modelo – passar callback= ao predict diretamente:

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

A assinatura do chamável é a mesma em qualquer dos casos.

7.15.2. O que o chamável 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 a predict(). O primeiro elemento é normalmente a instância de Normalization vinculada; o seu atributo roi é o que NMS espera para remapear caixas de volta para a imagem original.

  • outputs – os tensores de saída brutos como uma lista de objetos ndarray, no seu dtype nativo. As saídas float chegam como estão; as saídas inteiras chegam quantizadas.

7.15.3. Aritmética quantizada

Todos os descodificadores incluídos recorrem aos mesmos auxiliares em ml.utils, e um personalizado normalmente quer o mesmo padrão: quantize() eleva um limiar float 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é-sigmoid (os detetores MediaPipe são o caso canónico).

Para modelos com saídas float – cabeças de regressão, modelos com uma camada de desquantização final incorporada – os auxiliares de quantização passam sem alterações, pelo que o mesmo código do pós-processador funciona contra qualquer dtype sem tratamento especial.

7.15.4. Valor de retorno

O que quer que o chamável retorne é o que predict() retorna. Para descodificadores que emitem caixas, a convenção é enviar candidatos através de um NMS e retornar as suas listas por classe – a forma de chamada que supressão de não-máximos documenta e o tutorial YOLOv8 constrói em contexto. Para qualquer outra coisa, retornar o que for conveniente para a aplicação: um único ndarray, uma cadeia de etiqueta, um tuplo de (class, score, embedding), um dicionário.