
Só dói na primeira vez!
Olá a todos! Caros amigos, neste artigo eu quero compartilhar minha experiência com o uso do TensorRT e RetinaNet com base no repositório (este é um fork do repositório oficial de , que permitirá começar a usar modelos otimizados em produção rapidamente). Ao percorrer as mensagens nos canais da comunidade , encontro perguntas sobre o uso do TensorRT, e principalmente as perguntas se repetem, então eu decidi escrever um guia o mais completo possível sobre como realizar inferência rápida usando TensorRT, RetinaNet, Unet e docker.
Descrição da tarefa
Proponho formular a tarefa da seguinte maneira: precisamos anotar o dataset, treinar uma rede RetinaNet/Unet no Pytorch1.3+, converter os pesos obtidos para ONNX, em seguida, convertê-los em engine TensorRT e colocar tudo isso para rodar em um docker, de preferência no Ubuntu 18 e, idealmente, na arquitetura ARM(Jetson)*, minimizando assim a configuração manual do ambiente. No final, teremos um contêiner pronto não apenas para exportar e treinar RetinaNet/Unet, mas também para desenvolver e treinar a classificação e segmentação com toda a infraestrutura necessária.
Etapa 1. Preparação do ambiente
É importante notar que, recentemente, deixei de usar e implantar qualquer biblioteca em uma máquina desktop, assim como em um devbox. A única coisa que preciso criar e instalar é um ambiente virtual python e cuda 10.2 (pode-se limitar a apenas um driver nvidia) a partir de deb.
Suponha que você tenha um Ubuntu 18 recém-instalado. Vamos instalar cuda 10.2(deb), não vou me aprofundar no processo de instalação, a documentação oficial é suficiente.
Agora vamos instalar o docker, um guia para a instalação do docker pode ser facilmente encontrado, aqui está um exemplo , já está disponível a versão 19+, vamos instalar. E não se esqueça de permitir o uso do docker sem sudo, será mais conveniente. Depois que tudo estiver certo, fazemos assim:
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 nem precisamos olhar no repositório oficial .
Agora fazemos git clone .
Está quase na hora de começar a usar o Docker com a imagem da NVIDIA. Precisamos nos registrar no NGC Cloud e fazer login. Vamos para cá , faça seu registro e, depois de entrar no NGC Cloud, clique em CONFIGURAR no canto superior esquerdo da tela ou acesse este link . Clique em "gerar chave". É recomendável salvá-la; do contrário, na próxima vez que visitar, será necessário gerar uma nova e, consequentemente, ao implantar em uma nova máquina, você terá que repetir esta operação.
Vamos executar:
docker login nvcr.io
Username: $oauthtoken
Password: - chave gerada
Copiamos apenas o nome de usuário. Pronto, a ambiente está configurada!
Etapa 2. Montagem do contêiner Docker
Na segunda etapa do nosso trabalho, iremos montar o Docker e nos familiarizar com seus componentes internos.
Vamos entrar na pasta raiz em relação ao projeto retina-examples e executar
docker build --build-arg USER=$USER --build-arg UID=$UID --build-arg GID=$GID --build-arg PW=alex -t retinanet:latest retinanet/
Estamos montando o Docker passando o usuário atual — é muito útil se você for escrever algo em um VOLUME montado com as permissões do usuário atual; caso contrário, será root e complicado.
Enquanto o Docker está sendo montado, vamos dar uma olhada no 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"]
Como pode ser visto no texto, pegamos todas as nossas bibliotecas favoritas, compilamos o retinanet, adicionamos ferramentas básicas para facilitar o trabalho com Ubuntu e configuramos o servidor openssh. A primeira linha é justamente a herança da imagem nvidia, para a qual fizemos login no NGC Cloud e que contém Pytorch1.3, TensorRT6.x.x.x e várias outras bibliotecas que permitem compilar os códigos fonte cpp do nosso detector.
Etapa 3. Execução e depuração do contêiner docker
Vamos ao caso de uso principal do contêiner e do ambiente de desenvolvimento, primeiramente iniciais o nvidia docker. Executamos:
docker run --gpus all --net=host -v /home/:/workspace/mounted_vol -d -P --rm --ipc=host -it retinanet:latestAgora o contêiner está acessível via ssh @localhost. Após o início bem-sucedido, abra o projeto no PyCharm. Em seguida, abra
Configurações->Intérprete do Projeto->Adicionar->Intérprete Ssh Passo 1

Passo 2

Passo 3

Escolha tudo como nas capturas de tela,
Intérprete -> /opt/conda/bin/python--- isso será um link para o Python3.6 e
Pasta de Sincronização -> /workspace/retinanetClicamos em concluir, aguardamos a indexação, e pronto, o ambiente está pronto para uso!
IMPORTANTE!!! Imediatamente após a indexação, extrair os arquivos compilados de dentro do docker para o Retinanet. No menu contextual na raiz do projeto, escolheremos a opção
Implantação->BaixarUm arquivo e duas pastas aparecerão: build, retinanet.egg-info e _С.so

Se o seu projeto se parecer com isso, então o ambiente vê todos os arquivos necessários e estamos prontos para o treinamento do RetinaNet.
Etapa 4. Rotulando os dados e treinando o detector
Para rotulagem, eu uso principalmente --- uma ferramenta agradável e conveniente, nos últimos tempos corrigiram uma série de bugs e ela começou a se comportar muito melhor.
Suponha que você rotulou o conjunto de dados e o baixou, mas não conseguirá colocar imediatamente no nosso RetinaNet, pois ele está em um formato proprietário e precisamos convertê-lo para COCO. A ferramenta para conversão está em:
markup_utils/supervisly_to_coco.pyObserve que a Categoria no script é um exemplo e você deve inserir suas próprias (não adicione a categoria de fundo)
categories = [{'id': 1, 'name': '1'},
{'id': 2, 'name': '2'},
{'id': 3, 'name': '3'},
{'id': 4, 'name': '4'}] Os autores do repositório original, por algum motivo, decidiram que você não iria treinar nada além de COCO/VOC para detecção, então foi necessário editar um pouco o arquivo original
retinanet/dataset.pyAdicionando aqui as augmentações favoritas e remover categorias rigidamente incorporadas do COCO. Também há a possibilidade de recortar grandes áreas de detecção, se você estiver procurando pequenos objetos em imagens grandes, tem um pequeno conjunto de dados =), e nada funciona, mas falaremos mais sobre isso depois.
No geral, o loop de treinamento também é fraco, inicialmente não salvava pontos de verificação, usava algum scheduler horrível, etc. Mas agora você só precisa escolher a backbone e executar.
/opt/conda/bin/python retinanet/main.pycom os parâmetros:
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
Você verá no console:
Inicializando modelo...
modelo: RetinaNet
backbone: ResNet18FPN
classes: 2, anchors: 9
Nível de otimização selecionado O0: Treinamento FP32 puro.
Os padrões para este nível de otimização são:
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
Processando substituições do usuário (kwargs adicionais que não são None)...
Após processar substituições, as opções de otimização são:
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 dataset...
loader: pytorch
resize: [1024, 1280], max: 1280
device: 4 gpus
batch: 4, precision: mixed
Treinando modelo por 20000 iterações...
[ 1/20000] perda focal: 0.95619, perda de caixa: 0.51584, 4.042s/4-batch (fw: 0.698s, bw: 0.459s), 1.0 im/s, lr: 0.0001
[ 12/20000] perda focal: 0.76191, perda de caixa: 0.31794, 0.187s/4-batch (fw: 0.055s, bw: 0.133s), 21.4 im/s, lr: 0.0001
[ 24/20000] perda focal: 0.65036, perda de caixa: 0.30269, 0.173s/4-batch (fw: 0.045s, bw: 0.128s), 23.1 im/s, lr: 0.0001
[ 36/20000] perda focal: 0.46425, perda de caixa: 0.23141, 0.178s/4-batch (fw: 0.047s, bw: 0.131s), 22.4 im/s, lr: 0.0001
[ 48/20000] perda focal: 0.45115, perda de caixa: 0.23505, 0.180s/4-batch (fw: 0.047s, bw: 0.133s), 22.2 im/s, lr: 0.0001
[ 59/20000] perda focal: 0.38958, perda de caixa: 0.25373, 0.184s/4-batch (fw: 0.049s, bw: 0.134s), 21.8 im/s, lr: 0.0001
[ 71/20000] perda focal: 0.37733, perda de caixa: 0.23988, 0.174s/4-batch (fw: 0.049s, bw: 0.125s), 22.9 im/s, lr: 0.0001
[ 83/20000] perda focal: 0.39514, perda de caixa: 0.23878, 0.181s/4-batch (fw: 0.048s, bw: 0.133s), 22.1 im/s, lr: 0.0001
[ 94/20000] perda focal: 0.39947, perda de caixa: 0.23817, 0.185s/4-batch (fw: 0.050s, bw: 0.134s), 21.6 im/s, lr: 0.0001
[ 105/20000] perda focal: 0.37343, perda de caixa: 0.20238, 0.182s/4-batch (fw: 0.048s, bw: 0.134s), 22.0 im/s, lr: 0.0001
[ 116/20000] perda focal: 0.19689, perda de caixa: 0.17371, 0.183s/4-batch (fw: 0.050s, bw: 0.132s), 21.8 im/s, lr: 0.0001
[ 128/20000] perda focal: 0.20368, perda de caixa: 0.16538, 0.178s/4-batch (fw: 0.046s, bw: 0.131s), 22.5 im/s, lr: 0.0001
[ 140/20000] perda focal: 0.22763, perda de caixa: 0.15772, 0.176s/4-batch (fw: 0.050s, bw: 0.126s), 22.7 im/s, lr: 0.0001
[ 148/20000] perda focal: 0.21997, perda de caixa: 0.18400, 0.585s/4-batch (fw: 0.047s, bw: 0.144s), 6.8 im/s, lr: 0.0001
Precisão média (AP) @[ IoU=0.50:0.95 | área= toda | maxDets=100 ] = 0.52674
Precisão média (AP) @[ IoU=0.50 | área= toda | maxDets=100 ] = 0.91450
Precisão média (AP) @[ IoU=0.75 | área= toda | maxDets=100 ] = 0.35172
Precisão média (AP) @[ IoU=0.50:0.95 | área= pequena | maxDets=100 ] = 0.61881
Precisão média (AP) @[ IoU=0.50:0.95 | área=média | maxDets=100 ] = -1.00000
Precisão média (AP) @[ IoU=0.50:0.95 | área= grande | maxDets=100 ] = -1.00000
Recall médio (AR) @[ IoU=0.50:0.95 | área= toda | maxDets= 1 ] = 0.58824
Recall médio (AR) @[ IoU=0.50:0.95 | área= toda | maxDets= 10 ] = 0.61765
Recall médio (AR) @[ IoU=0.50:0.95 | área= toda | maxDets=100 ] = 0.61765
Recall médio (AR) @[ IoU=0.50:0.95 | área= pequena | maxDets=100 ] = 0.61765
Recall médio (AR) @[ IoU=0.50:0.95 | área=média | maxDets=100 ] = -1.00000
Recall médio (AR) @[ IoU=0.50:0.95 | área= grande | maxDets=100 ] = -1.00000
Salvando modelo: 148Para estudar todo o conjunto de parâmetros, veja
retinanet/main.pyEm geral, eles são padrões para detecção, e há uma descrição disponível. Execute o treinamento e aguarde os resultados. Um exemplo de inferência pode ser visto em:
retinanet/infer_example.pyou execute o 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
O repositório já possui Focal Loss e várias backbone integrados, além de permitir a fácil inclusão das suas próprias.
retinanet/backbones/*.pyNa tabela, os autores apresentam algumas características:

Há também a backbone ResNeXt50_32x4dFPN e ResNeXt101_32x8dFPN, retiradas do torchvision.
Espero que tenhamos conseguido entender a detecção, mas é essencial ler a documentação oficial para compreender os modos de exportação e registro..
Etapa 5. Exportação e Inferência de Modelos Unet com o codificador Resnet.
Como você provavelmente notou, no Dockerfile foram instaladas bibliotecas para segmentação, em particular a biblioteca incrível . No pacote Unet, você pode encontrar exemplos de inferência e exportação de checkpoints PyTorch para o engine TensorRT.
O principal problema ao exportar modelos do tipo Unet de ONNX para TensorRT é a necessidade de definir um tamanho fixo para Upsample ou usar ConvTranspose2D:
import torch.onnx.symbolic_opset9 as onnx_symbolic
def upsample_nearest2d(g, input, output_size):
# Atualmente, o Parser ONNX do TRT 5.1/6.0 não suporta todas as operações ONNX
# necessárias para suportar a formulação dinâmica de upsampling
# Aqui hardcodeamos scale=2 como uma solução temporária
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
Com essa transformação, é possível fazer isso automaticamente ao exportar para ONNX, mas já na versão 7 do TensorRT esse problema foi resolvido, e nos resta esperar um pouco mais.
Conclusão
Quando comecei a usar Docker, eu tinha dúvidas sobre seu desempenho para minhas tarefas. Em um dos meus agregados, atualmente há um tráfego de rede bastante grande gerado por várias câmeras.

Vários testes na internet mencionavam um overhead relativamente alto na interação de rede e gravação em VOLUME, e além disso, o misterioso e assustador GIL, já que a captura de quadros, o funcionamento do driver e a transmissão do quadro por rede são operações atômicas em modo de tempo real rígido, os atrasos na rede são muito críticos para mim.
Mas acabou tudo bem =)
P.S. Basta adicionar seu loop de treinamento favorito para segmentação e levar ao produção!
Agradecimentos
Obrigado à comunidade , sem a qual é impossível progredir! Um enorme obrigado , que me inspirou a me dedicar a DL, por seus conselhos inestimáveis e enorme profissionalismo!
Use modelos otimizados em produção!
Aurorai, llc
Fonte: habr.com
