7.15. Einen eigenen schreiben¶
Wenn der Katalog ein Modell nicht abdeckt – ein Forschungsnetz, dessen Ausgabelayout maßgeschneidert ist, eine Anpassung an eine bestehende Architektur, ein Tensor, dessen semantische Interpretation anwendungsspezifisch ist –, stellt die Anwendung ihren eigenen Post-Prozessor bereit. Das Protokoll ist schlicht: ein Callable, das (model, inputs, outputs) entgegennimmt und das zurückgibt, was die Anwendung von predict() erwartet.
Eine Klasse mit __call__ ist die übliche Form:
class MyPostprocessor:
def __init__(self, threshold=0.5):
self.threshold = threshold
def __call__(self, model, inputs, outputs):
...
return result
Eine einfache Funktion funktioniert ebenfalls – die Engine prüft nur, ob das Objekt aufrufbar ist.
7.15.1. Einbinden¶
Zwei Anbindungspunkte. Das kwarg postprocess= am Konstruktor bindet das Callable für jeden Aufruf von predict() am Modell:
model = ml.Model("/rom/my_model.tflite",
postprocess=MyPostprocessor())
Um die Bindung für einen einzelnen Aufruf zu überschreiben – Dekoder austauschen, ohne das Modell neu zu laden –, übergibt man callback= direkt an predict:
result = model.predict([img], callback=MyOtherPostprocessor())
Die Signatur des Callable ist in beiden Fällen identisch.
7.15.2. Was das Callable empfängt¶
model– dieModel-Instanz, nützlich für die Quantisierungsparameter (output_scale,output_zero_point,output_dtype) und die Eingabedimensionen (input_shape).inputs– die Liste der Eingaben, die die Anwendung anpredict()übergeben hat. Das erste Element ist gewöhnlich die gebundeneNormalization-Instanz; ihrroi-Attribut ist das, wasNMSzum Zurückabbilden der Rahmen in das ursprüngliche Bild erwartet.outputs– die rohen Ausgabetensoren als Liste vonndarray-Objekten in ihrem nativen dtype. Float-Ausgaben kommen unverändert an; Integer-Ausgaben kommen quantisiert an.
7.15.3. Quantisierte Arithmetik¶
Die mitgelieferten Dekoder greifen alle auf dieselben Hilfsfunktionen in ml.utils zurück, und ein benutzerdefinierter möchte gewöhnlich dasselbe Muster: quantize() hebt einen Float-Schwellenwert in den quantisierten Raum des Modells, threshold() filtert, ohne den gesamten Tensor zu dequantisieren, und dequantize() läuft einmal über die Überlebenden. sigmoid() und logit() stehen für Netze zur Verfügung, deren Ausgabekanäle Pre-Sigmoid-Logits sind (die MediaPipe-Detektoren sind der kanonische Fall).
Für Modelle mit Float-Ausgaben – Regressionsköpfe, Modelle mit einer eingebackenen finalen Dequantize-Schicht – werden die Quantisierungshilfsfunktionen unverändert durchgereicht, sodass derselbe Post-Prozessor-Code gegen beide dtypes ohne Sonderbehandlung funktioniert.
7.15.4. Rückgabewert¶
Was auch immer das Callable zurückgibt, ist das, was predict() zurückgibt. Für rahmenausgebende Dekoder besteht die Konvention darin, Kandidaten durch ein NMS zu schieben und seine Listen pro Klasse zurückzugeben – die Aufrufform, die Non-Maximum-Suppression dokumentiert und die YOLOv8-Durchführung im Kontext aufbaut. Für alles andere gibt man zurück, was die Anwendung praktisch findet: ein einzelnes ndarray, eine Label-Zeichenkette, ein Tupel aus (class, score, embedding), ein Dictionary.