
Fa male solo la prima volta!
Ciao a tutti! Cari amici, in questo articolo voglio condividere la mia esperienza nell'utilizzo di TensorRT, RetinaNet basato sul repository (questo è un fork del repository ufficiale di , che consentirà di iniziare a utilizzare modelli ottimizzati in produzione in tempi brevi). Scorrendo i messaggi nei canali della comunità , mi imbatto in domande sull'uso di TensorRT, e per lo più le domande si ripetono, quindi ho deciso di scrivere una guida il più completa possibile sull'uso di inferenza rapida basata su TensorRT, RetinaNet, Unet e docker.
Descrizione del compito
Propongo di impostare il compito in questo modo: dobbiamo etichettare il dataset, addestrare la rete RetinaNet/Unet su Pytorch1.3+, convertire i pesi ottenuti in ONNX, poi convertirli in engine TensorRT e avviare tutto ciò in docker, preferibilmente su Ubuntu 18 e molto desiderato su architettura ARM(Jetson)*, minimizzando così il deployment manuale dell'ambiente. In conclusione, otterremo un contenitore pronto non solo per l'esportazione e l'addestramento di RetinaNet/Unet, ma anche per uno sviluppo e un addestramento completo di classificazione, segmentazione, con tutta la necessaria interfaccia.
Fase 1. Preparazione dell'ambiente
Qui è importante notare che negli ultimi tempi ho completamente abbandonato l'uso e il deployment di qualsiasi libreria su una macchina desktop, così come su devbox. L'unica cosa che devo creare e installare è un ambiente virtuale python e cuda 10.2 (si può limitare a un solo driver nvidia) da deb.
Supponiamo che tu abbia Ubuntu 18 appena installato. Installeremo cuda 10.2(deb), non approfondirò il processo di installazione, la documentazione ufficiale è più che sufficiente.
Ora installiamo docker, la guida all'installazione di docker si può facilmente trovare, ecco un esempio , è già disponibile la versione 19+ — la installiamo. E non dimenticate di permettere l'uso di docker senza sudo, sarà più comodo. Dopo che tutto sarà andato a buon fine, 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 si può anche non guardare nel repository ufficiale .
Ora facciamo un git clone .
Manca davvero poco per iniziare a utilizzare Docker con l'immagine NVIDIA, dobbiamo registrarci su NGC Cloud e fare il login. Andiamo qui , registriamoci 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". Consiglio di salvarla, altrimenti alla prossima visita dovremo generarla di nuovo e, di conseguenza, ripetere questa operazione su una nuova macchina.
Eseguiamo:
docker login nvcr.io
Username: $oauthtoken
Password: - chiave generata
Copiamo semplicemente l'Username. Bene, consideriamo l'ambiente avviato!
Fase 2. Creazione del contenitore Docker
Nella seconda fase del nostro lavoro creeremo Docker e ci familiarizzeremo con i suoi internals.
Entriamo 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 creando Docker passando l'utente corrente — questo è molto utile se intendete scrivere su un VOLUME montato con i diritti dell'utente corrente, altrimenti sarà root e complicato.
Mentre Docker si sta creando, 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 risulta dal testo, prendiamo tutte le nostre librerie preferite, compiliamo retinanet, mettiamo a disposizione strumenti di base per un lavoro più facile con Ubuntu e configuriamo il server openssh. La prima riga riguarda proprio l'eredità 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 permettono di compilare i sorgenti cpp del nostro rilevatore.
Fase 3. Avvio e debug del contenitore docker
Passiamo al caso principale di utilizzo del contenitore e dell'ambiente di sviluppo, iniziamo avviando nvidia docker. Eseguiamo:
docker run --gpus all --net=host -v /home/:/workspace/mounted_vol -d -P --rm --ipc=host -it retinanet:latestOra il contenitore è disponibile via ssh @localhost. Dopo un avvio riuscito, apriamo il progetto in PyCharm. Proseguiamo aprendo
Impostazioni->Interprete Progetto->Aggiungi->Interprete Ssh Passo 1

Passo 2

Passo 3

Selezioniamo tutto come nelle schermate catturate,
Interprete -> /opt/conda/bin/python— questo sarà un collegamento a Python3.6 e
Cartella di sincronizzazione -> /workspace/retinanetFacciamo clic su fine, aspettiamo l'indicizzazione, ed ecco, l'ambiente è pronto per l'uso!
IMPORTANTE!!! Subito dopo l'indicizzazione, estrarre dai file compilati in docker per Retinanet. Nel menu contestuale nella radice del progetto selezioniamo l'opzione
Distribuzione->ScaricaAppariranno un file e due cartelle build, retinanet.egg-info e _C.so

Se il tuo progetto appare in questo modo, allora l'ambiente vede tutti i file necessari e siamo pronti per addestrare RetinaNet.
Fase 4. Annotiamo i dati e alleniamo il rilevatore
Per l'annotazione, principalmente utilizzo — una ferramenta piacevole e comoda, negli ultimi tempi sono stati risolti molti problemi e ora funziona in modo significativamente migliore.
Supponiamo che tu abbia annotato il dataset e lo abbia scaricato, ma non possiamo semplicemente inserirlo nel nostro RetinaNet, poiché è nel formato proprietario e per questo dobbiamo convertirlo in COCO. Lo strumento per la conversione si trova in:
markup_utils/supervisly_to_coco.pyNota che Category nello script è un esempio e devi inserire i tuoi (non 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 allenerai nulla oltre a COCO/VOC per la rilevazione, quindi è stato necessario modificare un po' il file sorgente
retinanet/dataset.pyAggiungendo qui le mie augmentazioni preferite e rimuovere rigidamente le categorie integrate in COCO. C'è anche la possibilità di ritagliare grandi aree di rilevamento, se stai cercando piccoli oggetti in immagini grandi, hai un piccolo dataset =), e niente funziona, ma di questo parleremo un'altra volta.
In generale, anche il ciclo di addestramento è piuttosto debole, inizialmente non salvava i checkpoint, utilizzava un dispettoso scheduler e così via. Ma ora non ti resta che scegliere il backbone ed eseguire
/opt/conda/bin/python retinanet/main.pycon i seguenti 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
Vedrai nel terminale:
Inizializzazione del modello...
modello: RetinaNet
backbone: ResNet18FPN
classi: 2, ancore: 9
Livello di ottimizzazione selezionato O0: Formazione pura FP32.
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 (kwargs aggiuntivi che non sono None)...
Dopo l'elaborazione delle 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
Preparazione del dataset...
loader: pytorch
resize: [1024, 1280], max: 1280
device: 4 gpu
batch: 4, precisione: mista
Formazione del modello per 20000 iterazioni...
[ 1/20000] perdita focale: 0.95619, perdita della casella: 0.51584, 4.042s/4-batch (fw: 0.698s, bw: 0.459s), 1.0 im/s, lr: 0.0001
[ 12/20000] perdita focale: 0.76191, perdita della casella: 0.31794, 0.187s/4-batch (fw: 0.055s, bw: 0.133s), 21.4 im/s, lr: 0.0001
[ 24/20000] perdita focale: 0.65036, perdita della casella: 0.30269, 0.173s/4-batch (fw: 0.045s, bw: 0.128s), 23.1 im/s, lr: 0.0001
[ 36/20000] perdita focale: 0.46425, perdita della casella: 0.23141, 0.178s/4-batch (fw: 0.047s, bw: 0.131s), 22.4 im/s, lr: 0.0001
[ 48/20000] perdita focale: 0.45115, perdita della casella: 0.23505, 0.180s/4-batch (fw: 0.047s, bw: 0.133s), 22.2 im/s, lr: 0.0001
[ 59/20000] perdita focale: 0.38958, perdita della casella: 0.25373, 0.184s/4-batch (fw: 0.049s, bw: 0.134s), 21.8 im/s, lr: 0.0001
[ 71/20000] perdita focale: 0.37733, perdita della casella: 0.23988, 0.174s/4-batch (fw: 0.049s, bw: 0.125s), 22.9 im/s, lr: 0.0001
[ 83/20000] perdita focale: 0.39514, perdita della casella: 0.23878, 0.181s/4-batch (fw: 0.048s, bw: 0.133s), 22.1 im/s, lr: 0.0001
[ 94/20000] perdita focale: 0.39947, perdita della casella: 0.23817, 0.185s/4-batch (fw: 0.050s, bw: 0.134s), 21.6 im/s, lr: 0.0001
[ 105/20000] perdita focale: 0.37343, perdita della casella: 0.20238, 0.182s/4-batch (fw: 0.048s, bw: 0.134s), 22.0 im/s, lr: 0.0001
[ 116/20000] perdita focale: 0.19689, perdita della casella: 0.17371, 0.183s/4-batch (fw: 0.050s, bw: 0.132s), 21.8 im/s, lr: 0.0001
[ 128/20000] perdita focale: 0.20368, perdita della casella: 0.16538, 0.178s/4-batch (fw: 0.046s, bw: 0.131s), 22.5 im/s, lr: 0.0001
[ 140/20000] perdita focale: 0.22763, perdita della casella: 0.15772, 0.176s/4-batch (fw: 0.050s, bw: 0.126s), 22.7 im/s, lr: 0.0001
[ 148/20000] perdita focale: 0.21997, perdita della casella: 0.18400, 0.585s/4-batch (fw: 0.047s, bw: 0.144s), 6.8 im/s, lr: 0.0001
Precisione Media (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.52674
Precisione Media (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.91450
Precisione Media (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.35172
Precisione Media (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.61881
Precisione Media (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.00000
Precisione Media (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.00000
Richiamo Medio (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.58824
Richiamo Medio (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.61765
Richiamo Medio (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.61765
Richiamo Medio (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.61765
Richiamo Medio (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.00000
Richiamo Medio (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.00000
Salvataggio del modello: 148Per studiare l'intero set di parametri, guarda
retinanet/main.pyIn generale, sono standard per la rilevazione e hanno una descrizione. Avvia l'allenamento e attendi i risultati. Un esempio di inferenza può essere visualizzato in:
retinanet/infer_example.pyo 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à integrato Focal Loss e diversi backbone, e si possono facilmente integrare i propri.
retinanet/backbones/*.pyNella tabella gli autori forniscono alcune caratteristiche:

C'è anche il backbone ResNeXt50_32x4dFPN e ResNeXt101_32x8dFPN, preso da torchvision.
Spero che con la detezione siamo riusciti a chiarire, ma è necessario leggere assolutamente la documentazione ufficiale per capire le modalità di esportazione e registrazione..
Fase 5. Esportazione e inferenza dei modelli Unet con l'encoder Resnet.
Come avrete notato, nel Dockerfile sono state installate librerie per la segmentazione, e in particolare la fantastica libreria Nel pacchetto Unet puoi trovare esempi di inferenza e esportazione dei checkpoint pytorch in engine TensorRT.
Il problema principale durante l'esportazione di modelli simili a Unet da ONNX a TensorRT è la necessità di definire una dimensione fissa per l'Upsample o di utilizzare ConvTranspose2D:
import torch.onnx.symbolic_opset9 as onnx_symbolic
def upsample_nearest2d(g, input, output_size):
# Attualmente, il parser ONNX di TRT 5.1/6.0 non supporta tutte le operazioni ONNX
# necessarie per supportare l'upsampling dinamico nella formulazione 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 già nella settima versione di TensorRT questo problema è stato risolto, e non ci resta che aspettare molto poco.
Conclusione
Quando ho iniziato a usare Docker, avevo dei dubbi riguardo alle sue performance per i miei compiti. In uno dei miei aggregati, ora c'è un traffico di rete abbastanza elevato generato da diverse telecamere.

Diverse prove su internet parlavano di un overhead relativamente grande sulle interazioni di rete e sulla registrazione su VOLUME, inoltre c'era l'ignoto e spaventoso GIL, e poiché l'acquisizione del frame, il funzionamento del driver e la trasmissione della frame sono operazioni atomiche in modalità hard real-time, le latenze di rete sono per me molto critiche.
Ma è andato tutto bene =)
P.S. Non resta che aggiungere il vostro amato ciclo di addestramento per la segmentazione e mandarlo in produzione!
Ringraziamenti
Grazie alla comunità , senza la quale non sarebbe possibile progredire! Un enorme grazie , che mi ha incoraggiato a intraprendere il DL, per i suoi preziosi consigli e il suo straordinario professionismo!
Utilizzate in produzione modelli ottimizzati!
Aurorai, llc
Fonte: habr.com
