Technika Jedi pro redukci konvolučních sítí – prořezávání
Před vámi je opět úkol detekce objektů. Priorita - rychlost práce s přijatelnou přesností. Vezmete architekturu YOLOv3 a trénujete ji dále. Přesnost (mAp75) je větší než 0.95. Ale rychlost běhu je stále nízká. Blbost.
Dnes kvantování obejdeme. A pod řezem, zvažte Modelové prořezávání - odříznutí nadbytečných částí sítě pro urychlení Inference bez ztráty přesnosti. Jasně – kde, kolik a jak řezat. Pojďme zjistit, jak to udělat ručně a kde můžete automatizovat. Na konci je úložiště na keras.
úvod
Na svém posledním působišti, Macroscop v Permu, jsem si osvojil jeden zvyk – vždy sledovat dobu provádění algoritmů. A doba provozu sítě by měla být vždy kontrolována pomocí filtru přiměřenosti. Obvykle nejmodernější ve výrobě tímto filtrem neprojde, což mě přivedlo k Pruningu.
Prořezávání je staré téma, o kterém se diskutovalo v Stanfordské přednášky v roce 2017. Hlavní myšlenkou je zmenšit velikost trénované sítě bez ztráty přesnosti odstraněním různých uzlů. Zní to dobře, ale málokdy slyším o jeho použití. Pravděpodobně není dostatek implementací, neexistují žádné články v ruštině, nebo prostě každý zvažuje know-how o prořezávání a mlčí.
Ale rozebrat
Pohled do biologie
Líbí se mi, když myšlenky pocházející z biologie nahlédnou do Deep Learning. Stejně jako evoluci jim lze věřit (věděli jste, že ReLU je velmi podobný aktivační funkce neuronů v mozku?)
Proces Model Pruning má také blízko k biologii. Reakci sítě zde lze přirovnat k plasticitě mozku. V knize je pár zajímavých příkladů. Norman Doidge:
Mozek ženy, která se narodila pouze s jednou polovinou, se přeprogramoval tak, aby vykonával funkce chybějící poloviny
Ten chlap mu ustřelil část mozku zodpovědnou za vidění. Postupem času tyto funkce převzaly jiné části mozku. (nesnažte se opakovat)
Takže z vašeho modelu můžete vystřihnout některé slabé konvoluce. V extrémních případech zbývající svazky pomohou nahradit řezané.
Milujete Transfer Learning nebo se učíte od nuly?
Možnost číslo jedna. Používáte Transfer Learning na Yolov3. Retina, Mask-RCNN nebo U-Net. Ale častěji než ne, nepotřebujeme rozpoznávat 80 tříd objektů jako v COCO. V mé praxi je vše omezeno na 1-2 lekce. Dá se předpokládat, že architektura pro 80 tříd je zde nadbytečná. Vzniká myšlenka, že architekturu je třeba zredukovat. Navíc bych to chtěl udělat, aniž bych ztratil stávající předtrénované váhy.
Možnost číslo dvě. Možná máte spoustu dat a výpočetních zdrojů, nebo jen potřebujete super zakázkovou architekturu. Na tom nezáleží. Ale vy se učíte síť od nuly. Obvyklé pořadí – podíváme se na datovou strukturu, vybereme NADMĚRNOU architekturu z hlediska výkonu a vytlačíme výpadky z rekvalifikací. Viděl jsem 0.6 výpadků, Carle.
V obou případech lze síť zredukovat. Povýšeno. Nyní pojďme zjistit, jaký druh obřízky prořezávání
Obecný algoritmus
Rozhodli jsme se, že můžeme odstranit konvoluce. Vypadá to velmi jednoduše:
Odstranění jakékoli konvoluce je pro síť stresující, což obvykle vede k určitému nárůstu chyb. Na jedné straně je tento nárůst chyb indikátorem toho, jak správně odstraňujeme konvoluce (např. velký nárůst naznačuje, že něco děláme špatně). Ale malé navýšení je celkem přijatelné a často se eliminuje následným lehkým přeškolením s malým LR. Přidání kroku rekvalifikace:
Nyní musíme zjistit, kdy chceme zastavit naši smyčku učení<->prořezávání. Zde mohou být exotické možnosti, když potřebujeme zmenšit síť na určitou velikost a rychlost běhu (například pro mobilní zařízení). Nejběžnější možností je však pokračovat ve smyčce, dokud nebude chyba vyšší, než je přijatelné. Přidání podmínky:
Algoritmus je tedy jasný. Zbývá vymyslet, jak určit konvoluce, které mají být odstraněny.
Hledání odstraněných svazků
Musíme odstranit některé konvoluce. Uhánět vpřed a „střílet“ na jakékoli je špatný nápad, i když to bude fungovat. Ale protože existuje hlava, můžete přemýšlet a pokusit se vybrat „slabé“ konvoluce pro smazání. Existuje několik možností:
Každá z možností má právo na život a jeho implementační prvky. Zde uvažujeme variantu s nejmenší mírou L1
Manuální proces pro YOLOv3
Původní architektura obsahuje zbytkové bloky. Ale bez ohledu na to, jak cool jsou pro hluboké sítě, trochu nám překážejí. Potíž je v tom, že v těchto vrstvách nemůžete odstranit odsouhlasení s různými indexy:
Proto vybíráme vrstvy, ze kterých můžeme libovolně odebírat odsouhlasení:
Nyní vytvoříme pracovní cyklus:
Nahrávání aktivací
Zjistit, kolik řezat
Vystřihněte
Učení 10 epoch s LR=1e-4
Testování
Uvolnění souhrnů je užitečné k vyhodnocení toho, kolik můžeme v konkrétním kroku odstranit. Příklady nahrání:
Vidíme, že téměř všude má 5 % konvolucí velmi nízkou normu L1 a můžeme je odstranit. Na každém kroku se takové vykládání opakovalo a vyhodnocovalo se, které vrstvy a kolik lze řezat.
Celý proces se vešel do 4 kroků (zde a všude čísla pro RTX 2060 Super):
Krok
mapa75
Počet parametrů, milion
Velikost sítě, mb
Z originálu, %
Doba běhu, slečno
Mezní stav
0
0.9656
60
241
100
180
-
1
0.9622
55
218
91
175
5 % ze všech
2
0.9625
50
197
83
168
5 % ze všech
3
0.9633
39
155
64
155
15 % pro vrstvy s více než 400 svazky
4
0.9555
31
124
51
146
10 % pro vrstvy s více než 100 svazky
Ke kroku 2 byl přidán jeden pozitivní efekt - dávka velikosti 4 se dostala do paměti, což značně urychlilo proces dodatečného tréninku.
Ve 4. kroku byl proces zastaven, protože ani prodloužené dodatečné školení nezvedlo mAp75 na staré hodnoty.
V důsledku toho bylo možné urychlit inferenci na 15%, zmenšit velikost o 35% a neztratit přesnost.
Automatizace pro jednodušší architektury
Pro jednodušší síťové architektury (bez podmíněného přidávání, zřetězení a zbytkových bloků) je docela možné zaměřit se na zpracování všech konvolučních vrstev a automatizovat proces řezání konvolucí.
Tuto možnost jsem implementoval zde.
Je to jednoduché: máte pouze ztrátovou funkci, optimalizátor a dávkové generátory:
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)
V případě potřeby můžete změnit konfigurační parametry:
{
"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
}
Navíc je implementováno omezení založené na směrodatné odchylce. Cílem je omezit část odstraněných, s vyloučením konvolucí s již „dostatečnými“ mírami L1:
Umožňujeme tedy odstranit pouze slabé konvoluce z pravoúhlých distribucí a neovlivnit odstranění z levých distribucí:
Když se distribuce blíží normálu, koeficient pruning_standart_deviation_part lze vybrat z:
Doporučuji předpoklad 2 sigma. Nebo můžete tuto funkci ignorovat a ponechat hodnotu < 1.0.
Výstupem je graf velikosti sítě, ztráty a doby běhu sítě během celého testu, normalizovaný na 1.0. Například zde se velikost sítě zmenšila téměř 2krát bez ztráty kvality (malá konvoluční síť pro 100k závaží):
Rychlost běhu podléhá běžným výkyvům a příliš se nezměnila. Existuje pro to vysvětlení:
Počet závitů se mění z vhodných (32, 64, 128) na ne nejvhodnější pro grafické karty - 27, 51 atd. Tady se mohu mýlit, ale pravděpodobně ano.
Architektura není široká, ale konzistentní. Zmenšením šířky se nedotýkáme hloubky. Tím snížíme zátěž, ale neměníme rychlost.
Zlepšení se tedy projevilo snížením zátěže CUDA během běhu o 20-30%, ale ne snížením doby běhu
Výsledky
Pojďme se zamyslet. Zvažovali jsme 2 možnosti prořezávání – pro YOLOv3 (když musíte pracovat rukama) a pro sítě s jednodušší architekturou. Je vidět, že v obou případech je možné dosáhnout zmenšení velikosti sítě a zrychlení bez ztráty přesnosti. Výsledek:
Zmenšení velikosti
Zrychlení běhu
Snížení zatížení CUDA
Výsledkem je šetrnost k životnímu prostředí (Optimalizujeme budoucí využití výpočetních zdrojů. Někde se člověk raduje Greta Thunbergová)
Příloha
Po kroku prořezávání můžete také vyladit kvantizaci (například pomocí TensorRT)