Technika Jedi redukcji sieci splotowych - przycinanie
Przed tobą znowu zadanie wykrywania obiektów. Priorytetem jest szybkość działania przy akceptowalnej dokładności. Bierzesz architekturę YOLOv3 i dalej ją szkolisz. Dokładność (mAp75) jest większa niż 0.95. Ale tempo biegania jest nadal niskie. Gówno.
Dziś ominiemy kwantyzację. I pod nacięciem zajrzymy Przycinanie modelu — przycięcie zbędnych części sieci w celu przyspieszenia wnioskowania bez utraty dokładności. Jasne jest, gdzie, ile i jak ciąć. Zastanówmy się, jak to zrobić ręcznie i gdzie można to zautomatyzować. Na końcu znajduje się repozytorium na keras.
Wprowadzenie
W moim poprzednim miejscu pracy, Macroscop w Permie, nabyłem jednego nawyku - zawsze monitorować czas wykonania algorytmów. I zawsze sprawdzaj czas działania sieci poprzez filtr adekwatności. Zazwyczaj najnowocześniejsze rozwiązania w produkcji nie przechodzą przez ten filtr, co doprowadziło mnie do Pruningu.
Przycinanie to stary temat omawiany w Wykłady Stanforda w 2017 r. Główną ideą jest zmniejszenie rozmiaru wytrenowanej sieci bez utraty dokładności poprzez usunięcie różnych węzłów. Brzmi fajnie, ale rzadko słyszę o jego zastosowaniu. Prawdopodobnie nie ma wystarczającej liczby wdrożeń, nie ma artykułów w języku rosyjskim lub po prostu wszyscy uważają to za przycinanie know-how i milczą.
Ale rozbierzmy to
Rzut oka na biologię
Uwielbiam, gdy Deep Learning analizuje pomysły wywodzące się z biologii. Im, podobnie jak ewolucji, można ufać (czy wiesz, że ReLU jest bardzo podobne do Funkcja aktywacji neuronów w mózgu?)
Proces przycinania modelu jest również bliski biologii. Reakcję sieci można tu porównać do plastyczności mózgu. W książce znajduje się kilka ciekawych przykładów. Normana Doidge’a:
Mózg kobiety, która urodziła się z tylko jedną połówką, przeprogramował się, aby pełnić funkcje brakującej połowy.
Facet odstrzelił część mózgu odpowiedzialną za widzenie. Z biegiem czasu inne części mózgu przejęły te funkcje. (nie staramy się powtarzać)
Podobnie możesz wyciąć niektóre słabe sploty ze swojego modelu. W ostateczności pozostałe wiązki pomogą zastąpić wycięte.
Czy lubisz Transfer Learning, czy uczysz się od zera?
Opcja numer jeden. Korzystasz z Transfer Learning na Yolov3. Retina, Mask-RCNN lub U-Net. Ale przez większość czasu nie musimy rozpoznawać 80 klas obiektów, jak w COCO. W mojej praktyce wszystko ogranicza się do klas 1-2. Można założyć, że architektura dla 80 klas jest tu zbędna. Sugeruje to, że należy zmniejszyć architekturę. Co więcej, chciałbym to zrobić bez utraty dotychczasowych, wytrenowanych ciężarów.
Opcja numer dwa. Być może masz dużo danych i zasobów obliczeniowych lub po prostu potrzebujesz niestandardowej architektury. Nie ma znaczenia. Ale uczysz się sieci od zera. Typowa procedura polega na przyjrzeniu się strukturze danych, wybraniu architektury o NADMIERNEJ mocy i wypchnięciu osób, które porzuciły szkolenie. Karl, widziałem 0.6 osób, które porzuciły naukę.
W obu przypadkach sieć można zmniejszyć. Zmotywowany. Teraz zastanówmy się, jaki rodzaj przycinania polega na obrzezaniu
Algorytm ogólny
Zdecydowaliśmy, że możemy usunąć wiązki. Wygląda to całkiem prosto:
Usunięcie jakiegokolwiek splotu jest stresujące dla sieci, co zwykle prowadzi do pewnego wzrostu błędów. Z jednej strony ten wzrost błędu jest wskaźnikiem tego, jak poprawnie usuwamy sploty (np. duży wzrost wskazuje, że robimy coś źle). Ale niewielki wzrost jest całkiem akceptowalny i często jest eliminowany przez późniejsze lekkie dodatkowe szkolenie z małym LR. Dodaj dodatkowy etap szkoleniowy:
Teraz musimy dowiedzieć się, kiedy chcemy zatrzymać pętlę Learning<->Pruning. Mogą tu pojawić się egzotyczne opcje, gdy musimy zmniejszyć sieć do określonego rozmiaru i prędkości (na przykład dla urządzeń mobilnych). Jednak najczęstszą opcją jest kontynuowanie cyklu, aż błąd stanie się większy niż akceptowalny. Dodaj warunek:
Zatem algorytm staje się jasny. Pozostaje dowiedzieć się, jak określić usunięte zwoje.
Wyszukaj usunięte pakiety
Musimy usunąć niektóre sploty. Pędzenie przed siebie i „strzelanie” do kogokolwiek to zły pomysł, chociaż zadziała. Ale ponieważ masz głowę, możesz pomyśleć i spróbować wybrać „słabe” zwoje do usunięcia. Istnieje kilka opcji:
Każda z opcji ma prawo do życia i własne cechy wykonawcze. Tutaj rozważamy opcję z najmniejszą miarą L1
Ręczny proces dla YOLOv3
Oryginalna architektura zawiera pozostałości bloków. Ale bez względu na to, jak fajne są dla głębokich sieci, będą nam nieco przeszkadzać. Trudność polega na tym, że nie można usunąć uzgodnień z różnymi indeksami w tych warstwach:
Wybierzmy zatem warstwy, z których będziemy mogli dowolnie usuwać uzgodnienia:
Teraz zbudujmy cykl pracy:
Przesyłanie aktywacji
Zastanawiam się, ile obciąć
Wytnij to
Uczenie się 10 epok z LR=1e-4
Testowanie
Rozładowywanie splotów jest przydatne do oszacowania, ile części możemy usunąć w danym kroku. Przykłady rozładunku:
Widzimy, że prawie wszędzie 5% splotów ma bardzo niską normę L1 i możemy je usunąć. Na każdym etapie rozładunek powtarzano i oceniano, które warstwy i ile można wyciąć.
Cały proces składał się z 4 kroków (liczby tutaj i wszędzie dla RTX 2060 Super):
Krok
mapa75
Liczba parametrów, miliony
Rozmiar sieci, mb
Od początku,%
Czas pracy, pani
Stan obrzezania
0
0.9656
60
241
100
180
-
1
0.9622
55
218
91
175
5% wszystkich
2
0.9625
50
197
83
168
5% wszystkich
3
0.9633
39
155
64
155
15% dla warstw z ponad 400 zwojami
4
0.9555
31
124
51
146
10% dla warstw z ponad 100 zwojami
Do kroku 2 dodano jeszcze jeden pozytywny efekt – w pamięci wpasowała się porcja o rozmiarze 4, co znacznie przyspieszyło proces dodatkowego szkolenia.
W kroku 4 proces został zatrzymany, ponieważ nawet długotrwałe dodatkowe szkolenie nie podniosło mAp75 do starych wartości.
W rezultacie udało nam się przyspieszyć wnioskowanie o 15%, zmniejsz rozmiar o 35% i nie stracić dokładnie.
Automatyzacja dla prostszych architektur
W przypadku prostszych architektur sieci (bez dodawania warunkowego, łączenia i bloków resztkowych) całkiem możliwe jest skupienie się na przetwarzaniu wszystkich warstw splotowych i zautomatyzowanie procesu wycinania splotów.
Zaimplementowałem tę opcję tutaj.
To proste: potrzebujesz tylko funkcji straty, optymalizatora i generatorów wsadowych:
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)
W razie potrzeby możesz zmienić parametry konfiguracyjne:
{
"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
}
Dodatkowo zaimplementowano ograniczenie oparte na odchyleniu standardowym. Celem jest ograniczenie usuwanej części, z wyłączeniem splotów z już „wystarczającymi” miarami L1:
Tym samym pozwalamy na usunięcie tylko słabych splotów z rozkładów podobnych do prawego i nie wpływamy na usunięcie z rozkładów podobnych do lewego:
Gdy rozkład zbliża się do normalnego, współczynnik przycinania_standart_deviation_part można wybrać spośród:
Zalecam założenie 2 sigma. Możesz też zignorować tę funkcję, pozostawiając wartość < 1.0.
Wynikiem jest wykres rozmiaru sieci, strat i czasu działania sieci dla całego testu, znormalizowany do 1.0. Na przykład tutaj rozmiar sieci został zmniejszony prawie 2 razy bez utraty jakości (mała sieć splotowa o wagach 100 tys.):
Prędkość obrotowa podlega normalnym wahaniom i pozostaje praktycznie niezmieniona. Jest na to wyjaśnienie:
Liczba zwojów zmienia się z wygodnej (32, 64, 128) na niezbyt wygodną dla kart graficznych - 27, 51 itd. Mogę się tu mylić, ale najprawdopodobniej ma to wpływ.
Architektura nie jest szeroka, ale spójna. Zmniejszając szerokość nie wpływamy na głębokość. W ten sposób zmniejszamy obciążenie, ale nie zmieniamy prędkości.
Dlatego poprawa wyrażała się w zmniejszeniu obciążenia CUDA podczas biegu o 20-30%, ale nie w skróceniu czasu działania
Wyniki
Zastanówmy się. Rozważaliśmy 2 opcje przycinania - dla YOLOv3 (kiedy musisz pracować rękami) i dla sieci o prostszej architekturze. Można zauważyć, że w obu przypadkach możliwe jest zmniejszenie rozmiaru sieci i przyspieszenie jej bez utraty dokładności. Wyniki:
Zmniejszenie rozmiaru
Bieg przyspieszający
Zmniejszanie obciążenia CUDA
W rezultacie przyjazność dla środowiska (Optymalizujemy przyszłe wykorzystanie zasobów obliczeniowych. Gdzieś jest się szczęśliwym Greta Thunberg)
dodatek
Po etapie przycinania możesz dodać kwantyzację (na przykład za pomocą TensorRT)