Como treinar modelos de Deep Learning mais rápido

Alvaro Leandro Cavalcante Carneiro
Data Hackers
Published in
8 min readDec 28, 2020

--

Imagem de Herbert Aust por Pixabay

Quem já trabalhou com modelos de deep learning sabe que existe um grande gargalo na etapa de treinamento, sendo necessário horas ou dias de espera até que os parâmetros convirjam para o resultado que gostaríamos.

Esperar tanto tempo para testar cada nova hipótese que surge a cerca do problema pode ser inviável, limitando os nossos testes e possivelmente a solução encontrada. Iterar rápido é a chave para o sucesso.

Pensando nisso, toda nova técnica utilizada para tornar o treinamento desses modelos mais rápida pode ser extremamente valiosa em uma pipeline de aprendizado de máquina.

Nesse artigo, vou mostrar como a API de input de dados do TensorFlow pode ser utilizada para agilizar o treinamento de um modelo de deep learning com imagens.

Setup do Projeto

O projeto de teste será realizado na plataforma do Google Colaboratory, dessa forma, nenhuma biblioteca adicional precisará ser instalada na máquina e você conseguirá reproduzir os resultados de forma fácil.

Iremos trabalhar em um problema de Deep Learning com imagens, mais especificamente na classificação de palavras com Linguagem Brasileira de Sinais (LIBRAS).

Pipeline de Input de Dados do Tensorflow

O Tensorflow, desde a sua versão 2.0, recomenda o uso da sua API de entrada de dados para alimentar os modelos de Deep Learning a serem treinados. O motivo disso é simples: Eficiência.

A razão de ter uma estrutura de entrada de dados eficiente é bastante intuitiva:

Quanto melhor o hardware responsável por fazer os cálculos dos parâmetros do modelo (uma GPU moderna ou uma TPU por exemplo) mais o gargalo se concentra no input de dados.

Além disso, um conjunto de dados muito grande, onde geralmente é necessário carregar mais de 100 imagens na memória por iteração, também será prejudicado por uma entrada ineficiente desses dados ao modelo que está sendo treinado.

Em cada época do treinamento (ou melhor, em cada batch que compõe a época) precisamos abrir e ler as imagens, realizar operações de aumento de dados (rotação, translação, cisalhamento e etc) e disponibilizar essas imagens para que o modelo seja treinado, criando um fluxo parecido com isso:

Fonte: https://www.tensorflow.org/guide/data_performance. O “map” diz respeito a operação de aumento de dados.

Conforme evidenciado nesse tutorial, uma pipeline de input de dados padrão não possui uma paralelização das etapas que são realizadas, fazendo com que a execução de uma delas trave as próximas, gerando o efeito observado acima.

O segredo da pipeline de dados do TensorFlow é simplesmente tornar esse fluxo o mais paralelo possível, fazendo com que os dados da próxima época sejam carregados enquanto o treinamento da época atual ainda está acontecendo. Isso gera um resultado parecido com a imagem abaixo:

Fonte: https://www.tensorflow.org/guide/data_performance. O “map” diz respeito a operação de aumento de dados.

Keras como pipeline de Input de dados

A API do Keras costuma ser bastante utilizada devido a sua simplicidade e robustez, sendo possível criar modelos com ótimo desempenho de forma rápida. Entre uma dessas facilidades existem os métodos Flow e o ImageDataGenerator, utilizados respectivamente para carregar as imagens do diretório e aplicar transformações para o data augmentation.

Embora muito práticas de se utilizar, irei mostrar abaixo por que a facilidade na criação da estrutura pode não se pagar devido ao tempo extra requerido no treinamento.

Execução dos testes

Meu conjunto de dados não é muito extenso, mas serve como um exemplo. São 4923 imagens de treinamento e 1223 de teste. Obviamente que todos os hiperparâmetros se mantiveram os mesmos durante os testes, mudando apenas a forma como os dados foram inputados ao modelo, são eles:

  • Batch Size: 64
  • Épocas: 10
  • Resolução: 331x331 pixels
  • Modelo base: Xception (para transferência de aprendizado)

A GPU utilizada também foi a mesma, uma Tesla T4, para não haver diferenças relativas ao hardware.

A primeira execução foi sem nenhum tipo de aumento de dados, sendo realizado apenas uma normalização dos valores dos pixels no intervalo de 0 a 1 bem como o redimensionamento de 1280x720 para 331x331 pixels. Os resultados são mostrados abaixo:

Comparação entre o tempo de treinamento sem aumento de dados.

A diferença no desempenho das duas técnicas é notável, o TensorFlow data consegue economizar mais de 6 minutos em tempo de treinamento. Essa diferença tende a aumentar em conjunto de dados maiores ou se o treinamento fosse realizado por mais épocas.

Feito isso, vamos para um exemplo mais parecido com o que enfrentamos no mundo real, utilizando aumento de dados. Nesse caso, optei por utilizar 5 técnicas que achei melhor se adaptar ao meu conjunto de dados, sendo elas: Rotação horizontal, Zoom, Translação, Ajuste de contraste e rotações aleatórias no sentido horário. Todas essas operações envolvem cálculos matemáticos aplicados em matrizes, o que sem dúvida gera uma necessidade maior de processamento. Os resultados obtidos podem ser visualizados abaixo:

Comparação entre o tempo de treinamento com aumento de dados

Assim como esperado, o TensorFlow se manteve com a vantagem de tempo, processando as operações de aumento de dados enquanto a GPU continuou ocupada treinando o modelo.

Indo um passo além com TF Records

Seguindo as recomendações do TensorFlow, podemos ver que utilizar TF records também é considerado uma boa prática para treinar os modelos.

TF records são arquivos binários utilizados por padrão em algumas pipelines do TensorFlow, como por exemplo no TF Extended. A ideia é que com esses arquivos, conseguimos armazenar, carregar e transmitir nosso conjunto de dados de forma mais eficiente, aumentando ainda mais o ganho de desempenho.

Como transformar seu conjunto de dados em um conjunto de TF records foge do escopo desse artigo, mas vou trazer um tutorial sobre isso em breve.

Ao final dessa transformação, ao invés de possuir um conjunto de milhares de imagens, possuo apenas 10 arquivos binários no formato .record com aproximadamente 230 MB cada um, seguindo as recomendações do próprio TensorFlow.

Utilizar esses arquivos binários também exige algumas pequenas modificações na forma como os arquivos são carregados na pipeline, iremos ver mais detalhes a frente.

Ao executar o treinamento com aumento de dados, o modelo apresentou o seguinte desempenho:

Desempenho do TensorFlow com uso de imagens e TF records.

Como é possível observar, existe uma vantagem de performance ao adotar esse formato de dados no treinamento. Além disso, com os TF records é possível utilizar o serviço de TPU’s do Google Colab, garantindo um tempo de treinamento ainda menor.

Como isso funciona?

Agora que já consegui provar que de fato é uma técnica vantajosa, vamos ver um pouco de código e tentar entender como o TensorFlow faz essa “mágica” de acelerar o treinamento.

Em relação ao Keras, o único código que precisamos é esse:

Lendo e aumentado os dados com o Keras

Apenas com essas poucas linhas de código é realizada a leitura das imagens do conjunto, redimensionamento e aplicação das técnicas de aumento de dados, bastante simples certo?

No TF data, por outro lado, cada etapa do processo precisa ser programada individualmente, visando sempre extrair o máximo de desempenho de cada uma delas. Vamos ver cada uma delas abaixo em detalhes.

Lendo os dados no formato TF Record

O seguinte código pode ser utilizado para ler os arquivos em formato TF record:

Lendo e carregando o conjunto de dados com TF data.

Tudo começa no método load_dataset, que recebe uma lista contendo o caminho de todos os arquivos records de treinamento e teste em cada iteração. Feito isso, os arquivos binários são lidos e decodificados na memória, no formato que desejamos. No caso das imagens, por exemplo, é preciso normalizá-las entre 0 e 1 além de redimensioná-las para a resolução de interesse.

Repare que todo esse processo é realizado de forma paralela, por isso utilizamos o método map() acompanhado do num_parellel_reads, que distribui a tarefa pelos núcleos disponíveis da CPU. Você pode definir manualmente o número de núcleos a serem utilizados dependendo da configuração do seu computador, porém o TensorFlow pode lidar com isso automaticamente utilizando o AUTOTUNE (essa variável deve ser definida dessa forma AUTOTUNE = tf.data.experimental.AUTOTUNE).

Lendo os dados no formato de imagens

Caso você não queira converter seu conjunto de dados em TF Records também é possível utilizar o TF data, através o seguinte código:

lendo os dados para um conjunto em formato de imagens

O processo aqui também é bastante simples, sendo necessário listar todas as imagens na pasta de treinamento e teste e lendo-as de forma paralelizada, além de normalizá-las entre 0 e 1 e redimensioná-las para resolução desejada.

Configurando o comportamento da entrada de dados

Após a leitura dos arquivos, é necessário configurar a forma como esses dados serão “injetados” no modelo. Para isso, o seguinte método foi utilizado:

Configurando a entrada de dados

Logo na primeira linha, estamos definindo a utilização do cache. Com essa opção, todo nosso conjunto (de aproximadamente 2,5 GB no meu caso) será carregado diretamente na memória RAM. Essa opção é definitivamente a mais eficiente em termos de velocidade de treinamento, uma vez que acessar dados presentes na RAM do computador é muito mais rápido do que utilizar o disco rígido, porém, se o seu conjunto de dados for maior que o total de memória disponível essa linha deve ser comentada para evitar erros de falta de memória.

Em seguida, estamos definindo que o processo de carregamento de dados será repetido intermitentemente (com o ds.repeat()), uma vez que o controle de quando o input de dados será interrompido é feito pelo método fit() do modelo (baseado no número de épocas escolhidas).

Na sequência, selecionamos um batch de imagens (ds.batch()) de tamanho definido pelo usuário e, caso seja o conjunto de treinamento, aplicamos a técnica de aumento de dados. Para verificar em detalhes como é realizado o aumento de dados nesse caso, sugiro verificar o meu notebook.

Depois disso, embaralhamos os dados para não gerar nenhum viés ordinal no modelo com o método suffle(). Por fim, utilizamos o método prefetch(), que é responsável por carregar a próxima época de dados antes que a anterior finalize, garantindo que todo o potencial do input de dados seja aproveitado.

Caso tenha dúvidas adicionais no funcionamento do processo mostrado aqui, sugiro conferir a página oficial onde os processos são explicados em detalhes.

Conclusões

Reduzir o tempo de espera no treinamento de um modelo é sem dúvida um grande passo para se atingir bons resultados, principalmente em conjunto de dados maiores. Muita das vezes, pensamos que a única solução para isso é possuir dispositivos cada vez mais poderosos computacionalmente ou algoritmos super otimizados, porém, a demora no treinamento pode ser causada por motivos como o tempo de leitura e preprocessamento desses dados.

Pensando nisso, saber criar uma pipeline eficiente de entrada de dados pode ser bastante valioso, e é exatamente isso que o TensorFlow nos permite fazer com apenas algumas linhas de código. Com isso, conseguimos atingir um desempenho maior do que utilizando outros métodos de entrada de dados, o que certamente vai te ajudar ao treinar seus próprios modelos de Deep Learning.

--

--

Alvaro Leandro Cavalcante Carneiro
Data Hackers

MSc. Computer Science | Data Engineer. I write about artificial intelligence, deep learning and programming.