Jedi-teknikk for å redusere konvolusjonelle nettverk - beskjæring
Før du igjen er oppgaven med å oppdage objekter. Prioriteten er operasjonshastighet med akseptabel nøyaktighet. Du tar YOLOv3-arkitekturen og trener den videre. Nøyaktigheten (mAp75) er større enn 0.95. Men løpsraten er fortsatt lav. Dritt.
I dag vil vi omgå kvantisering. Og under kuttet ser vi Modell Beskjæring — trimming av redundante deler av nettverket for å øke hastigheten på inferens uten tap av nøyaktighet. Det er tydelig hvor, hvor mye og hvordan du skal kutte. La oss finne ut hvordan du gjør dette manuelt og hvor du kan automatisere det. På slutten er det et depot på keras.
Innledning
På mitt forrige arbeidssted, Macroscop i Perm, fikk jeg en vane - å alltid overvåke utførelsestiden til algoritmer. Og kontroller alltid nettverkets kjøretid gjennom et tilstrekkelighetsfilter. Vanligvis passerer ikke toppmoderne produksjon dette filteret, noe som førte meg til Beskjæring.
Beskjæring er et gammelt tema som ble diskutert i Stanford forelesninger i 2017. Hovedideen er å redusere størrelsen på det trente nettverket uten å miste nøyaktigheten ved å fjerne ulike noder. Det høres kult ut, men jeg hører sjelden om bruken. Sannsynligvis er det ikke nok implementeringer, det er ingen russiskspråklige artikler, eller rett og slett alle anser det som beskjæring av kunnskap og forblir tause.
Men la oss ta det fra hverandre
Et innblikk i biologi
Jeg elsker det når Deep Learning ser på ideer som kommer fra biologi. De, som evolusjon, kan stole på (visste du at ReLU er veldig lik funksjon av nevronaktivering i hjernen?)
Modellbeskjæringsprosessen er også nær biologi. Nettverkets respons her kan sammenlignes med hjernens plastisitet. Det er et par interessante eksempler i boken. Norman Doidge:
Hjernen til en kvinne som ble født med bare den ene halvdelen, har omprogrammert seg til å utføre funksjonene til den manglende halvdelen.
Fyren skjøt av den delen av hjernen som var ansvarlig for synet. Over tid overtok andre deler av hjernen disse funksjonene. (vi prøver ikke å gjenta)
På samme måte kan du kutte ut noen av de svake konvolusjonene fra modellen din. Som en siste utvei vil de gjenværende buntene bidra til å erstatte de kuttede.
Elsker du Transfer Learning eller lærer du fra bunnen av?
Alternativ nummer én. Du bruker Transfer Learning på Yolov3. Retina, Mask-RCNN eller U-Net. Men mesteparten av tiden trenger vi ikke å gjenkjenne 80 objektklasser som i COCO. I min praksis er alt begrenset til klasse 1-2. Man kan anta at arkitekturen for 80 klasser er overflødig her. Dette tilsier at arkitekturen må gjøres mindre. Dessuten vil jeg gjerne gjøre dette uten å miste de eksisterende ferdigtrente vektene.
Alternativ nummer to. Kanskje du har mye data og dataressurser, eller trenger bare en supertilpasset arkitektur. spiller ingen rolle. Men du lærer nettverket fra bunnen av. Den vanlige prosedyren er å se på datastrukturen, velge en arkitektur som er OVERDRAGENDE i kraft, og presse frafall fra omskolering. Jeg så 0.6 frafall, Karl.
I begge tilfeller kan nettverket reduseres. Motivert. La oss nå finne ut hva slags omskjæring beskjæring er
Generell algoritme
Vi bestemte oss for at vi kunne fjerne buntene. Det ser ganske enkelt ut:
Å fjerne enhver konvolusjon er stressende for nettverket, noe som vanligvis fører til en viss økning i feil. På den ene siden er denne økningen i feil en indikator på hvor riktig vi fjerner konvolusjoner (for eksempel indikerer en stor økning at vi gjør noe galt). Men en liten økning er ganske akseptabelt og elimineres ofte ved påfølgende lett tilleggstrening med en liten LR. Legg til et ekstra treningstrinn:
Nå må vi finne ut når vi vil stoppe Learning<->Pruning loopen vår. Det kan være eksotiske alternativer her når vi skal redusere nettverket til en viss størrelse og hastighet (for eksempel for mobile enheter). Det vanligste alternativet er imidlertid å fortsette syklusen til feilen blir høyere enn akseptabelt. Legg til en betingelse:
Så algoritmen blir tydelig. Det gjenstår å finne ut hvordan man bestemmer de slettede konvolusjonene.
Søk etter slettede bunter
Vi må fjerne noen viklinger. Å skynde seg frem og "skyte" noen er en dårlig idé, selv om det vil fungere. Men siden du har et hode, kan du tenke og prøve å velge "svake" konvolusjoner for fjerning. Det er flere alternativer:
Hvert av alternativene har rett til liv og sine egne implementeringsfunksjoner. Her vurderer vi alternativet med det minste L1-målet
Manuell prosess for YOLOv3
Den opprinnelige arkitekturen inneholder restblokker. Men uansett hvor kule de er for dype nettverk, vil de hindre oss noe. Vanskeligheten er at du ikke kan slette avstemminger med forskjellige indekser i disse lagene:
La oss derfor velge lag som vi fritt kan slette avstemminger fra:
La oss nå bygge en arbeidssyklus:
Laster opp aktiveringer
Finner ut hvor mye du skal kutte
Kutt ut
Læring 10 epoker med LR=1e-4
Testing
Lossing av konvolusjoner er nyttig for å estimere hvor mye del vi kan fjerne på et bestemt trinn. Eksempler på lossing:
Vi ser at nesten overalt 5 % av konvolusjonene har en veldig lav L1-norm og vi kan fjerne dem. Ved hvert trinn ble denne lossingen gjentatt og det ble gjort en vurdering av hvilke lag og hvor mange som kunne kuttes ut.
Hele prosessen ble fullført i 4 trinn (tall her og overalt for RTX 2060 Super):
Trinn
mAp75
Antall parametere, millioner
Nettverksstørrelse, mb
Fra initial, %
Kjøretid, ms
Omskjæringstilstand
0
0.9656
60
241
100
180
-
1
0.9622
55
218
91
175
5 % av alle
2
0.9625
50
197
83
168
5 % av alle
3
0.9633
39
155
64
155
15 % for lag med 400+ viklinger
4
0.9555
31
124
51
146
10 % for lag med 100+ viklinger
En positiv effekt ble lagt til trinn 2 - batchstørrelse 4 passet inn i minnet, noe som i stor grad akselererte prosessen med tilleggstrening.
Ved trinn 4 ble prosessen stoppet pga selv langvarig tilleggstrening løftet ikke mAp75 til gamle verdier.
Som et resultat klarte vi å fremskynde slutningen med 15%, reduser størrelsen med 35% og ikke tape akkurat.
Automatisering for enklere arkitekturer
For enklere nettverksarkitekturer (uten betinget add, concaternate og residual blocks), er det fullt mulig å fokusere på å behandle alle konvolusjonslag og automatisere prosessen med å kutte ut konvolusjoner.
Jeg implementerte dette alternativet her.
Det er enkelt: du trenger bare en tapsfunksjon, en optimizer og 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 nødvendig kan du endre konfigurasjonsparametrene:
{
"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
}
I tillegg implementeres en begrensning basert på standardavviket. Målet er å begrense delen som fjernes, unntatt konvolusjoner med allerede "tilstrekkelige" L1-mål:
Dermed lar vi deg fjerne bare svake konvolusjoner fra distribusjoner som ligner på den høyre og ikke påvirke fjerningen fra distribusjoner som ligner på den venstre:
Når fordelingen nærmer seg normal, kan pruning_standart_deviation_part koeffisienten velges fra:
Jeg anbefaler en antagelse om 2 sigma. Eller du kan ignorere denne funksjonen og la verdien være < 1.0.
Utdataene er en graf over nettverksstørrelse, tap og nettverkskjøringstid for hele testen, normalisert til 1.0. For eksempel, her ble nettverksstørrelsen redusert med nesten 2 ganger uten tap av kvalitet (lite konvolusjonsnettverk med 100k vekter):
Kjørehastigheten er underlagt normale svingninger og forblir praktisk talt uendret. Det er en forklaring på dette:
Antall viklinger endres fra praktisk (32, 64, 128) til ikke det mest praktiske for skjermkort - 27, 51, etc. Jeg kan ta feil her, men mest sannsynlig har det en effekt.
Arkitekturen er ikke bred, men konsekvent. Ved å redusere bredden påvirker vi ikke dybden. Dermed reduserer vi belastningen, men endrer ikke hastigheten.
Derfor ble forbedringen uttrykt i en reduksjon i CUDA-belastningen under kjøringen med 20-30 %, men ikke i en reduksjon i løpetiden
Resultater av
La oss reflektere. Vi vurderte 2 alternativer for beskjæring - for YOLOv3 (når du må jobbe med hendene) og for nettverk med enklere arkitekturer. Det kan sees at i begge tilfeller er det mulig å oppnå reduksjon og hastighet på nettverksstørrelse uten tap av nøyaktighet. Resultater:
Redusere størrelsen
Akselerasjonsløp
Reduserer CUDA-belastningen
Som et resultat, miljøvennlighet (Vi optimerer fremtidig bruk av dataressurser. Et sted er man fornøyd Greta Thunberg)
Vedlegg
Etter beskjæringstrinnet kan du legge til kvantisering (for eksempel med TensorRT)