7.7. Normalization¶
ml.Model.predict() nimmt eine Liste von Eingaben entgegen, weil einige Netze mehr als einen Eingabetensor haben, aber die Liste hat keine Möglichkeit, Argumente pro Eingabe inline mitzuführen – es gibt keinen kwarg-Slot für „schneide diese Eingabe auf (x, y, w, h) zu, lasse aber die anderen Eingaben unangetastet“. ml.preprocessing.Normalization ist der Wrapper, der diese Lücke schließt. Eine Normalization-Instanz hält die Parameter für eine Eingabe; das Skript übergibt die umhüllte Eingabe in der predict-Liste, wann immer es etwas anderes als die Standardwerte benötigt.
Der häufigste Grund, darauf zurückzugreifen, ist, einen bestimmten Bereich des erfassten Einzelbilds anstelle des gesamten Bildes in das Netz zuzuschneiden.
7.7.1. Parameter¶
Normalization(scale=(0.0, 1.0),
mean=(0.0, 0.0, 0.0),
stdev=(1.0, 1.0, 1.0),
roi=None)
roi–(x, y, w, h)-Rechteck im Quell-Einzelbild, das vor dem Skalieren zugeschnitten werden soll. Standardmäßig das gesamte Einzelbild. Die meisten Verwendungen vonNormalizationsetzen nur diesen Parameter.scale– der(min, max)-Bereich, den Gleitkomma-Eingabetensoren nach der Normalisierung erwarten. Der Pixelbereich0..255wird linear in diesen Bereich abgebildet. Übliche Werte sind(0.0, 1.0)für mit ReLU trainierte Netze und(-1.0, 1.0)für symmetrisch normalisierte Netze.mean– pro Kanal(R, G, B)-Mittelwert, der nach dem Skalieren vom Bild subtrahiert wird. Stimmt mit den Kanalstatistiken überein, gegen die das Netz trainiert wurde –(0.485, 0.456, 0.406)für von ImageNet abgeleitete Netze ist das kanonische Beispiel. Graustufen-Netze reduzieren den Mittelwert mit dem Standardverfahren0.299*R + 0.587*G + 0.114*Bauf einen Luma-Wert.stdev– pro Kanal(R, G, B)-Standardabweichung, durch die das Bild geteilt wird, nachdem der Mittelwert subtrahiert wurde, wiederum passend zu den Trainingsstatistiken des Netzes. Für Graustufen-Netze auf dieselbe Weise auf Luma reduziert.
7.7.2. Wann Parameter eine Rolle spielen¶
scale, mean und stdev werden ignoriert, wenn der input_dtype des Netzes int8 oder uint8 ist. Bei Netzen mit ganzzahliger Eingabe werden die Bytes des zugeschnittenen Bildes direkt in den Tensor geschrieben, und der netzeigene input_scale und input_zero_point übernehmen die Umrechnung von Ganzzahl zu Reellzahl. Die drei Parameter spielen nur dann eine Rolle, wenn das Netz Gleitkomma-Eingaben erwartet.
roi wird in jedem Fall gelesen – es steuert, welcher Teil des Quell-Einzelbilds das Netz erreicht, unabhängig vom Eingabe-dtype.
7.7.3. ROI und Skalierung¶
Der ROI wird bilinear von seinen Quellabmessungen auf die Eingabeabmessungen des Netzes skaliert. Das Bild wird im Ziel zentriert, und die Skalierung füllt das Ziel aus – sie bewahrt das Seitenverhältnis nicht. Ein nicht-quadratischer ROI, der einer quadratischen Netzeingabe zugeführt wird, kommt horizontal oder vertikal gestreckt heraus.
Ob die Streckung eine Rolle spielt, hängt vom Netz ab. Gesichtserkennungs- und Landmarken-Modelle wie die MediaPipe-Familie (BlazeFace, FaceLandmarks, HandLandmarks, MoveNet) wurden gegen quadratische Zuschnitte trainiert und verschlechtern sich schnell, wenn das Eingabe-Seitenverhältnis nicht stimmt; für diese muss die Anwendung ihnen einen quadratischen ROI geben – entweder durch Erfassen in einer quadratischen Einzelbildgröße über window() oder durch Zuschneiden mit dem Parameter roi=. Objektdetektoren der YOLO-Familie werden typischerweise mit Augmentierung trainiert, die zufällige Streckungen enthält, und akzeptieren nicht-quadratische ROIs ohne großen Genauigkeitsverlust; das vollständige erfasste Einzelbild direkt hineinzugeben ist üblicherweise in Ordnung.
Wenn die Eingabeabmessungen des Netzes exakt mit dem ROI übereinstimmen, reduziert sich die Skalierung auf eine Kopie, was der günstigste Fall ist.
7.7.4. Die Standardwerte überschreiben¶
predict() umhüllt jede image.Image-Eingabe automatisch mit Normalization() – den oben genannten Standardparametern. Die meisten Modelle, die mit der Kamera ausgeliefert werden, wurden gegen Pixelbereiche trainiert, die die Standardwerte bereits abdecken, sodass der häufige Fall darin besteht, das Bild direkt zu übergeben:
result = model.predict([img])
Um einen benutzerdefinierten ROI zu verwenden – die häufigste Überschreibung – erstelle eine Normalization mit gesetztem ROI und binde das Bild daran:
from ml.preprocessing import Normalization
norm = Normalization(roi=(80, 60, 160, 120))
result = model.predict([norm(img)])
Um die Kanalstatistiken aus der Trainingszeit eines Netzes abzugleichen, setze die Gleitkomma-Parameter:
norm = Normalization(scale=(0.0, 1.0),
mean=(0.485, 0.456, 0.406),
stdev=(0.229, 0.224, 0.225))
result = model.predict([norm(img)])
Das Aufrufen der Normalization-Instanz auf dem Bild gibt eine neue gebundene Instanz zurück, aus der die Engine den Tensor füllt. Die gebundene Instanz ist das, was predict anstelle des rohen Bildes akzeptiert, und weil sie ein Objekt pro Eingabe ist, kann ein Netz mit mehreren Eingaben Bilder mit unterschiedlichen ROIs in derselben predict-Liste mischen.
Bei Netzen, die Eingaben erwarten, die die Anwendung bereits in Tensorform erzeugt hat – ein Puffer von einem Peripheriegerät, ein von einer anderen Pipeline berechnetes ndarray, nicht-bildliche numerische Daten – überspringe Normalization vollständig und übergib das ndarray oder ein aufrufbares Objekt, das es erzeugt. predict() reicht diese ohne Umhüllung an die Engine durch.