
Boli tylko za pierwszym razem!
Cześć wszystkim! Drodzy przyjaciele, w tym artykule chcę podzielić się swoim doświadczeniem z wykorzystaniem TensorRT oraz RetinaNet bazując na repozytorium (to jest fork oficjalnego repozytorium od , które umożliwi szybkie wprowadzenie zoptymalizowanych modeli do produkcji). Przeglądając wiadomości w kanałach społeczności , natrafiam na pytania dotyczące wykorzystania TensorRT, a pytania te często się powtarzają, dlatego postanowiłem napisać jak najbardziej wyczerpujące poradnik dotyczący szybkiego wnioskowania opartego na TensorRT, RetinaNet, Unet i dockerze.
Opis zadania
Proponuję postawić zadanie w ten sposób: musimy oznaczyć zbiór danych, wytrenować na nim sieć RetinaNet/Unet w Pytorch1.3+, przekształcić uzyskane wagi do ONNX, następnie skonwertować je do silnika TensorRT i uruchomić wszystko w dockerze, najlepiej na Ubuntu 18 i zdecydowanie na architekturze ARM (Jetson)*, minimalizując ręczne wdrażanie środowiska. W rezultacie uzyskamy kontener gotowy nie tylko do eksportu i szkolenia RetinaNet/Unet, ale również do kompleksowego rozwoju i szkolenia klasyfikacji oraz segmentacji z całym niezbędnym wsparciem.
Etap 1. Przygotowanie środowiska
Warto zauważyć, że w ostatnim czasie całkowicie zrezygnowałem z używania i wdrażania jakichkolwiek bibliotek na komputerze stacjonarnym, tak samo jak na devboxie. Jedynym, co trzeba stworzyć i zainstalować, jest środowisko wirtualne Pythona i cuda 10.2 (można ograniczyć się do samego sterownika nvidia) z deb.
Załóżmy, że masz świeżo zainstalowane Ubuntu 18. Zainstalujemy cuda 10.2 (deb), szczegółowo na procesie instalacji nie będę się zatrzymywał, oficjalna dokumentacja jest wystarczająca.
Teraz zainstalujemy docker, przewodnik po instalacji dockera można łatwo znaleźć, oto przykład , dostępna jest już wersja 19+ — instalujemy ją. Nie zapomnij również umożliwić korzystania z dockera bez sudo, będzie to wygodniejsze. Po tym, jak wszystko się udało, robimy tak:
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo systemctl restart docker
I można nawet nie zaglądać do oficjalnego repozytorium .
Teraz robimy git clone .
Zostało tylko kilka kroków, aby zacząć korzystać z dockera z obrazem nvidia. Musimy zarejestrować się w NGC Cloud i zalogować. Idźmy tutaj , rejestrujemy się, a po wejściu do NGC Cloud klikamy SETUP w lewym górnym rogu ekranu lub przechodzimy pod ten link . Klikamy „wygenerować klucz”. Polecam go zapisać, w przeciwnym razie przy kolejnym wejściu trzeba będzie go wygenerować ponownie, a przy uruchamianiu na nowym sprzęcie powtórzyć tę operację.
Wykonaj:
docker login nvcr.io
Username: $oauthtoken
Password: - wygenerowany klucz
Username po prostu kopiujemy. No, oto, środowisko zostało uruchomione!
Etap 2. Budowa kontenera docker
Na drugim etapie naszej pracy zbudujemy docker i zaznajomimy się z jego wnętrzem.
Przejdziemy do głównego katalogu projektu retina-examples i wykonamy
docker build --build-arg USER=$USER --build-arg UID=$UID --build-arg GID=$GID --build-arg PW=alex -t retinanet:latest retinanet/
Budujemy docker przekazując bieżącego użytkownika – to bardzo przydatne, jeśli planujesz coś pisać na zamontowanym VOLUME z prawami obecnego użytkownika, w przeciwnym razie będzie to root i będzie to problem.
Podczas gdy docker się buduje, przyjrzyjmy się plikowi Dockerfile:
FROM nvcr.io/nvidia/pytorch:19.10-py3
ARG USER=alex
ARG UID=1000
ARG GID=1000
ARG PW=alex
RUN useradd -m ${USER} --uid=${UID} && echo "${USER}:${PW}" | chpasswd
RUN apt-get -y update && apt-get -y upgrade && apt-get -y install curl && apt-get -y install wget && apt-get -y install git && apt-get -y install automake && apt-get install -y sudo && adduser ${USER} sudo
RUN pip install git+https://github.com/bonlime/pytorch-tools.git@master
COPY . retinanet/
RUN pip install --no-cache-dir -e retinanet/
RUN pip install /workspace/retinanet/extras/tensorrt-6.0.1.5-cp36-none-linux_x86_64.whl
RUN pip install tensorboardx
RUN pip install albumentations
RUN pip install setproctitle
RUN pip install paramiko
RUN pip install flask
RUN pip install mem_top
RUN pip install arrow
RUN pip install pycuda
RUN pip install torchvision
RUN pip install pretrainedmodels
RUN pip install efficientnet-pytorch
RUN pip install git+https://github.com/qubvel/segmentation_models.pytorch
RUN pip install pytorch_toolbelt
RUN chown -R ${USER}:${USER} retinanet/
RUN cd /workspace/retinanet/extras/cppapi && mkdir build && cd build && cmake -DCMAKE_CUDA_FLAGS="--expt-extended-lambda -std=c++14" .. && make && cd /workspace
RUN apt-get install -y openssh-server && apt install -y tmux && apt-get -y install bison flex && apt-cache search pcre && apt-get -y install net-tools && apt-get -y install nmap
RUN apt-get -y install libpcre3 libpcre3-dev && apt-get -y install iputils-ping
RUN mkdir /var/run/sshd
RUN echo 'root:pass' | chpasswd
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" >> /etc/profile
CMD ["/usr/sbin/sshd", "-D"]
Jak widać z tekstu, bierzemy wszystkie nasze ulubione biblioteki, kompilujemy retinanet, dodajemy podstawowe narzędzia do wygodnej pracy z Ubuntu i konfigurujemy serwer openssh. Pierwszą linią jest dziedziczenie obrazu nvidia, do którego logowaliśmy się w NGC Cloud i który zawiera Pytorch1.3, TensorRT6.x.x.x oraz wiele bibliotek, które pozwalają na skompilowanie cpp źródeł naszego detektora.
Etap 3. Uruchamianie i debugowanie kontenera docker
Przejdźmy do głównego przypadku użycia kontenera i środowiska programistycznego, na początek uruchomimy nvidia docker. Wykonujemy:
docker run --gpus all --net=host -v /home/:/workspace/mounted_vol -d -P --rm --ipc=host -it retinanet:latestTeraz kontener jest dostępny przez ssh @localhost. Po pomyślnym uruchomieniu, otwieramy projekt w PyCharm. Następnie otwieramy
Ustawienia->Interpreter projektu->Dodaj->Interpreter Ssh Krok 1

Krok 2

Krok 3

Wybieramy wszystko jak na zrzutach ekranu,
Interpreter -> /opt/conda/bin/python— to będzie odniesienie do Python3.6 i
Folder synchronizacji -> /workspace/retinanetKlikamy zakończ, czekamy na indeksowanie, i wszystko, środowisko jest gotowe do użycia!
WAŻNE!!! Tuż po indeksowaniu należy pobrać z docker skompilowane pliki dla Retinanet. W menu kontekstowym w głównym folderze projektu wybieramy opcję
Zarządzanie->PobierzPojawią się jeden plik i dwa foldery build, retinanet.egg-info oraz _C.so

Jeśli twój projekt wygląda tak, to środowisko widzi wszystkie niezbędne pliki i jesteśmy gotowi do szkolenia RetinaNet.
Etap 4. Oznaczamy dane i trenujemy detektor
Do oznaczania zazwyczaj używam — przyjemne i wygodne narzędzie, w ostatnim czasie poprawiono wiele błędów i działa znacznie lepiej.
Załóżmy, że oznaczyłeś zbiór danych i go pobrałeś, ale nie można go od razu włożyć do naszego RetinaNet, ponieważ jest w własnym formacie i musimy go skonwertować do COCO. Narzędzie do konwersji znajduje się w:
markup_utils/supervisly_to_coco.pyZwróć uwagę, że Kategoria w skrypcie to przykład i musisz wstawić swoje (kategorii background nie dodawaj)
categories = [{'id': 1, 'name': '1'},
{'id': 2, 'name': '2'},
{'id': 3, 'name': '3'},
{'id': 4, 'name': '4'}] Autorzy oryginalnego repozytorium postanowili, że nic poza COCO/VOC nie będziesz trenować do detekcji, więc musiałem nieco edytować oryginalny plik
retinanet/dataset.pyDodając tutaj ulubione augmentacje i usunąć sztywno wbudowane kategorie z COCO. Istnieje także możliwość przycięcia dużych obszarów detekcji, jeśli na dużych obrazkach szukasz małych obiektów, masz mały zbiór danych =), i nic nie działa, ale o tym innym razem.
Ogólnie pętla treningowa też jest słaba, początkowo nie zapisywała punktów kontrolnych, używała jakiegoś strasznego harmonogramu itd. Ale teraz wystarczy, że wybierzesz backbone i wykonasz
/opt/conda/bin/python retinanet/main.pyz parametrami:
train retinanet_rn34fpn.pth
--backbone ResNet34FPN
--classes 12
--val-iters 10
--images /workspace/mounted_vol/dataset/train/images
--annotations /workspace/mounted_vol/dataset/train_12_class.json
--val-images /workspace/mounted_vol/dataset/test/images_small
--val-annotations /workspace/mounted_vol/dataset/val_10_class_cropped.json
--jitter 256 512
--max-size 512
--batch 32
W konsoli zobaczysz:
Inicjowanie modelu...
model: RetinaNet
backbone: ResNet18FPN
klasy: 2, kotwice: 9
Wybrany poziom optymalizacji O0: Czyste szkolenie FP32.
Domyślne ustawienia dla tego poziomu optymalizacji to:
enabled : True
opt_level : O0
cast_model_type : torch.float32
patch_torch_functions : False
keep_batchnorm_fp32 : None
master_weights : False
loss_scale : 1.0
Przetwarzanie nadpisania użytkownika (dodatkowe kwargs, które nie są None)...
Po przetworzeniu nadpisania, opcje optymalizacji to:
enabled : True
opt_level : O0
cast_model_type : torch.float32
patch_torch_functions : False
keep_batchnorm_fp32 : None
master_weights : False
loss_scale : 128.0
Przygotowywanie zbioru danych...
loader: pytorch
zmiana rozmiaru: [1024, 1280], max: 1280
urządzenie: 4 GPU
partia: 4, precyzja: mieszana
Trenowanie modelu przez 20000 iteracji...
[ 1/20000] strata ogniskowa: 0.95619, strata ramki: 0.51584, 4.042s/4-partii (fw: 0.698s, bw: 0.459s), 1.0 im/s, lr: 0.0001
[ 12/20000] strata ogniskowa: 0.76191, strata ramki: 0.31794, 0.187s/4-partii (fw: 0.055s, bw: 0.133s), 21.4 im/s, lr: 0.0001
[ 24/20000] strata ogniskowa: 0.65036, strata ramki: 0.30269, 0.173s/4-partii (fw: 0.045s, bw: 0.128s), 23.1 im/s, lr: 0.0001
[ 36/20000] strata ogniskowa: 0.46425, strata ramki: 0.23141, 0.178s/4-partii (fw: 0.047s, bw: 0.131s), 22.4 im/s, lr: 0.0001
[ 48/20000] strata ogniskowa: 0.45115, strata ramki: 0.23505, 0.180s/4-partii (fw: 0.047s, bw: 0.133s), 22.2 im/s, lr: 0.0001
[ 59/20000] strata ogniskowa: 0.38958, strata ramki: 0.25373, 0.184s/4-partii (fw: 0.049s, bw: 0.134s), 21.8 im/s, lr: 0.0001
[ 71/20000] strata ogniskowa: 0.37733, strata ramki: 0.23988, 0.174s/4-partii (fw: 0.049s, bw: 0.125s), 22.9 im/s, lr: 0.0001
[ 83/20000] strata ogniskowa: 0.39514, strata ramki: 0.23878, 0.181s/4-partii (fw: 0.048s, bw: 0.133s), 22.1 im/s, lr: 0.0001
[ 94/20000] strata ogniskowa: 0.39947, strata ramki: 0.23817, 0.185s/4-partii (fw: 0.050s, bw: 0.134s), 21.6 im/s, lr: 0.0001
[ 105/20000] strata ogniskowa: 0.37343, strata ramki: 0.20238, 0.182s/4-partii (fw: 0.048s, bw: 0.134s), 22.0 im/s, lr: 0.0001
[ 116/20000] strata ogniskowa: 0.19689, strata ramki: 0.17371, 0.183s/4-partii (fw: 0.050s, bw: 0.132s), 21.8 im/s, lr: 0.0001
[ 128/20000] strata ogniskowa: 0.20368, strata ramki: 0.16538, 0.178s/4-partii (fw: 0.046s, bw: 0.131s), 22.5 im/s, lr: 0.0001
[ 140/20000] strata ogniskowa: 0.22763, strata ramki: 0.15772, 0.176s/4-partii (fw: 0.050s, bw: 0.126s), 22.7 im/s, lr: 0.0001
[ 148/20000] strata ogniskowa: 0.21997, strata ramki: 0.18400, 0.585s/4-partii (fw: 0.047s, bw: 0.144s), 6.8 im/s, lr: 0.0001
Średnia precyzja (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.52674
Średnia precyzja (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.91450
Średnia precyzja (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.35172
Średnia precyzja (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.61881
Średnia precyzja (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.00000
Średnia precyzja (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.00000
Średnia przypomnienie (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.58824
Średnia przypomnienie (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.61765
Średnia przypomnienie (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.61765
Średnia przypomnienie (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.61765
Średnia przypomnienie (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.00000
Średnia przypomnienie (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.00000
Zapisywanie modelu: 148Aby zapoznać się z pełnym zestawem parametrów, sprawdź
retinanet/main.pyOgólnie mówiąc, są standardowe dla detekcji i zawierają opisy. Uruchom trening i poczekaj na wyniki. Przykład inferencji można zobaczyć w:
retinanet/infer_example.pylub wykonaj polecenie:
/opt/conda/bin/python retinanet/main.py infer retinanet_rn34fpn.pth
--images /workspace/mounted_vol/dataset/test/images
--annotations /workspace/mounted_vol/dataset/val.json
--output result.json
--resize 256
--max-size 512
--batch 32
W repozytorium zintegrowano już Focal Loss oraz kilka backbone'ów, a także łatwo można dodać własne.
retinanet/backbones/*.pyW tabeli autorzy przedstawiają niektóre charakterystyki:

Jest także backbone ResNeXt50_32x4dFPN oraz ResNeXt101_32x8dFPN, pobrany z torchvision.
Mam nadzieję, że udało się trochę zrozumieć detekcję, ale warto koniecznie przeczytać oficjalną dokumentację, aby zrozumieć tryby eksportu i logowania..
Krok 5. Eksport i inferencja modeli Unet z enkoderem Resnet.
Jak zapewne zauważyliście, w Dockerfile zainstalowano biblioteki do segmentacji, w tym znakomitą bibliotekę . W pakiecie YUNET można znaleźć przykłady inferencji i eksportu punktów kontrolnych pytorch do silnika TensorRT.
Głównym problemem przy eksporcie modeli podobnych do Unet z ONNX do TensorRT jest konieczność podania stałego rozmiaru Upsample lub używania ConvTranspose2D:
import torch.onnx.symbolic_opset9 as onnx_symbolic
def upsample_nearest2d(g, input, output_size):
# Obecnie, parser ONNX TRT 5.1/6.0 nie obsługuje wszystkich operacji ONNX
# potrzebnych do obsługi dynamicznego upscale, ONNX forumlation
# Tutaj w hardcodujemy skalę=2 jako tymczasowe obejście
scales = g.op("Constant", value_t=torch.tensor([1., 1., 2., 2.]))
return g.op("Upsample", input, scales, mode_s="nearest")
onnx_symbolic.upsample_nearest2d = upsample_nearest2d
Dzięki temu przekształceniu można zrobić to automatycznie podczas eksportu do ONNX, ale w 7 wersji TensorRT ten problem został rozwiązany, a my musimy czekać już bardzo niewiele.
Podsumowanie
Kiedy zacząłem korzystać z dockera, miałem wątpliwości co do jego wydajności dla moich zadań. W jednej z moich strukturyzacji generuje się obecnie dość duży ruch sieciowy, tworzony przez kilka kamer.

Różne testy w internecie mówiły o stosunkowo dużym narzucie na interakcji sieciowej i zapisie na VOLUME, a także o nieznanym i strasznym GIL, a ponieważ uchwycenie klatki, działanie sterownika i przesyłanie klatki przez sieć są operacjami atomowymi w trybie hard real-time, opóźnienia w sieci są dla mnie bardzo krytyczne.
Ale wszystko się udało =)
P.S. Pozostaje dodać ulubioną pętlę treningową dla segmentacji i do produkcji!
Podziękowania
Dziękuję społeczności , bez niej rozwój jest niemożliwy! Ogromne dzięki , który namówił mnie do zajęcia się DL, za jego cenne porady i niezwykły profesjonalizm!
Używaj w produkcji zoptymalizowanych modeli!
Aurorai, llc
Źródło: habr.com
