
Vor dir steht wieder die Aufgabe der Objekterkennung. PrioritĂ€t hat die Arbeitsgeschwindigkeit bei akzeptabler Genauigkeit. Du nimmst die Architektur YOLOv3 und bildest sie weiter. Die Genauigkeit (mAp75) liegt ĂŒber 0,95. Aber die Laufgeschwindigkeit ist immer noch niedrig. Verdammt.
Heute umgehen wir die Quantisierung. Im Nachfolgenden betrachten wir Model Pruning â das Entfernen ĂŒberflĂŒssiger Teile des Netzwerks zur Beschleunigung der Inferenz ohne Verlust der Genauigkeit. Verdeutlicht wird, wo, wie viel und wie man schneiden kann. Wir werden untersuchen, wie man dies manuell durchfĂŒhrt und wo Automatisierung möglich ist. Am Ende â ein Repository auf Keras.
EinfĂŒhrung
An meinem vorherigen Arbeitsplatz, dem permischen Macroscop, habe ich mir eine Gewohnheit angeeignet â immer die AusfĂŒhrungszeit der Algorithmen zu ĂŒberwachen. Die Laufzeit der Netzwerke ĂŒberprĂŒfe ich immer durch einen Angemessenheitsfilter. Normalerweise bestehen state-of-the-art Modelle diesen Filter nicht, was mich zum Pruning gefĂŒhrt hat.
Pruning â ein altes Thema, das in den im Jahr 2017 behandelt wurde. Die Hauptidee ist die Reduzierung der GröĂe eines trainierten Netzwerks, ohne die Genauigkeit zu verlieren, indem verschiedene Knoten entfernt werden. Klingt cool, aber ich höre selten von seiner Anwendung. Wahrscheinlich fehlen Umsetzungen, es gibt keine russischsprachigen Artikel oder alle halten Pruning einfach fĂŒr ein Geheimnis und schweigen.
Aber lass uns das betrachten.
Ein Blick in die Biologie
Ich liebe es, wenn im Deep Learning Ideen aus der Biologie aufblitzen. Ihnen, wie der Evolution, kann man vertrauen (wusstest du, dass ReLU sehr Àhnlich ist wie ?)
Der Prozess des Model Pruning ist auch nahe an der Biologie. Die Reaktion des Netzwerks hier kann mit der PlastizitÀt des Gehirns verglichen werden. Einige interessante Beispiele finden sich im Buch :
- Das Gehirn einer Frau, die von Geburt an nur eine GehirnhĂ€lfte hatte, hat sich selbst umprogrammiert, um die Funktionen der fehlenden HĂ€lfte auszufĂŒhren.
- Ein Junge hat sich einen Teil seines Gehirns entfernt, der fĂŒr das Sehen verantwortlich war. Im Laufe der Zeit haben andere Teile des Gehirns diese Funktionen ĂŒbernommen. (Wiederholen wir nicht.)
So kann auch aus deinem Modell ein Teil schwacher Faltungen entfernt werden. Im schlimmsten Fall können die verbleibenden Faltungen die entfernten ersetzen.
Liebst du Transfer Learning oder lernst du von Grund auf neu?
Option Nummer eins. Du verwendest Transfer Learning fĂŒr Yolov3. Retina, Mask-RCNN oder U-Net. Aber meistens mĂŒssen wir nicht 80 Klassen von Objekten erkennen, wie in COCO. In meiner Praxis beschrĂ€nken sich die Klassen auf 1-2. Man könnte annehmen, dass die Architektur fĂŒr 80 Klassen hier ĂŒberdimensioniert ist. Es liegt nahe, die Architektur zu reduzieren. Und das möchte ich gerne tun, ohne die vorhandenen vortrainierten Gewichte zu verlieren.
Variante Nummer zwei. Vielleicht hast du viele Daten und Rechenressourcen oder brauchst einfach eine super individuelle Architektur. Egal. Aber du trainierst das Netzwerk von Grund auf. Der ĂŒbliche Ablauf â wir schauen uns die Datenstruktur an, wĂ€hlen eine ĂBERDIMENSIONIERTE Architektur aus und drĂŒcken Dropouts gegen Ăberanpassung. Ich habe Dropouts von 0.6 gesehen, Karl.
In beiden FÀllen kann das Netzwerk verkleinert werden. Das waren die Anreize. Jetzt lass uns klÀren, was es mit dem Pruning auf sich hat.
Allgemeiner Algorithmus
Wir haben beschlossen, dass wir Faltungen entfernen können. Das sieht ziemlich einfach aus:

Die Entfernung einer beliebigen Faltung ist ein Stress fĂŒr das Netzwerk, der normalerweise mit einem gewissen Anstieg des Fehlers einhergeht. Einerseits ist dieser Anstieg des Fehlers ein MaĂ dafĂŒr, wie korrekt wir die Faltungen entfernen (zum Beispiel zeigt ein groĂer Anstieg, dass wir etwas falsch machen). Aber ein kleiner Anstieg ist durchaus akzeptabel und wird oft durch anschlieĂendes leichtes Nachtraining mit einem niedrigen LR behoben. Wir fĂŒgen einen Schritt zum Nachtraining hinzu:

Jetzt mĂŒssen wir verstehen, wann wir unseren Zyklus Learning<->Pruning stoppen wollen. Hier kann es exotische FĂ€lle geben, in denen wir das Netzwerk auf eine bestimmte GröĂe und Geschwindigkeit reduzieren mĂŒssen (zum Beispiel fĂŒr mobile GerĂ€te). Der hĂ€ufigste Fall ist jedoch, dass wir den Zyklus fortsetzen, bis der Fehler den zulĂ€ssigen Wert ĂŒberschreitet. Wir fĂŒgen eine Bedingung hinzu:

So wird der Algorithmus klar. Es bleibt zu klÀren, wie wir die zu entfernenden Faltungen bestimmen.
Suche nach zu entfernenden Faltungen
Wir mĂŒssen einige Faltungen entfernen. Einfach unverblĂŒmt âwegzuschieĂenâ ist keine gute Idee, auch wenn es funktioniert. Aber wenn man einen Kopf hat, kann man nachdenken und versuchen, âschwacheâ Faltungen herauszufiltern, die man entfernen kann. Es gibt mehrere Optionen:
- . Die Idee besagt, dass Faltungen mit kleinen Gewichtswerten, einen geringen Beitrag zur endgĂŒltigen Entscheidungsfindung leisten.
- Kleinster L1-MaĂstab unter BerĂŒcksichtigung von Mittelwert und Standardabweichung. Wir ergĂ€nzen die Bewertung der Verteilung.
- . Eine genauere Bestimmung der unwesentlichen Faltungen, jedoch zeit- und ressourcenintensiv.
- Andere
Jede der Varianten hat das Recht auf Existenz und eigene Umsetzungsmerkmale. Hier betrachten wir die Variante mit der kleinsten L1-Metrik
Manueller Prozess fĂŒr YOLOv3
In der ursprĂŒnglichen Architektur sind Residualblöcke enthalten. Doch egal wie beeindruckend sie fĂŒr tiefe Netzwerke sind, sie stören uns etwas. Das Problem ist, dass Faltungen mit unterschiedlichen Indizes in diesen Schichten nicht entfernt werden können:

Deshalb bestimmen wir die Schichten, aus denen wir Faltungen frei entfernen können:

Jetzt erstellen wir den Arbeitszyklus:
- Wir laden die Aktivierungen hoch
- Wir schÀtzen ein, wie viel ausgeschnitten werden soll
- Wir schneiden aus
- Wir trainieren 10 Epochen mit LR=1e-4
- Wir testen
Das Entfernen der Faltungen ist nĂŒtzlich, um zu bewerten, welchen Teil wir in einem bestimmten Schritt entfernen können. Beispiele fĂŒr das Entfernen:

Wir sehen, dass fast ĂŒberall 5% der Faltungen eine sehr niedrige L1-Norm aufweisen und wir sie entfernen können. In jedem Schritt wurde solch ein Entfernen wiederholt und eine Bewertung durchgefĂŒhrt, aus welchen Schichten und wie viel entfernt werden kann.
Der gesamte Prozess war in 4 Schritte unterteilt (hier und ĂŒberall die Zahlen fĂŒr RTX 2060 Super):
| Schritt | mAp75 | Anzahl der Parameter, Mio. | GröĂe des Netzwerks, MB | Von der ursprĂŒnglichen, % | Laufzeit, ms | Bedingung zum Ausschneiden |
|---|---|---|---|---|---|---|
| 0 | 0.9656 | 60 | 241 | 100 | 180 | â |
| 1 | 0.9622 | 55 | 218 | 91 | 175 | 5% von allen |
| 2 | 0.9625 | 50 | 197 | 83 | 168 | 5% von allen |
| 3 | 0.9633 | 39 | 155 | 64 | 155 | 15% fĂŒr Schichten mit 400+ Faltungen |
| 4 | 0.9555 | 31 | 124 | 51 | 146 | 10% fĂŒr Schichten mit 100+ Faltungen |
Zu Schritt 2 kam ein positiver Effekt hinzu â eine Batch-GröĂe von 4 passte in den Speicher, was den Prozess des weiteren Trainings erheblich beschleunigte.
Im Schritt 4 wurde der Prozess gestoppt, da auch ein lÀngeres weiteres Training mAp75 nicht auf die alten Werte erhöhen konnte.
Letztendlich konnte der Inferenzprozess beschleunigt werden um 15%, die GröĂe reduziert werden um 35% und es ging keine Genauigkeit verloren.
Automatisierung fĂŒr einfachere Architekturen
FĂŒr einfachere Netzwerkarchitekturen (ohne bedingte add, concatenate und residual Blöcke) kann man sich ganz auf die Verarbeitung aller Faltungsschichten konzentrieren und den Prozess des Ausschneidens von Faltungen automatisieren.
Solch eine Variante habe ich implementiert .
Es ist ganz einfach: Sie benötigen nur die Verlustfunktion, den Optimierer und die Batch-Generatoren:
import pruning
from keras.optimizers import Adam
from keras.utils import Sequence
train_batch_generator = BatchGenerator...
score_batch_generator = BatchGenerator...
opt = Adam(lr=1e-4)
pruner = pruning.Pruner("config.json", "categorical_crossentropy", opt)
pruner.prune(train_batch, valid_batch)Falls nötig, können die Konfigurationsparameter geÀndert werden:
{
"input_model_path": "model.h5",
"output_model_path": "model_pruned.h5",
"finetuning_epochs": 10, # die Anzahl der Epochen zum Trainieren zwischen den Pruning-Schritten
"stop_loss": 0.1, # Verlust zum Stoppen des Prozesses
"pruning_percent_step": 0.05, # Anteil der Konvolutionen, die bei jedem Pruning-Schritt gelöscht werden
"pruning_standart_deviation_part": 0.2 # Verschiebung zur Begrenzung des Pruning-Anteils
}ZusĂ€tzlich wurde eine EinschrĂ€nkung basierend auf der Standardabweichung implementiert. Ziel ist es, den Teil der zu löschenden Elemente zu begrenzen, wobei Konvolutionen mit bereits "ausreichenden" L1-MaĂen ausgeschlossen werden:

Damit erlauben wir es, nur schwache Konvolutionen aus Verteilungen Àhnlich der rechten zu löschen und nicht die Löschung aus Verteilungen Àhnlich der linken zu beeinflussen:

Bei der AnnÀherung der Verteilung an die Normalverteilung kann der Koeffizient pruning_standart_deviation_part ausgewÀhlt werden aus:

Ich empfehle eine Toleranz von 2 Sigma. Alternativ kann man diese Besonderheit ignorieren und den Wert < 1.0 belassen.
Das Ergebnis ist ein Diagramm der NetzwerkgröĂe, Verluste und der Laufzeit des Netzwerks ĂŒber den gesamten Test, normiert auf 1.0. Zum Beispiel wurde hier die NetzwerkgröĂe fast halbiert, ohne an QualitĂ€t zu verlieren (ein kleines Convolutional-Netzwerk mit 100k Gewichten):

Die Laufgeschwindigkeit unterliegt normalen Schwankungen und hat sich praktisch nicht verÀndert. Das hat einen Grund:
- Die Anzahl der Konvolutionen wechselt von bequemen (32, 64, 128) zu weniger bequemen fĂŒr Grafikkarten â 27, 51 usw. Hier kann ich mich irren, aber wahrscheinlich hat das einen Einfluss.
- Die Architektur ist nicht breit, sondern durchgÀngig. Indem wir die Breite reduzieren, Àndern wir nicht die Tiefe. So verringern wir die Last, ohne die Geschwindigkeit zu beeinflussen.
Daher zeigt sich die Verbesserung in der Reduzierung der CUDA-Last beim Durchlauf um 20-30%, jedoch nicht in der Verringerung der Durchlaufzeit.
Ergebnisse
Lass uns reflektieren. Wir haben zwei Varianten des Pruning betrachtet â fĂŒr YOLOv3 (wenn man manuell arbeiten muss) und fĂŒr Netzwerke mit einfacheren Architekturen. Es ist klar, dass in beiden FĂ€llen eine Verringerung der NetzwerkgröĂe und Beschleunigung ohne Verlust an Genauigkeit erreicht werden kann. Ergebnisse:
- Verringerung der GröĂe
- Beschleunigung des Durchlaufs
- Verringerung der CUDA-Last
- Als Folge davon ist die Umweltfreundlichkeit (Wir optimieren die zukĂŒnftige Nutzung von Rechenressourcen. Irgendwo freut sich eine )
Anhang
- Nach dem Pruning-Schritt kann noch Quantisierung hinzugefĂŒgt werden (zum Beispiel mit TensorRT)
- Tensorflow bietet Möglichkeiten fĂŒr . Es funktioniert.
- Ich möchte mich weiterentwickeln und freue mich ĂŒber Hilfe.
Quelle: habr.com
