
Ça fait mal seulement la première fois !
Bonjour à tous ! Chers amis, dans cet article, je souhaite partager mon expérience d'utilisation de TensorRT, RetinaNet basé sur le dépôt (c'est un fork du dépôt officiel de , qui permettra de commencer à utiliser des modèles optimisés en production dans les plus brefs délais). En parcourant les messages dans les canaux de la communauté , je rencontre des questions sur l'utilisation de TensorRT, et la plupart des questions se répètent, c'est pourquoi j'ai décidé d'écrire un guide aussi complet que possible sur l'utilisation de l'inférence rapide basée sur TensorRT, RetinaNet, Unet et docker.
Description de la tâche
Je propose de formuler la tâche comme suit : nous devons annoter un dataset, entraîner un réseau RetinaNet/Unet sur Pytorch1.3+, convertir les poids obtenus en ONNX, puis les convertir en moteur TensorRT et tout cela faire fonctionner dans docker, de préférence sur Ubuntu 18 et idéalement sur une architecture ARM (Jetson)*, minimisant ainsi le déploiement manuel de l'environnement. Au final, nous obtiendrons un conteneur prêt non seulement pour l'exportation et l'entraînement de RetinaNet/Unet, mais aussi pour le développement complet et l'entraînement de la classification, de la segmentation avec toute l'infrastructure nécessaire.
Étape 1. Préparation de l'environnement
Il est important de noter que ces derniers temps, je me suis complètement éloigné de l'utilisation et du déploiement de bibliothèques sur une machine de bureau, ainsi que sur un devbox. La seule chose que j'ai besoin de créer et d'installer est un environnement virtuel Python et cuda 10.2 (on peut se contenter d'un seul pilote nvidia) depuis deb.
Supposons que vous avez une Ubuntu 18 fraîchement installée. Installons cuda 10.2 (deb), je ne vais pas m'attarder sur le processus d'installation, la documentation officielle est largement suffisante.
Nous allons maintenant installer docker, un guide d'installation de docker peut être facilement trouvé, voici un exemple , la version 19+ est déjà disponible — installons-la. Et n'oubliez pas de permettre l'utilisation de docker sans sudo, ce sera plus pratique. Une fois que tout est en ordre, faisons comme suit :
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
Et on peut même ne pas regarder dans le dépôt officiel .
Maintenant, faisons un git clone .
Il ne reste plus longtemps avant de pouvoir utiliser Docker avec l'image NVIDIA. Nous devons nous inscrire sur NGC Cloud et nous connecter. Allons ici , nous nous inscrivons et dès que nous accédons à NGC Cloud, nous cliquons sur SETUP en haut à gauche de l'écran ou nous suivons ce lien . Cliquons sur « générer la clé ». Je recommande de la conserver, sinon il faudra la générer à nouveau lors de votre prochaine visite, ce qui impliquera de répéter cette opération en la déployant sur une nouvelle machine.
Exécutons :
docker login nvcr.io
Nom d'utilisateur : $oauthtoken
Mot de passe : - clé générée
Nous copions simplement le nom d'utilisateur. Voilà, c'est fait, l'environnement est déployé !
Étape 2. Construction du conteneur Docker
À la deuxième étape de notre travail, nous allons construire Docker et nous familiariser avec ses composants.
Accédons au dossier racine par rapport au projet retina-examples et exécutons
docker build --build-arg USER=$USER --build-arg UID=$UID --build-arg GID=$GID --build-arg PW=alex -t retinanet:latest retinanet/
Nous construisons Docker en y passant l'utilisateur actuel - c'est très utile si vous prévoyez de créer quelque chose sur un VOLUME monté avec les droits de l'utilisateur actuel, sinon ce sera root et ce sera douloureux.
Pendant que Docker se construit, étudions le 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"]
Comme vous pouvez le voir dans le texte, nous rassemblons toutes nos bibliothèques préférées, compilons retinanet, ajoutons des outils de base pour faciliter le travail avec Ubuntu et configurons le serveur openssh. En première ligne, il y a justement l'héritage de l'image nvidia, pour laquelle nous avons effectué la connexion à NGC Cloud et qui contient Pytorch1.3, TensorRT6.x.x.x et plein d'autres bibliothèques permettant de compiler les sources cpp de notre détecteur.
Étape 3. Lancement et débogage du conteneur docker
Passons au principal cas d'utilisation du conteneur et de l'environnement de développement, pour commencer, lançons nvidia docker. Exécutons :
docker run --gpus all --net=host -v /home/:/workspace/mounted_vol -d -P --rm --ipc=host -it retinanet:latestMaintenant, le conteneur est accessible par ssh @localhost. Après le lancement réussi, ouvrez le projet dans PyCharm. Ensuite, ouvrez
Settings->Project Interpreter->Add->Ssh Interpreter Étape 1

Étape 2

Étape 3

Choisissez tout comme sur les captures d'écran,
Interpreter -> /opt/conda/bin/python— ce sera un lien vers Python3.6 et
Sync folder -> /workspace/retinanetCliquez sur finir, attendez l'indexation, et voilà, l'environnement est prêt à l'emploi !
IMPORTANT !!! Juste après l'indexation, récupérez depuis docker les fichiers compilés pour Retinanet. Dans le menu contextuel à la racine du projet, sélectionnez l'option
Deployment->DownloadUn fichier et deux dossiers build, retinanet.egg-info et _С.so apparaîtront

Si votre projet ressemble à cela, alors l'environnement voit tous les fichiers nécessaires et nous sommes prêts pour l'entraînement de RetinaNet.
Étape 4. Annotation des données et entraînement du détecteur
Pour l'annotation, j'utilise principalement — un outil agréable et pratique, récemment beaucoup de bugs ont été corrigés et il fonctionne beaucoup mieux.
Supposons que vous ayez annoté le jeu de données et que vous l'ayez téléchargé, mais vous ne pourrez pas simplement le mettre dans notre RetinaNet, car il est dans son propre format et nous devons le convertir en COCO. L'outil de conversion se trouve à :
markup_utils/supervisly_to_coco.pyVeuillez noter que Category dans le script est un exemple et que vous devez insérer les vôtres (vous n'avez pas besoin d'ajouter la catégorie background)
categories = [{'id': 1, 'name': '1'},
{'id': 2, 'name': '2'},
{'id': 3, 'name': '3'},
{'id': 4, 'name': '4'}] Les auteurs du dépôt original ont décidé, pour une raison quelconque, que vous n'alliez entraîner rien d'autre que COCO/VOC pour la détection, donc j'ai dû éditer un peu le fichier source
retinanet/dataset.pyEn y ajoutant mes augmentations préférées et retirer les catégories intégrées de manière rigide de COCO. Il est également possible de recadrer de grandes zones de détection si vous cherchez de petits objets sur de grandes images, que vous avez un petit jeu de données =), et que rien ne fonctionne, mais nous en discuterons une autre fois.
En général, la boucle d'entraînement est aussi assez faible, au départ elle ne sauvegardait pas les checkpoints, utilisait un scheduler horrible, etc. Mais maintenant, il ne vous reste plus qu'à choisir le backbone et à exécuter
/opt/conda/bin/python retinanet/main.pyavec les paramètres :
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
Dans la console, vous verrez :
Initialisation du modèle...
modèle : RetinaNet
backbone : ResNet18FPN
classes : 2, ancres : 9
Niveau d'optimisation sélectionné O0 : entraînement FP32 pur.
Les paramètres par défaut pour ce niveau d'optimisation sont :
enabled : True
opt_level : O0
cast_model_type : torch.float32
patch_torch_functions : False
keep_batchnorm_fp32 : Aucun
master_weights : False
loss_scale : 1.0
Traitement des surcharges utilisateur (kwargs supplémentaires qui ne sont pas None)...
Après traitement des surcharges, les options d'optimisation sont :
enabled : True
opt_level : O0
cast_model_type : torch.float32
patch_torch_functions : False
keep_batchnorm_fp32 : Aucun
master_weights : False
loss_scale : 128.0
Préparation du jeu de données...
chargeur : pytorch
redimensionner : [1024, 1280], max : 1280
appareil : 4 gpus
lot : 4, précision : mixte
Entraînement du modèle pendant 20000 itérations...
[ 1/20000] perte focale : 0.95619, perte de boîte : 0.51584, 4.042s/4-lots (fw : 0.698s, bw : 0.459s), 1.0 im/s, lr : 0.0001
[ 12/20000] perte focale : 0.76191, perte de boîte : 0.31794, 0.187s/4-lots (fw : 0.055s, bw : 0.133s), 21.4 im/s, lr : 0.0001
[ 24/20000] perte focale : 0.65036, perte de boîte : 0.30269, 0.173s/4-lots (fw : 0.045s, bw : 0.128s), 23.1 im/s, lr : 0.0001
[ 36/20000] perte focale : 0.46425, perte de boîte : 0.23141, 0.178s/4-lots (fw : 0.047s, bw : 0.131s), 22.4 im/s, lr : 0.0001
[ 48/20000] perte focale : 0.45115, perte de boîte : 0.23505, 0.180s/4-lots (fw : 0.047s, bw : 0.133s), 22.2 im/s, lr : 0.0001
[ 59/20000] perte focale : 0.38958, perte de boîte : 0.25373, 0.184s/4-lots (fw : 0.049s, bw : 0.134s), 21.8 im/s, lr : 0.0001
[ 71/20000] perte focale : 0.37733, perte de boîte : 0.23988, 0.174s/4-lots (fw : 0.049s, bw : 0.125s), 22.9 im/s, lr : 0.0001
[ 83/20000] perte focale : 0.39514, perte de boîte : 0.23878, 0.181s/4-lots (fw : 0.048s, bw : 0.133s), 22.1 im/s, lr : 0.0001
[ 94/20000] perte focale : 0.39947, perte de boîte : 0.23817, 0.185s/4-lots (fw : 0.050s, bw : 0.134s), 21.6 im/s, lr : 0.0001
[ 105/20000] perte focale : 0.37343, perte de boîte : 0.20238, 0.182s/4-lots (fw : 0.048s, bw : 0.134s), 22.0 im/s, lr : 0.0001
[ 116/20000] perte focale : 0.19689, perte de boîte : 0.17371, 0.183s/4-lots (fw : 0.050s, bw : 0.132s), 21.8 im/s, lr : 0.0001
[ 128/20000] perte focale : 0.20368, perte de boîte : 0.16538, 0.178s/4-lots (fw : 0.046s, bw : 0.131s), 22.5 im/s, lr : 0.0001
[ 140/20000] perte focale : 0.22763, perte de boîte : 0.15772, 0.176s/4-lots (fw : 0.050s, bw : 0.126s), 22.7 im/s, lr : 0.0001
[ 148/20000] perte focale : 0.21997, perte de boîte : 0.18400, 0.585s/4-lots (fw : 0.047s, bw : 0.144s), 6.8 im/s, lr : 0.0001
Précision moyenne (AP) @[ IoU=0.50:0.95 | zone= tout | maxDets=100 ] = 0.52674
Précision moyenne (AP) @[ IoU=0.50 | zone= tout | maxDets=100 ] = 0.91450
Précision moyenne (AP) @[ IoU=0.75 | zone= tout | maxDets=100 ] = 0.35172
Précision moyenne (AP) @[ IoU=0.50:0.95 | zone= petite | maxDets=100 ] = 0.61881
Précision moyenne (AP) @[ IoU=0.50:0.95 | zone=moyenne | maxDets=100 ] = -1.00000
Précision moyenne (AP) @[ IoU=0.50:0.95 | zone= grande | maxDets=100 ] = -1.00000
Rappel moyen (AR) @[ IoU=0.50:0.95 | zone= tout | maxDets= 1 ] = 0.58824
Rappel moyen (AR) @[ IoU=0.50:0.95 | zone= tout | maxDets= 10 ] = 0.61765
Rappel moyen (AR) @[ IoU=0.50:0.95 | zone= tout | maxDets= 100 ] = 0.61765
Rappel moyen (AR) @[ IoU=0.50:0.95 | zone= petite | maxDets=100 ] = 0.61765
Rappel moyen (AR) @[ IoU=0.50:0.95 | zone=moyenne | maxDets=100 ] = -1.00000
Rappel moyen (AR) @[ IoU=0.50:0.95 | zone= grande | maxDets=100 ] = -1.00000
Enregistrement du modèle : 148Pour examiner tous les paramètres, consultez
retinanet/main.pyEn général, ils sont standards pour la détection et ont une description. Lancez l'entraînement et attendez les résultats. Vous pouvez consulter un exemple d'inférence ici :
retinanet/infer_example.pyou exécutez la commande :
/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
Le dépôt contient déjà Focal Loss et plusieurs backbones, et il est également facile d'intégrer les vôtres.
retinanet/backbones/*.pyDans le tableau, les auteurs présentent certaines caractéristiques :

Il y a aussi les backbones ResNeXt50_32x4dFPN et ResNeXt101_32x8dFPN, pris de torchvision.
J'espère que nous avons un peu clarifié la détection, mais il est essentiel de lire la documentation officielle pour comprendre les modes d'exportation et de journalisation..
Étape 5. Exportation et inférence des modèles Unet avec l'encodeur Resnet.
Comme vous l'avez probablement remarqué, des bibliothèques pour la segmentation ont été installées dans le Dockerfile, notamment la formidable bibliothèque Dans le paquet Unet, vous pouvez trouver des exemples d'inférence et d'exportation des checkpoints PyTorch dans le moteur TensorRT.
Le principal problème lors de l'exportation de modèles de type Unet d'ONNX vers TensorRT est la nécessité de définir une taille fixe pour Upsample ou d'utiliser ConvTranspose2D :
import torch.onnx.symbolic_opset9 as onnx_symbolic
def upsample_nearest2d(g, input, output_size):
# Actuellement, le Parser ONNX TRT 5.1/6.0 ne prend pas en charge toutes les opérations ONNX
# nécessaires pour supporter l'upsampling dynamique dans la formulation ONNX
# Ici, nous codons en dur scale=2 comme solution temporaire
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
Avec cette transformation, cela peut être fait automatiquement lors de l'exportation vers ONNX, mais dans la version 7 de TensorRT, ce problème a été résolu, et nous devons attendre très peu de temps.
Conclusion
Lorsque j'ai commencé à utiliser Docker, j'avais des doutes sur sa performance pour mes tâches. Dans l'un de mes agrégats, il y a actuellement un trafic réseau assez important généré par plusieurs caméras.

Divers tests sur Internet parlaient d'un overhead relativement important sur l'interaction réseau et l'écriture sur VOLUME, de plus le mystérieux et effrayant GIL, et comme la capture d'image, le fonctionnement du pilote et la transmission d'images sur le réseau sont des opérations atomiques en mode hard real-time, les délais réseau sont très critiques pour moi.
Mais tout s'est bien passé =)
P.S. Il ne reste plus qu'à ajouter votre boucle d'entraînement préférée pour la segmentation et en production !
Remerciements
Merci à la communauté , sans elle il est impossible de progresser ! Un grand merci , qui m'a encouragé à me lancer dans DL, pour ses conseils précieux et son professionnalisme exceptionnel !
Utilisez dans la production des modèles optimisés !
Aurorai, llc
Source : habr.com
