Jedi-teknik för att minska konvolutionella nätverk - beskärning
Innan du igen är uppgiften att upptäcka föremål. Prioriteten är drifthastighet med acceptabel noggrannhet. Du tar YOLOv3-arkitekturen och vidareutbildar den. Noggrannheten (mAp75) är större än 0.95. Men körhastigheten är fortfarande låg. Skit.
Idag kommer vi att kringgå kvantisering. Och under snittet ska vi titta Modell beskärning — trimma redundanta delar av nätverket för att påskynda inferens utan förlust av noggrannhet. Det är tydligt var, hur mycket och hur man skär. Låt oss ta reda på hur du gör detta manuellt och var du kan automatisera det. I slutet finns ett förråd på keras.
Inledning
På min tidigare arbetsplats, Macroscop i Perm, fick jag en vana - att alltid övervaka exekveringstiden för algoritmer. Och kontrollera alltid nätverkets körtid genom ett lämplighetsfilter. Vanligtvis klarar inte den senaste produktionen detta filter, vilket ledde mig till beskärning.
Beskärning är ett gammalt ämne som diskuterades i Stanford föreläsningar under 2017. Huvudtanken är att minska storleken på det tränade nätverket utan att tappa exaktheten genom att ta bort olika noder. Det låter coolt, men jag hör sällan om användningen. Förmodligen finns det inte tillräckligt med implementeringar, det finns inga ryskspråkiga artiklar, eller helt enkelt anser alla att det beskär kunskap och förblir tysta.
Men låt oss ta isär det
En inblick i biologin
Jag älskar när Deep Learning tittar på idéer som kommer från biologin. De, precis som evolution, kan lita på (visste du att ReLU är väldigt lik funktion av neuronaktivering i hjärnan?)
Modellbeskärningsprocessen ligger också nära biologi. Nätverkets svar här kan jämföras med hjärnans plasticitet. Det finns ett par intressanta exempel i boken. Norman Doidge:
Hjärnan hos en kvinna som föddes med bara en halva har programmerat om sig själv för att utföra funktionerna hos den saknade halvan.
Killen sköt av den del av hans hjärna som ansvarar för synen. Med tiden tog andra delar av hjärnan över dessa funktioner. (vi försöker inte upprepa)
På samma sätt kan du klippa bort några av de svaga vecken från din modell. Som en sista utväg kommer de återstående buntarna att hjälpa till att ersätta de skurna.
Älskar du Transfer Learning eller lär du dig från grunden?
Alternativ nummer ett. Du använder Transfer Learning på Yolov3. Retina, Mask-RCNN eller U-Net. Men för det mesta behöver vi inte känna igen 80 objektklasser som i COCO. I min praktik är allt begränsat till årskurs 1-2. Man kan anta att arkitekturen för 80 klasser är överflödig här. Detta talar för att arkitekturen behöver göras mindre. Dessutom skulle jag vilja göra detta utan att förlora de befintliga förtränade vikterna.
Alternativ nummer två. Kanske har du mycket data och datorresurser, eller bara behöver en superanpassad arkitektur. spelar ingen roll. Men du lär dig nätverket från grunden. Det vanliga förfarandet är att titta på datastrukturen, välja en arkitektur som är EXCESSIG i kraft och skjuta upp avhopp från omskolning. Jag såg 0.6 avhopp, Karl.
I båda fallen kan nätverket minskas. Motiverad. Nu ska vi ta reda på vilken typ av beskärning av omskärelse
Allmän algoritm
Vi bestämde att vi kunde ta bort buntarna. Det ser ganska enkelt ut:
Att ta bort eventuell faltning är stressande för nätverket, vilket vanligtvis leder till en viss ökning av antalet fel. Å ena sidan är denna ökning av fel en indikator på hur korrekt vi tar bort faltningar (till exempel en stor ökning indikerar att vi gör något fel). Men en liten ökning är helt acceptabelt och elimineras ofta av efterföljande lätt tilläggsträning med en liten LR. Lägg till ytterligare ett träningssteg:
Nu måste vi ta reda på när vi vill stoppa vår Learning<->Pruning loop. Det kan finnas exotiska alternativ här när vi behöver minska nätet till en viss storlek och hastighet (till exempel för mobila enheter). Det vanligaste alternativet är dock att fortsätta cykeln tills felet blir högre än acceptabelt. Lägg till ett villkor:
Så algoritmen blir tydlig. Det återstår att ta reda på hur man bestämmer de raderade veckningarna.
Sök efter borttagna paket
Vi måste ta bort några veck. Att rusa framåt och "skjuta" vem som helst är en dålig idé, även om det kommer att fungera. Men eftersom du har ett huvud kan du tänka och försöka välja "svaga" veck för borttagning. Det finns flera alternativ:
Vart och ett av alternativen har rätt till liv och sina egna implementeringsfunktioner. Här överväger vi alternativet med det minsta L1-måttet
Manuell process för YOLOv3
Den ursprungliga arkitekturen innehåller restblock. Men oavsett hur coola de är för djupa nätverk, kommer de att hindra oss något. Svårigheten är att du inte kan ta bort avstämningar med olika index i dessa lager:
Låt oss därför välja lager från vilka vi fritt kan ta bort avstämningar:
Låt oss nu bygga en arbetscykel:
Laddar upp aktiveringar
Funderar på hur mycket man ska skära
Sluta
Lärande 10 epoker med LR=1e-4
Testning
Att lossa veck är användbart för att uppskatta hur mycket del vi kan ta bort vid ett visst steg. Exempel på lossning:
Vi ser att nästan överallt 5% av vecken har en mycket låg L1-norm och vi kan ta bort dem. Vid varje steg upprepades denna lossning och en bedömning gjordes av vilka lager och hur många som kunde skäras ut.
Hela processen slutfördes i fyra steg (nummer här och överallt för RTX 4 Super):
Steg
mAp75
Antal parametrar, miljoner
Nätverksstorlek, mb
Från initial, %
Körtid, fröken
Omskärelsetillstånd
0
0.9656
60
241
100
180
-
1
0.9622
55
218
91
175
5% av alla
2
0.9625
50
197
83
168
5% av alla
3
0.9633
39
155
64
155
15 % för lager med 400+ veck
4
0.9555
31
124
51
146
10 % för lager med 100+ veck
En positiv effekt lades till i steg 2 - batchstorlek 4 passade in i minnet, vilket avsevärt påskyndade processen med ytterligare träning.
Vid steg 4 stoppades processen pga inte ens långvarig tilläggsutbildning höjde mAp75 till gamla värden.
Som ett resultat lyckades vi påskynda slutsatsen med 15%, minska storleken med 35% och inte förlora exakt.
Automation för enklare arkitekturer
För enklare nätverksarkitekturer (utan villkorlig addering, konkaternerade och resterande block) är det fullt möjligt att fokusera på att bearbeta alla faltningslager och automatisera processen att klippa ut faltningar.
Jag implementerade det här alternativet här.
Det är enkelt: du behöver bara en förlustfunktion, en optimerare och batchgeneratorer:
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)
Om det behövs kan du ändra konfigurationsparametrarna:
{
"input_model_path": "model.h5",
"output_model_path": "model_pruned.h5",
"finetuning_epochs": 10, # the number of epochs for train between pruning steps
"stop_loss": 0.1, # loss for stopping process
"pruning_percent_step": 0.05, # part of convs for delete on every pruning step
"pruning_standart_deviation_part": 0.2 # shift for limit pruning part
}
Dessutom implementeras en begränsning baserad på standardavvikelsen. Målet är att begränsa den del som tas bort, exklusive veck med redan "tillräckliga" L1-mått:
Således tillåter vi dig att ta bort endast svaga faltningar från distributioner som liknar den högra och inte påverka borttagningen från distributioner som liknar den vänstra:
När fördelningen närmar sig normal kan pruning_standart_deviation_part-koefficienten väljas från:
Jag rekommenderar ett antagande om 2 sigma. Eller så kan du ignorera den här funktionen och lämna värdet < 1.0.
Utdata är ett diagram över nätverksstorlek, förlust och nätverkskörtid för hela testet, normaliserat till 1.0. Till exempel, här reducerades nätverksstorleken med nästan 2 gånger utan kvalitetsförlust (litet konvolutionellt nätverk med 100 XNUMX vikter):
Körhastigheten är föremål för normala fluktuationer och förblir praktiskt taget oförändrad. Det finns en förklaring till detta:
Antalet faltningar ändras från bekvämt (32, 64, 128) till inte det mest bekväma för grafikkort - 27, 51, etc. Jag kan ha fel här, men troligen har det en effekt.
Arkitekturen är inte bred, men konsekvent. Genom att minska bredden påverkar vi inte djupet. Således minskar vi belastningen, men ändrar inte hastigheten.
Därför uttrycktes förbättringen i en minskning av CUDA-belastningen under körningen med 20-30 %, men inte i en minskning av körtiden
Resultat av
Låt oss reflektera. Vi övervägde 2 alternativ för beskärning - för YOLOv3 (när du måste arbeta med händerna) och för nätverk med enklare arkitekturer. Det kan ses att i båda fallen är det möjligt att uppnå minskning av nätverksstorlek och snabbhet utan förlust av noggrannhet. Resultat:
Minska storleken
Accelerationskörning
Minska CUDA-belastningen
Som ett resultat, miljövänlighet (Vi optimerar den framtida användningen av datorresurser. Någonstans är man nöjd Greta Thunberg)
Appendix
Efter beskärningssteget kan du lägga till kvantisering (till exempel med TensorRT)