Olá, pessoal. Tudo bem? Espero que sim.

Conforme prometi, vou concluir a série de três publicações sobre como organizar o seu projeto de machine learning e deep learning com Pytorch. Eu demorei a escrever este último post porque eu tenho trabalhado na área de TinyML (https://www.plugandplaytechcenter.com/resources/tinyml-making-smart-devices-tinier-ever/) e otimização de modelos para dispositivos com recursos limitados (resource-constrained devices). Aprendi C++… e tem sido isso. E não: eu não sabia C++. Eu trabalhei mais de dez anos com C# (e eu gosto dela).

Ah! Lembrando que o código completo está aqui oh: https://github.com/adrianosantospb/estrutura_base_pytorch. Uma coisa que eu gostaria de enfatizar aqui: se voc realizar todas essas melhorias em seu projeto, você melhorará consideravelmente a acurácia do seu modelo.

Vamos lá. Eu prometi no post passado que falaria sobre:

a) Como utilizar GPU no treinamento do nosso modelo;

A primeira coisa que você deverá fazer é a instalação do pacote apropriado (com suporte ao CUDA). No momento em que eu escrevo este post, o comando para a instalacao eh o seguinte:

conda install pytorch torchvision torchaudio cudatoolkit=11.0 -c pytorch

Para que o nosso exemplo anterior utilize a GPU no processo de treinamento, precisamos, apenas, fazer pequenas modificações em nosso código. A primeira coisa que você deve fazer é verificar se existe GPU em seu computador. Se sim, basta que você modifique os seguintes trechos de códigos:

GPU :)

b) Alteraremos o nosso modelo para um modelo de CNN;

Com base na estrutura que criamos nos projetos anteriores, fica fácil criar um modelo de CNN. Na verdade, em termos de estrutura, não vai mudar nada! Incrível, não é mesmo? Claro que estamos criando uma arquitetura simples, “puramente didática”, e que existem diversas outras técnicas que podem ser inseridas em nosso modelo. Mas não será o nosso caso de agora, tudo bem?

A minha ideia aqui é fazer com que você tenha uma noção de continuidade. Isso mesmo: continuidade. Em nossos primeiros exemplos, nós trabalhamos com organização estrutural do projeto, criamos um modelo com base em uma rede simples e o testamos. Agora estamos entrando no contexto de CNN.

Se você ainda não é familiarizado com CNN, eu o aconselho a dar uma olhada aqui (https://medium.com/neuronio-br/entendendo-redes-convolucionais-cnns-d10359f21184) . Eu até pensei em escrever sobre elas, apresentar toda a teoria, mas eu encontrei esse material aí e achei que foi bem escrito, didático, e eu vou focar em coisas que não encontro de forma didática.

O nosso modelo terá três camadas CNN e duas fully connected layer (FC). Simples, simples, simples. Mas você verá que, para o domínio explorado neste exemplo, ela apresentará resultados mais interessantes do que os exemplos anteriores. Utilizaremos a função de ativação ReLU (https://machinelearningmastery.com/rectified-linear-activation-function-for-deep-learning-neural-networks/) e a função de regularização Dropout (https://machinelearningmastery.com/dropout-for-regularizing-deep-neural-networks/). Na saída da nossa rede (devido, inclusive, ao domínio de nossa aplicação), utilizaremos a função log_softmax (https://pytorch.org/docs/stable/generated/torch.nn.LogSoftmax.html).

Voilà! Este é o resultado de nossas alterações:

Nosso novo modelo.

c) Otimizaremos alguns parâmetros do PyTorch;

Bem, existem alguns pequenos detalhes na codificação que poderão nos ajudar em termos de desempenho. O primeiro passo nós já demos no momento em que começamos a usar o poder de processamento da GPU em nosso código. Você deve ter percebido, inclusive, que o tempo de execução do código (treinamento e validação) diminuiu bastante em nosso exemplo atual, comparando-o aos anteriores.

Vamos conhecer mais alguns:

  1. Use workers no DataLoaders (https://medium.com/swlh/pytorch-dataset-dataloader-b50193dc9855)

Se você possui uma boa quantidade de memória em seu computador, você pode utilizar o recurso de workers. Sem dúvida você ganhará desempenho na etapa de treinamento do seu modelo. A proporção geralmente utilizada é de 4X o número de GPU do seu computador. Em termos de código, com Pytorch, você poderá utilizar esse recurso aqui:

Workers
  1. Pin memory (http://deeplearnphysics.org/Blog/2018-10-02-Pinning-data-to-GPU.html)

O recurso de pinned_memory coloca automaticamente os tensores de dados buscados na memória fixada, fazendo com que a transferência de dados para a GPU seja mais rápida.

  1. Mixed precision training

Em redes neurais, os cálculos são geralmente feitos em precisão de flutuantes (float) de 32 bits. Para reduzir o uso de memória foi pensada a abordagem de fazer a mesma coisa em meia-precisão (half), o que significa usar flutuadores de 16 bits com o uso de GPU. A ideia é simples: 16 ocupam metade do espaço na RAM que 32 e, em teoria, podem permitir o dobro do tamanho do seu modelo e o tamanho do lote. Porém existem alguns probleminhas com essa abordagem (https://en.wikipedia.org/wiki/Half-precision_floating-point_format) . Sendo assim, aconselha-se o uso de mixed precision training (https://pytorch.org/docs/stable/notes/amp_examples.html). Em nosso exemplo, eu apenas habilitei que o nosso modelo fosse treinado com precisão de 16. É um bom exercício melhorar esse processo, ok?

  1. Distributed Data Parallel

A ideia de executar paralelismo no processo de treinamento é algo que possibilita benefícios diretos em termos de treinamento. Porém isso funciona quando temos mais de uma GPU disponível. Pytorch implementa duas formas de implementacão DistributedDataParallel (https://pytorch.org/docs/stable/generated/torch.nn.parallel.DistributedDataParallel.html) e DataParallel (https://pytorch.org/docs/stable/generated/torch.nn.DataParallel.html). Correntemente, o uso do DataParallel é desencorajado.

  1. Transferência de informações de GPU para CPU e de CPU para GPU

Uma das atividades mais recorrentes no trabalho de modelos que utilizam GPU é a transferência de informações de GPU para CPU e de CPU para GPU. Essa operação é custosa e, a cada chamada de .item (), .cpu () ou .numpy (), realizamos a transferência da GPU para a CPU. Outra forma é quando criamos um array com numpy, por exemplo, e depois o transferimos para a GPU.

Idealmente, em termos de criação, a melhor alternativa é criar o array diretamente na GPU (a = torch.cuda.FloatTensor([1., 2.])). Quando tivermos que transferir o valor da GPU para a CPU, devemos utilizar a função .detach(). Esta função removerá quaisquer gráficos computacionais anexados a essa variável. Você poderá obter mais dicas aqui (https://medium.com/@attyuttam/5-gradient-derivative-related-pytorch-functions-8fd0e02f13c6).

d) Utilizando métricas de otimização para selecionarmos o melhor modelo.

Este tópico é de extrema importância. Inclusive, eu vou escrever um blog especificamente sobre este tema, incluindo algumas dicas práticas que eu desenvolvi no meu dia a dia como cientista de dados na área de visão computacional.

Porém, eu estava dando uma olhada sobre alguns materiais e encontrei duas fontes interessantes e ricas de informações sobre este tema; então, por agora, eu vou compartilhar esses dois links e deixo registrada a minha promessa de escrever sobre as dicas complementares a esses materiais, ok?

Os materiais são os seguintes:

a) O que não te contam sobre métricas de classificação binária: (https://medium.com/@arnaldog12/o-que-n%C3%A3o-te-contam-sobre-m%C3%A9tricas-de-classifica%C3%A7%C3%A3o-bin%C3%A1ria-d1834e385402);

b) Metrics for object detection: (https://github.com/rafaelpadilla/Object-Detection-Metrics)

Vale muito a pena a leitura desses materiais. Eu mesmo já os utilizei na preparação de aulas.

Bem, pessoal… eu acho que é isso. Tenho mais dois posts para compartilhar aqui ainda essa semana. Em breve novidades.

Abraço a todos

--

--

Adriano A. Santos

Senior Computer Vision Data Scientist at Conception Ro-Main (Quebec — CA). DSc in Computer Science. MTAC Brazil. https://github.com/adrianosantospb