
Fa male solo la prima volta!
Ciao a tutti! Cari amici, in questo articolo voglio condividere la mia esperienza con TensorRT, RetinaNet basato sul repository (questo è un fork del repository ufficiale di , che permette di iniziare a utilizzare modelli ottimizzati in produzione nel minor tempo possibile). Scorrendo i messaggi nei canali della comunità , mi imbatto in domande sull'uso di TensorRT, e le domande sono principalmente ripetitive, quindi ho deciso di scrivere una guida il più completa possibile sull'uso dell'inferenza veloce basata su TensorRT, RetinaNet, Unet e docker.
Descrizione del compito
Propongo di impostare il compito in questo modo: dobbiamo etichettare il dataset, addestrare il rete RetinaNet/Unet su Pytorch1.3+, convertire i pesi ottenuti in ONNX, poi convertirli in engine TensorRT e mettere tutto in docker, preferibilmente su Ubuntu 18 e, se possibile, su architettura ARM(Jetson)*, riducendo al minimo il dispiegamento manuale dell'ambiente. Alla fine otterremo un contenitore pronto non solo per l'esportazione e l'addestramento di RetinaNet/Unet, ma anche per lo sviluppo e l'addestramento completo di classificazione, segmentazione con tutta la necessaria configurazione.
Fase 1. Preparazione dell'ambiente
È importante notare che ultimamente ho completamente abbandonato l'uso e il dispiegamento di qualsiasi libreria su una macchina desktop, così come su un devbox. L'unica cosa che devo creare e installare è un ambiente virtuale python e cuda 10.2 (è possibile limitarsi a un driver nvidia) da deb.
Supponiamo che tu abbia un Ubuntu 18 frescamente installato. Procediamo con l'installazione di cuda 10.2(deb), non mi soffermerò sul processo di installazione, la documentazione ufficiale è più che sufficiente.
Ora installiamo docker, una guida per l'installazione di docker può essere facilmente trovata, ecco un esempio , la versione 19+ è già disponibile - installiamola. E non dimenticare di rendere possibile l'uso di docker senza sudo, sarà più comodo. Dopo aver completato tutto, facciamo così:
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
E non è nemmeno necessario guardare il repository ufficiale .
Ora facciamo un git clone .
Manca poco ormai, per iniziare a usare docker con l'immagine nvidia, dobbiamo registrarci in NGC Cloud e accedere. Andiamo qui , ci registriamo e dopo essere entrati in NGC Cloud, clicchiamo su SETUP nell'angolo in alto a sinistra dello schermo o seguiamo questo link . Clicchiamo su "genera chiave". Ti consiglio di salvarla, altrimenti la prossima volta che visiterai dovrai generarne un'altra e, di conseguenza, ripetere questa operazione su una nuova macchina.
Eseguiamo:
docker login nvcr.io
Username: $oauthtoken
Password: - chiave generata
Username semplicemente lo copiamo. Ecco fatto, l'ambiente è stato dispiegato!
Fase 2. Costruzione del contenitore docker
Nella seconda fase del nostro lavoro costruiremo docker e ci familiarizzeremo con le sue internals.
Andiamo nella cartella radice rispetto al progetto retina-examples e eseguiamo
docker build --build-arg USER=$USER --build-arg UID=$UID --build-arg GID=$GID --build-arg PW=alex -t retinanet:latest retinanet/
Stiamo costruendo docker passando al suo interno l'utente attuale - è molto utile se scriverai qualcosa su un VOLUME montato con i diritti dell'utente attuale, altrimenti saremo root e sarà un problema.
Mentre docker si sta costruendo, diamo un'occhiata al 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"]
Come si può vedere dal testo, prendiamo tutte le nostre librerie preferite, compiliamo retinanet, aggiungiamo strumenti di base per facilitare il lavoro con Ubuntu e configuriamo il server openssh. La prima riga riguarda proprio l'ereditarietà dell'immagine NVIDIA, per la quale abbiamo effettuato il login in NGC Cloud e che contiene Pytorch1.3, TensorRT6.x.x.x e molte altre librerie che consentono di compilare i sorgenti cpp del nostro rilevatore.
Fase 3. Avvio e debug del container docker
Passiamo al principale caso d'uso del container e dell'ambiente di sviluppo, per iniziare avviamo nvidia docker. Eseguiamo:
docker run --gpus all --net=host -v /home/:/workspace/mounted_vol -d -P --rm --ipc=host -it retinanet:latestOra il container è accessibile tramite ssh @localhost. Dopo un avvio riuscito, apriamo il progetto in PyCharm. Poi apriamo
Impostazioni->Interprete del progetto->Aggiungi->Interprete Ssh Passo 1

Passo 2

Passo 3

Selezioniamo tutto come nelle schermate
Interprete -> /opt/conda/bin/python– questo sarà un link a Python3.6 e
Cartella di sincronizzazione -> /workspace/retinanetClicchiamo su fine, aspettiamo l'indicizzazione e il gioco è fatto, l'ambiente è pronto per l'uso!
IMPORTANTE!!! Subito dopo l'indicizzazione estrarre da docker i file compilati per Retinanet. Nel menu contestuale nella radice del progetto selezioniamo l'opzione
Distribuzione->ScaricaAppariranno un file e due cartelle build, retinanet.egg-info e _С.so

Se il tuo progetto appare così, l'ambiente riconosce tutti i file necessari ed siamo pronti per l'addestramento di RetinaNet.
Fase 4. Etichettare i dati e addestrare il rilevatore
Per l'etichettatura utilizzo principalmente – uno strumento piacevole e conveniente, negli ultimi tempi hanno corretto molti errori e ora si comporta notevolmente meglio.
Supponiamo che tu abbia etichettato il dataset e lo abbia scaricato, ma non puoi inserirlo direttamente nel nostro RetinaNet, poiché è in un formato proprietario e per questo dobbiamo convertirlo in COCO. Lo strumento per la conversione si trova in:
markup_utils/supervisly_to_coco.pySi prega di notare che la categoria nello script è un esempio e devi inserire le tue (non è necessario aggiungere la categoria background)
categories = [{'id': 1, 'name': '1'},
{'id': 2, 'name': '2'},
{'id': 3, 'name': '3'},
{'id': 4, 'name': '4'}] Gli autori del repository originale hanno deciso che non addestrerai nulla al di fuori di COCO/VOC per la rilevazione, quindi ho dovuto modificare leggermente il file sorgente
retinanet/dataset.pyAggiungendo qui le mie amiche a augumentazioni e rimuovendo le categorie fissate nel COCO. C'è anche la possibilità di ritagliare ampie aree di rilevazione, se stai cercando piccoli oggetti in grandi immagini, hai un dataset piccolo =), e niente funziona, ma di questo parleremo un'altra volta.
Insomma, il ciclo di addestramento è anch'esso piuttosto debole; inizialmente non salvava i checkpoint, utilizzava un scheduler orribile, ecc. Ma ora non ti resta che scegliere il backbone e procedere
/opt/conda/bin/python retinanet/main.pycon i parametri:
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
Nel console vedrai:
Inizializzazione del modello...
modello: RetinaNet
backbone: ResNet18FPN
classi: 2, ancore: 9
Ottimizzazione selezionata livello O0: Formazione FP32 pura.
I valori predefiniti per questo livello di ottimizzazione sono:
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
Elaborazione delle sovrascritture utente (ulteriori kwargs che non sono None)...
Dopo aver elaborato le sovrascritture, le opzioni di ottimizzazione sono:
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
Preparando il dataset...
caricatore: pytorch
ridimensiona: [1024, 1280], max: 1280
dispositivo: 4 gpu
batch: 4, precisione: mista
Addestramento del modello per 20000 iterazioni...
[ 1/20000] focal loss: 0.95619, box loss: 0.51584, 4.042s/4-batch (fw: 0.698s, bw: 0.459s), 1.0 im/s, lr: 0.0001
[ 12/20000] focal loss: 0.76191, box loss: 0.31794, 0.187s/4-batch (fw: 0.055s, bw: 0.133s), 21.4 im/s, lr: 0.0001
[ 24/20000] focal loss: 0.65036, box loss: 0.30269, 0.173s/4-batch (fw: 0.045s, bw: 0.128s), 23.1 im/s, lr: 0.0001
[ 36/20000] focal loss: 0.46425, box loss: 0.23141, 0.178s/4-batch (fw: 0.047s, bw: 0.131s), 22.4 im/s, lr: 0.0001
[ 48/20000] focal loss: 0.45115, box loss: 0.23505, 0.180s/4-batch (fw: 0.047s, bw: 0.133s), 22.2 im/s, lr: 0.0001
[ 59/20000] focal loss: 0.38958, box loss: 0.25373, 0.184s/4-batch (fw: 0.049s, bw: 0.134s), 21.8 im/s, lr: 0.0001
[ 71/20000] focal loss: 0.37733, box loss: 0.23988, 0.174s/4-batch (fw: 0.049s, bw: 0.125s), 22.9 im/s, lr: 0.0001
[ 83/20000] focal loss: 0.39514, box loss: 0.23878, 0.181s/4-batch (fw: 0.048s, bw: 0.133s), 22.1 im/s, lr: 0.0001
[ 94/20000] focal loss: 0.39947, box loss: 0.23817, 0.185s/4-batch (fw: 0.050s, bw: 0.134s), 21.6 im/s, lr: 0.0001
[ 105/20000] focal loss: 0.37343, box loss: 0.20238, 0.182s/4-batch (fw: 0.048s, bw: 0.134s), 22.0 im/s, lr: 0.0001
[ 116/20000] focal loss: 0.19689, box loss: 0.17371, 0.183s/4-batch (fw: 0.050s, bw: 0.132s), 21.8 im/s, lr: 0.0001
[ 128/20000] focal loss: 0.20368, box loss: 0.16538, 0.178s/4-batch (fw: 0.046s, bw: 0.131s), 22.5 im/s, lr: 0.0001
[ 140/20000] focal loss: 0.22763, box loss: 0.15772, 0.176s/4-batch (fw: 0.050s, bw: 0.126s), 22.7 im/s, lr: 0.0001
[ 148/20000] focal loss: 0.21997, box loss: 0.18400, 0.585s/4-batch (fw: 0.047s, bw: 0.144s), 6.8 im/s, lr: 0.0001
Precision Media (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.52674
Precision Media (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.91450
Precision Media (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.35172
Precision Media (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.61881
Precision Media (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.00000
Precision Media (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.00000
Recall Media (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.58824
Recall Media (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.61765
Recall Media (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.61765
Recall Media (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.61765
Recall Media (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.00000
Recall Media (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.00000
Salvataggio modello: 148Per esaminare l'intero set di parametri, controlla
retinanet/main.pyIn generale, sono standard per la rilevazione e sono descritti. Avvia l'allenamento e attendi i risultati. Puoi vedere un esempio di inferenza in:
retinanet/infer_example.pyoppure eseguire il comando:
/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
Nel repository è già presente la Focal Loss e diversi backbone, e sono facilmente integrabili i propri
retinanet/backbones/*.pyNella tabella, gli autori forniscono alcune caratteristiche:

È disponibile anche il backbone ResNeXt50_32x4dFPN e ResNeXt101_32x8dFPN, preso da torchvision.
Spero che ora tu abbia un'idea migliore sulla rilevazione, ma è fondamentale leggere la documentazione ufficiale per comprendere le modalità di esportazione e logging.
Fase 5. Esportazione e inferenza dei modelli Unet con encoder Resnet
Come avrai notato, nel Dockerfile sono state installate librerie per la segmentazione e in particolare un'ottima libreria . Nel pacchetto unet puoi trovare esempi di inferenza e esportazione dei checkpoint pytorch in engine TensorRT.
Il principale problema nell'esportazione di modelli simili a Unet da ONNX a TensorRT è la necessità di specificare una dimensione fissa per l'Upsample oppure usare ConvTranspose2D:
import torch.onnx.symbolic_opset9 as onnx_symbolic
def upsample_nearest2d(g, input, output_size):
# Attualmente, il parser ONNX TRT 5.1/6.0 non supporta tutte le operazioni ONNX
# necessarie per supportare la formulazione dell'upsampling dinamico in ONNX
# Qui hardcodiamo scale=2 come soluzione temporanea
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
Con questa trasformazione, è possibile farlo automaticamente durante l'esportazione in ONNX, ma nella 7° versione di TensorRT questo problema è stato risolto, e ora non ci resta che aspettare poco.
Conclusione
Quando ho iniziato a usare Docker avevo delle riserve sulla sua performance per i miei compiti. In uno dei miei aggregatori attualmente c'è un traffico di rete abbastanza elevato generato da diverse telecamere.

Diverse prove online parlavano di un overhead relativamente alto nelle interazioni di rete e nella scrittura su VOLUME, oltre a un GIL non familiare e spaventoso, e poiché l'acquisizione di un fotogramma, l'operazione del driver e la trasmissione del fotogramma in rete sono operazioni atomiche in modalità hard real-time, le latenze in rete sono molto critiche per me.
Ma alla fine va tutto bene =)
P.S. Non dimenticare di aggiungere il tuo ciclo di allenamento preferito per la segmentazione e portarlo in produzione!
Riconoscimenti
Grazie alla comunità , senza la quale non sarebbe possibile progredire! Un enorme grazie , che mi ha ispirato a dedicarmi al DL, per i suoi preziosi consigli e la sua straordinaria professionalità!
Utilizza modelli ottimizzati in produzione!
Aurorai, llc
Fonte: habr.com
