Acelere seus projetos de Deep Learning com Redes Neurais pré-treinadas

Nos últimos anos, muitas aplicações de Machine Learning relacionadas a reconhecimento de imagens ficaram em evidência. Seja para criar os mecanismos de visão dos veículos autônomos, detectar anomalias em imagens médicas ou até mesmo diferenciar raças de cachorros, os classificadores de imagens são construídos sobre a arquitetura de Redes Neurais Convolucionais (Convolutional Neural Networks, ou CNNs para os mais íntimos).

Carros autônomos, imagens médicas e classificação de raças de cães: todas elas usam CNNs.

Essas CNNs existem desde a década de 1980, porém só se tornaram populares em 2012, com uma arquitetura chamada AlexNet. Desde então, a cada ano são descobertas novas arquiteturas cada vez mais poderosas e/ou mais rápidas.

Diagrama da CNN AlexNet.

Esse recente interesse nessa arquitetura de rede só se tornou possível devido ao poder computacional existente nos dias de hoje. Seria impossível utilizar uma CNN em 1980. Para se ter uma ideia da ordem de grandeza dos avanços, o supercomputador Cray X-MP/1 (lançado em 1983 com custo de 10 milhões de dólares) tinha seu poder computacional medido em 200 milhões de FLOPS (floating-point operations per second), enquanto uma GPU Nvidia RTX 2080Ti (lançada em setembro de 2018, ao custo de 1000 dólares) é capaz de realizar 13.45 trilhões de FLOPS. É um ganho de 62.250 vezes no poder computacional, a um preço 10.000 vezes mais acessível .

A imagem à esquerda mostra o supercomputador Cray X-MP/1. Na direita, temos a Nvidia RTX 2080Ti.

Apesar de termos todo esse poder computacional disponível atualmente, treinar uma CNN pode ser algo demorado. Uma arquitetura de rede muito eficiente descoberta em 2017, chamada de Xception, precisou de 60 GPUs Nvidia K80 em paralelo para conseguir ser treinada no dataset Imagenet a uma taxa de 28 steps por segundo. Treinar redes assim muito frequentemente pode se tornar algo problemático nos ambientes de produção que precisam de deployments ágeis.

Mas se alguém já gastou horas e horas (além de muita energia elétrica) treinando essas redes neurais sofisticadas, por que eu tenho que treinar tudo de novo, desde o começo? Não dá para aproveitar alguma coisa?

Sim, é possível aproveitar uma rede já treinada. Podemos aproveitar os pesos de uma rede já treinada no dataset Imagenet, por exemplo, para criarmos um classificador de imagens de alimentos saudáveis. É importante observar que o Imagenet não tem uma categoria de "alimentos saudáveis", porém podemos treinar essa nossa nova rede muito mais rapidamente usando a rede pré-treinada como ponto de partida.

Isso se deve ao fato de que as primeiras camadas das CNNs aprendem a reconhecer elementos básicos das imagens, tais como bordas, cantos, formatos arredondados e outras formas geométricas básicas, além de constatações básicas sobre cores.

Representações de filtros de camadas iniciais de uma CNN.

Quando utilizamos redes pré-treinadas para treinar em cima de novas categorias, essas camadas iniciais são pouco modificadas, pois representam elementos básicos presentes em qualquer tipo de imagem. Assim, o treinamento fica muito mais rápido.

Nas próxima seção, vamos mostrar como isso funciona na prática.


Criando um classificador de animais de estimação com redes pré-treinadas

Para mostrar que uma rede neural pode ser treinada num tempo muito menor com os pesos de uma rede pré-treinada, vamos desenvolver um modelo capaz de diferenciar imagens de cães, gatos e pássaros. E para ficar livre de qualquer enviesamento, usaremos o dataset Open Images para treinar a rede, que foi pré-treinada com o dataset Imagenet.

Vamos criar um classificador que diferencie as 3 classes mostradas acima.

O código pode ser acessado neste repositório: https://github.com/adrianodennanni/pre-trained-nn-benchmark

Primeiramente, é feito o download de todas as imagens das três categorias para diretórios distintos, divididas em treino, validação e teste. Para cada uma das categorias, foram obtidas as seguintes quantidades de imagens:

  • Treino:
    53,137 files in directory ./train/cat
    89,369 files in directory ./train/dog
    105,962 files in directory ./train/bird
  • Validação:
    303 files in directory ./validation/cat
    1,480 files in directory ./validation/dog
    1,052 files in directory ./validation/bird
  • Teste:
    907 files in directory ./test/cat
    4,491 files in directory ./test/dog
    3,222 files in directory ./test/bird

Apesar de termos poucas imagens de validação, a quantidade deve ser suficiente para mostrar as diferenças das redes pré-treinadas.

Com os dados obtidos, podemos começar desenvolver nosso classificador. Ele será baseado na arquitetura Xception, que é muito eficiente.

Usaremos o Keras para o desenvolvimento, por oferecer a rede Xception pré-treinada com muita facilidade. Para declarar o modelo, basta o seguinte código:

import tensorflow as tf
# Para usar a rede pré treinada, weights deve ser 'imagenet'
# Para treinar a rede com pesos aleatórios, weights deve ser None
weights = 'imagenet'
# São 3 classes no nosso problema
n_classes = 3
# Tamanhos para o qual as imagens devem ser redimensionadas ao entrar na rede
shape = [100, 100, 3]
trained_model = tf.keras.applications.xception.Xception(
include_top=False,
weights=weights,
input_shape=shape,
pooling='max'
)
model = tf.keras.Sequential()
model.add(trained_model)
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(n_classes, activation='softmax'))

Simples, não concorda? A última camada do tipo Dense foi adicionada para a rede responder os scores das 3 classes que desejamos obter. O parâmetro include_top presente na declaração da rede Xception existe para podermos remover a primeira camada da rede, que originalmente só aceitava imagens do tamanho 299x299.

Após treinar por 30 épocas com o otimizador Adam, tanto com a rede pré-treinada e com a rede de inicialização aleatória, foram obtidos os seguintes resultados:

Dataset de treino

Comparação da evolução da taxa de acerto para o modelo classificador de pets (dataset de treino).
Comparação da evolução do valor da função de perda para o modelo classificador de pets (dataset de treino).

Dataset de validação

Comparação da evolução da taxa de acerto para o modelo classificador de pets (dataset de validação).
Comparação da evolução do valor da função de perda para o modelo classificador de pets (dataset de validação).

Dataset de teste

  • Rede pré-treinada:
    Accuracy: 0.9596
    Loss: 0,1184
  • Rede com pesos aleatórios:
    Accuracy: 0.9366
    Loss: 0.2408

Conclusões sobre o classificador de animais de estimação

A rede pré-treinada estabiliza sua acurácia em validação após 8 épocas. A rede com inicialização aleatória leva 17 épocas para atingir a estabilidade. É um excelente exemplo de como redes pré-treinadas agilizam muito o treinamento de novos modelos. Também podemos concluir que neste caso a rede pré-treinada apresentou uma acurácia ligeiramente melhor após estabilizar durante o treinamento.

No dataset de treino, as duas redes convergem na mesma direção. Nenhuma delas evitou o overfit.

Podemos notar algo curioso acontecendo com a rede pré-treinada, na função de perda do dataset de validação. Após atingir seu pico de acurácia, a perda começou a aumentar. Isso pode ser um indício de que a rede pré-treinada, além de aprender mais rapidamente, também caminha para o overfit mais rapidamente. Para evitar que isso estrague a sua rede, é recomendado acompanhar bem o processo de treinamento da rede, utilizando técnicas como early stopping para evitar o overfit.

Por fim, a rede pré-treinada apresentou uma precisão 2.3% melhor do que a rede com pesos aleatórios no dataset de teste.


Criando um solucionador de CAPTCHAs com redes pré-treinadas

No exemplo acima vimos como podemos acelerar o treinamento de CNNs ao utilizar redes neurais com pesos de treinamentos anteriores, realizados no dataset Imagenet. Aprendemos também que o treinamento sobre uma rede pré-treinada é mais rápido devido às camadas iniciais da rede serem pouco modificadas, por aproveitar semelhanças em todas as imagens do dataset.

Essas semelhanças entre todas as imagens do dataset se devem ao fato de que o Imagenet utiliza apenas fotografias no seu repositório. Assim, coisas muito abstratas ou bizarras, que fogem do padrão de uma fotografia normal, ficam excluídas do aprendizado da rede.

Então surge a pergunta: será que teremos ganho no aprendizado de desenhos bizarros, como CAPTCHAs?

Imagens reais apresentam regras de formação mais “naturais” quando comparadas as CAPTCHAs.

Vamos tirar essa dúvida a limpo. Vamos gerar 200.000 exemplos de CAPTCHAs e usá-los para treinar nosso novo modelo. Usamos as seguintes quantidades de captchas:

  • Treino: 200.000 exemplos
  • Validação: 5.000 exemplos
  • Teste: 5.000 exemplos

Mais uma vez, usaremos o Keras para realizar esse teste. A diferença desta vez é que usaremos um classificador multi-task para identificar cada caracter do CAPTCHA (6 caracteres, 36 possibilidades em cada), sobre o núcleo da rede Xception pré-treinada.

import tensorflow as tf
# Para usar a rede pré treinada, weights deve ser 'imagenet'
# Para treinar a rede com pesos aleatórios, weights deve ser None
weights = 'imagenet'
# São 36 caracteres no nosso problema
n_classes = 36
# Tamanho de cada CAPTCHA
shape = [160, 80, 3]
trained_model = tf.keras.applications.xception.Xception(
include_top=False,
weights=weights,
input_shape=shape,
pooling='max')
c1 = tf.keras.layers.Dense(n_classes, activation='softmax')(trained_model.output)
c2 = tf.keras.layers.Dense(n_classes, activation='softmax')(trained_model.output)
c3 = tf.keras.layers.Dense(n_classes, activation='softmax')(trained_model.output)
c4 = tf.keras.layers.Dense(n_classes, activation='softmax')(trained_model.output)
c5 = tf.keras.layers.Dense(n_classes, activation='softmax')(trained_model.output)
c6 = tf.keras.layers.Dense(n_classes, activation='softmax')(trained_model.output)
model = tf.keras.Model(inputs=trained_model.input, outputs=[c1, c2, c3, c4, c5, c6])

Desta vez, a rede tem 6 ramificações, uma para cada letra do CAPTCHA. O Keras se encarrega de calcular a função de perda neste caso.

Após treinar por 30 épocas com o otimizador Adam, tanto com a rede pré-treinada e com a rede de inicialização aleatória, foram obtidos os seguintes resultados:

Dataset de treino

Comparação da evolução da taxa de acerto para o modelo que resolve CAPTCHAs (dataset de treino).
Comparação da evolução do valor da função de perda para o modelo que resolve CAPTCHAs (dataset de treino).

Dataset de validação

Comparação da evolução da taxa de acerto para o modelo que resolve CAPTCHAs (dataset de validação).
Comparação da evolução do valor da função de perda para o modelo que resolve CAPTCHAs (dataset de validação).

Dataset de teste

  • Rede pré-treinada:
    Accuracy: 0.9982
    Loss: 0.0479
  • Rede com pesos aleatórios:
    Accuracy: 0.9496
    Loss: 0.4323

Conclusões sobre o modelo que resolve CAPTCHAs

O uso de uma rede neural pré-treinada não só acelera o treinamento, mas também chega em um resultado melhor em poucas épocas. Isso usando uma rede pré-treinada num dataset de fotografias.

Porém, vale observar que a diferença na velocidade de convergência neste caso é menor quando comparado com o exemplo do classificador de animais de estimação. Isso é decorrente da diferença entre a natureza das duas distribuições de imagens, conforme explicado anteriormente.


Considerações finais

Utilizar redes neurais pré-treinadas pode ajudar muito seus modelos a atingir bons resultados em pouco tempo. Não é necessário reinventar a roda toda vez que quisermos fazer um classificador de imagens.