7.2. Was ML verändert hat¶
Das image-Modul enthält eine Handvoll veralteter Erkennungsmethoden – find_features() für die Haar-Kaskaden-Gesichtserkennung, find_eye() für den festen Pupillenfinder, find_hog() für Zusammenfassungen der Gradientenrichtung, die Pfade find_keypoints() und find_lbp() für beliebige Schlüsselpunkte. Sie alle funktionieren noch; sie alle wurden durch die Machine-Learning-Pipeline abgelöst.
7.2.1. Die klassische Aufteilung: handentworfene Zusammenfassungen, gelernte Entscheidungen¶
Eine klassische Sehpipeline war eine zweistufige Angelegenheit. Der erste Schritt verwandelte rohe Pixel in einen kompakten Satz von Zahlen, der so gewählt war, dass er zusammenfasste, was im Bild zu sehen war – nicht die Pixelwerte selbst, sondern eine kürzere Beschreibung dessen, welche Muster wo auftraten. Der zweite Schritt nahm diese Zusammenfassung und traf eine Entscheidung: Gesicht oder nicht, dieses Objekt oder jenes, dasselbe Ziel oder ein anderes.
Die Aufteilung war von Bedeutung, weil die beiden Schritte unterschiedliche Urheber hatten. Der erste Schritt wurde von einem Menschen geschrieben. Jemand setzte sich hin und entschied, dass der Helligkeitsunterschied zwischen zwei bestimmten Rechtecken eine gute Zusammenfassung einer Augenregion war, dass die dominante Kantenrichtung in jeder Zelle eines Gitters eine gute Zusammenfassung der Kontur einer stehenden Person war, dass das Hell-oder-Dunkel-Muster um jedes Pixel herum eine gute Zusammenfassung der lokalen Textur war. Jede dieser Entscheidungen war ein handgeschriebener Algorithmus – geschrieben, getestet und veröffentlicht. Die oben genannten veralteten Methoden waren allesamt Zusammenfassungen dieser Art, die zu Standardwerkzeugen geworden waren:
find_features()fasst einen Bildausschnitt zusammen, indem es die Helligkeit innerhalb mehrerer Rechtecke aufaddiert und die Summen vergleicht. Die Rechteckanordnungen wurden gewählt, weil menschliche Gesichter zuverlässige Hell-gegen-Dunkel-Kontraste zeigen: Augenbrauen gegen Wangen, Augenhöhlen gegen Stirn, Nase gegen umgebende Haut.find_hog()fasst ein Bild zusammen, indem es ein Gitter aus kleinen Zellen durchläuft und festhält, welche Kantenrichtung in jeder Zelle dominiert. Das Gitter wurde gewählt, weil die Kontur einer stehenden Person ein erkennbares Muster von Kantenrichtungen erzeugt, unabhängig von Kleidung oder Beleuchtung.find_lbp()fasst die Nachbarschaft jedes Pixels zusammen, indem es kodiert, welche der umgebenden Pixel heller und welche dunkler sind. Die Kodierung wurde gewählt, weil diese heller-als / dunkler-als-Muster die Textur einer Oberfläche unabhängig von der Gesamtbeleuchtung erfassen.find_keypoints()findet Eckpunkte im Bild und beschreibt den Bereich um jede Ecke auf eine Weise, die gleich bleibt, wenn die Ecke gedreht wird. Das Ecken-und-Rotations-Schema wurde gewählt, weil dieselben Ecken wieder auftauchen, wenn eine Szene aus einem anderen Winkel betrachtet wird.
Sobald eine Zusammenfassung von Hand geschrieben war, konnte ein kleiner Lernschritt darauf die Zahlen zu einer Entscheidung kombinieren. Der Gesichtserkennungsalgorithmus setzte einen Lernschritt auf die Rechteckdifferenz-Zusammenfassung auf und trainierte ihn anhand beschrifteter Gesichts- und Nichtgesichtsbilder, um zu lernen, welche Kombinationen von Differenzen ein Gesicht signalisieren. Die Kantenrichtungs-Zusammenfassung speiste in einen Lernschritt ein, der anhand beschrifteter Personen- und Nichtpersonenbilder trainiert wurde. Die Eckdeskriptoren speisten in einen Abgleichschritt ein, der lernte, wie viel Gewicht jeder Ecke zu geben ist. Jeder dieser zweiten Schritte ist ein Lernalgorithmus – nach modernen Maßstäben ein kleiner, aber ein Lernalgorithmus.
Worauf es ankam, war die Aufteilung des Beitrags. Der Mensch trug die Zusammenfassung bei. Die Maschine lernte die Kombination. Ein neues Ziel hinzuzufügen bedeutete, eine neue Zusammenfassung zu schreiben.
7.2.2. Was neuronale Netze verändert haben¶
Ein neuronales Netz löscht diese Aufteilung. Die ersten Schichten des Netzes leisten die Zusammenfassungsarbeit, die früher die handgeschriebenen Algorithmen erledigten – das Erkennen von Kanten, Ecken, orientierten Balken, Texturen, genau die Dinge, auf deren Erkennung die oben genannten veralteten Methoden jeweils abgestimmt waren – aber sie sind nicht handgeschrieben. Sie werden aus denselben Trainingsdaten gelernt, aus denen auch der Entscheidungsschritt gelernt wird, in einem einzigen Trainingsdurchlauf, der beide Hälften des Netzes gleichzeitig anpasst. Die tieferen Schichten leisten das Kombinieren, das früher der kleine Lernschritt auf den handgeschriebenen Zusammenfassungen erledigte, ebenfalls gelernt, im selben Durchlauf.
Die Veränderung darin, wer was entwirft, ist vollständig:
Der Mensch entwirft die Eingabe – aufgenommene Einzelbilder einer bestimmten Größe und eines bestimmten Formats.
Der Mensch entwirft die Ausgabe – das Layout des Ergebnistensors (ein Wert pro Klasse für die Klassifizierung, eine Liste von Rahmen für die Erkennung, ein Gitter von Schlüsselpunkten für Landmarken).
Der Mensch liefert beschriftete Trainingsdaten – genügend Beispiele des Ziels und genügend Beispiele von Nicht-Zielen, damit der Trainingsprozess etwas zum Lernen hat.
Alles zwischen Eingabe und Ausgabe wird vom Trainingsprozess erzeugt. Es gibt keinen separaten Schritt zum Schreiben von Zusammenfassungen. Die frühen Schichten entwickeln sich zu Kanten- und Texturdetektoren, nicht weil jemand sie so geschrieben hat, sondern weil Kanten- und Texturerkennung das ist, was die Vorhersagen des Netzes mit den Labels in Übereinstimmung bringt. Die tieferen Schichten entwickeln sich aus demselben Grund zu Form- und Objektdetektoren. Beide Hälften werden gemeinsam trainiert, wodurch die Zusammenfassungen, die jede Schicht erzeugt, genau die Zusammenfassungen sein können, die die nächste Schicht benötigt – nicht die generischen, mit denen sich eine handgeschriebene Pipeline begnügen musste.
7.2.3. Zusammenspiel mit dem image-Modul¶
Neuronale-Netz-Pipelines nehmen nach wie vor über dieselben Sensor-APIs auf, zeichnen Ergebnisse über dieselben Primitive draw_rectangle() und draw_circle() und grenzen die Arbeit über dieselben ROIs (x, y, w, h) ein. Eine typische Pipeline nimmt ein Einzelbild auf, findet optional ein grobes Ziel mit einem klassischen Detektor wie find_blobs() und übergibt dessen Begrenzungsrahmen als ROI an die Inferenz, führt die Inferenz aus und annotiert die zurückgegebenen Erkennungen wieder in das ursprüngliche Einzelbild. Die klassischen Primitive sind das Substrat; das Netz ist der neue Schritt in der Mitte.