7.8. Tensor-I/O

Die Engine akzeptiert einen einzelnen Tensor auf der Eingabeseite und erzeugt einen oder mehrere auf der Ausgabeseite. Die Tensoren sind ndarray-Objekte mit der Form, dem dtype und dem Deskriptor-Vokabular, das das numpy-Kapitel eingeführt hat. Ihre Formen und dtypes stammen aus der Modelldatei und werden über input_shape / output_shape und input_dtype / output_dtype gemeldet.

7.8.1. Quantisierung

Die meisten Netze, die die Kamera ausführt, arbeiten mit quantisierten ganzzahligen Tensoren – int8 oder uint8 – um in das RAM- und Rechenbudget der Kamera zu passen. Ein quantisierter Tensor trägt ganzzahlige Werte, die reellwertige Zahlen über eine Skalierung und einen Nullpunkt pro Tensor darstellen:

\[\text{real} = \text{scale} \times (q - \text{zero_point})\]
\[q = \mathrm{round}(\text{real} / \text{scale}) + \text{zero_point}\]

Die Skalierung und der Nullpunkt stammen aus der Kalibrierung des Modells zur Trainingszeit und sind in der Modelldatei gespeichert. Sie werden als input_scale, input_zero_point, output_scale und output_zero_point bereitgestellt – jeweils eine Liste mit einem Eintrag pro Eingabe- oder Ausgabetensor.

ml.utils.quantize() und ml.utils.dequantize() wenden die Formeln auf einen angegebenen Ausgabeindex an:

import ml.utils

real_tensor = ml.utils.dequantize(model, q_tensor, index=0)
q_tensor    = ml.utils.quantize(model, real_tensor, index=0)

Beide Funktionen geben den Wert unverändert zurück, wenn der Ausgabe-dtype am angegebenen Index bereits Float ist, sodass der Aufruf unabhängig vom Quantisierungsstatus des Modells sicher ist.

7.8.2. Was das Skript auf der Ausgabeseite sieht

Was predict() zurückgibt, hängt davon ab, ob ein Post-Prozessor registriert ist.

Ohne Post-Prozessor werden die rohen ganzzahligen Ausgaben der Engine automatisch dequantisiert zu Float und als Liste von Float-ndarray-Objekten zurückgegeben. Das Skript erhält reellwertige Zahlen, die zum Auslesen bereit sind. Dies ist der richtige Pfad für Klassifizierungsnetze, deren einzelner Ausgabetensor bereits eine Liste von Konfidenzwerten pro Klasse ist, über die die Anwendung iteriert – kein Dekodierschritt erforderlich. Es ist auch der einfache Weg, um ein unbekanntes Modell schnell zum Laufen zu bringen oder für eine ad-hoc-Inspektion aus dem REPL.

Mit einem registrierten Post-Prozessor (über postprocess= am Konstruktor oder callback= am predict-Aufruf) werden die rohen quantisierten Tensoren direkt an das aufrufbare Objekt des Post-Prozessors übergeben. Der Post-Prozessor erhält die rohen quantisierten Tensoren und ist für die Dequantisierung verantwortlich, die er benötigt.

Die Aufteilung ist eine Performance-Entscheidung. Die automatische Dequantisierung reserviert für jede Ausgabe einen neuen Float-Tensor und durchläuft jedes Element. Ein Post-Prozessor, der nur ein paar Werte aus jedem Tensor benötigt – die Konfidenzwerte schwellenwertbasiert filtern, dann Boxen für die Verbleibenden dekodieren – spart sich die Kosten für die Dequantisierung des Rests. Die unter ml.postprocessing ausgelieferten Box-Dekoder nehmen alle diesen Weg, und ml.utils.threshold() ist genau für diesen Fall gebaut: Es nimmt einen quantisierten Score-Tensor entgegen und gibt die Indizes zurück, deren dequantisierte Werte einen reellwertigen Schwellenwert überschreiten, ohne den gesamten Tensor zu dequantisieren.