7.15. Scriverne uno proprio

Quando il catalogo non copre un modello – una rete di ricerca la cui disposizione di output è su misura, una modifica a un’architettura esistente, un tensore la cui interpretazione semantica è specifica dell’applicazione – l’applicazione fornisce il proprio post-processore. Il protocollo è semplice: un callable che prende (model, inputs, outputs) e restituisce qualunque cosa l’applicazione si aspetti da predict().

Una classe con __call__ è la forma convenzionale:

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

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

Funziona anche una semplice funzione – il motore verifica solo che l’oggetto sia callable.

7.15.1. Collegarlo

Due punti di collegamento. Il kwarg postprocess= nel costruttore associa il callable a ogni chiamata di predict() sul modello:

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

Per sovrascrivere l’associazione per una singola chiamata – scambiare i decodificatori senza ricaricare il modello – passa callback= direttamente a predict:

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

La firma del callable è la stessa in entrambi i casi.

7.15.2. Cosa riceve il callable

  • model – l’istanza Model, utile per i parametri di quantizzazione (output_scale, output_zero_point, output_dtype) e per le dimensioni di input (input_shape).

  • inputs – la lista di input che l’applicazione ha passato a predict(). Il primo elemento è solitamente l’istanza Normalization associata; il suo attributo roi è ciò che NMS si aspetta per rimappare i box nell’immagine originale.

  • outputs – i tensori di output grezzi come una lista di oggetti ndarray, nel loro dtype nativo. Gli output float arrivano così come sono; gli output interi arrivano quantizzati.

7.15.3. Aritmetica quantizzata

I decodificatori forniti ricorrono tutti agli stessi helper in ml.utils, e uno personalizzato di solito vuole lo stesso schema: quantize() solleva una soglia float nello spazio quantizzato del modello, threshold() filtra senza dequantizzare l’intero tensore, e dequantize() viene eseguito una volta sui sopravvissuti. sigmoid() e logit() sono disponibili per reti i cui canali di output sono logit pre-sigmoide (i rilevatori MediaPipe sono il caso canonico).

Per i modelli con output float – teste di regressione, modelli con un livello finale di dequantizzazione integrato – gli helper di quantizzazione passano invariati, quindi lo stesso codice del post-processore funziona con entrambi i dtype senza casi speciali.

7.15.4. Valore di ritorno

Qualunque cosa il callable restituisca è ciò che predict() restituisce. Per i decodificatori che emettono box la convenzione è inviare i candidati attraverso un NMS e restituire le sue liste per classe – la forma di chiamata documentata in non-max suppression e costruita in contesto nella spiegazione dettagliata di YOLOv8. Per qualsiasi altra cosa, restituisci ciò che l’applicazione trova comodo: un singolo ndarray, una stringa di etichetta, una tupla (class, score, embedding), un dizionario.