fbpx

Deep Learning

Redes Neurais Convolucionais com Python

Carlos Melo
Escrito por Carlos Melo em 27/06/2019
Redes Neurais Convolucionais com Python
Junte-se a mais de 3.500 pessoas

Entre para nossa lista e receba conteúdos exclusivos e com prioridade

No post de hoje, irei mostrar como implementar sua primeira Rede Neural Convolucional (Convolutional Neural Network – CNN), inspirada na conhecida arquitetura neural LeNet-5, e aplicá-la ao dataset MNIST.

Este é um tutorial focado na implementação da CNN – ou seja, estou assumindo que você tem familiaridade com a área. Por esse motivo, não entrarei em detalhes teóricos e conceitos como funções de ativação, Fully-Connected Layer, Pooling Layer, entre outros tantos.

[optin-cat id=20143]

Recomendo àqueles que estão começando ou que querem aprofundar mais nos conceitos, darem uma olhada neste artigo do site, onde eu recomendo os 3 melhores cursos online de deep learning (na minha opinião).

Todo o código implementado aqui está disponível neste repositório no Github.

Conjunto de dados MNIST

No último post, usamos o muito conhecido conjunto de dados MNIST, que contém milhares de imagens manuscritas dos dígitos de 0-9, e criamos uma rede neural para classificá-las.

Resumidamente, cada imagem do dataset possui 28 X 28 pixels, com os valores dos pixels em escala de cinza. Como grande vantagem, os digitos já estão normalizados (tamanho) e centralizados.

Figura 1 – Amostra do conjunto de dados MNIST.

Como em uma imagem em escala de cinza o valor de cada pixel é a única amostra do espaço de cores, esse valor irá variar no intervalo [0, 255], indicando a intensidade desse pixel. Para maiores detalhes sobre o dataset, veja o artigo “Redes Neurais Multicamadas com Python e Keras”.

Redes Neurais Convolucionais são um tipo de redes neurais que vêm sendo aplicadas com muito sucesso à problemas de Computer Vision.

Redes Neurais Convolucionais

Como eu disse na introdução, a nossa primeira implementação de uma CNN vai ser baseada na arquitetura da LeNet-5, primeira CNN implementada e testada com êxito!

A escolha dessa arquitetura (apesar da idade dela) é por que essa CNN é muito didática! Pequena e fácil de entender, ainda sim consegue ótimos resultados em problemas como o MNIST. Implementar a LeNet-5 é como se fosse o Hello, World! das CNN.

Arquitetura LeNet-5

Proposta por LeCun (1998) em seu paper Gradient-Based Learning Applied to Document. Recognition, a LeNet-5 tem foco no reconhecimento de dígitos, e foi pensada em reconhecer os números de CEPs em correspondências.

Figura 2 – Arquitetura da LeNet-5, uma Rede Neural Convolucional (CNN) para reconhecimento de dígitos. Fonte: LeCun (1998).

A Figura 2 é a imagem original do paper de LeCun. Em uma análise rápida, vemos que a imagem passada como input não é achatada (flatten), mas é passada preservando as suas dimensões. Isso é mandatório, para manter a relação espacial entre seus pixels  -uma imagem achatada perderia essa informação importante.

Também dá para ver que existem três tipos de layers:

  1. Convolutional Layers (CONV);
  2. Pooling Layers (POOL);
  3. Fully-Connected Layers (FC).

Caso você não esteja acostumado com a representação gráfica usada na Figura 2, gosto muito da representação usada por Andrew Ng em um dos slides do curso Convolutional Neural Networks (Coursera):

Figura 3 – Arquitetura da LeNet-5 detalhada. Fonte: Andrew Ng.

Resumidamente, a arquitetura da LeNet-5 é composta por uma sequência com as seguintes camadas:

  • CNN é composta por um conjunto de 6 filtros (5×5), stride=1.
  • POOL (2×2), stride=2, para reduzir o tamanho espacial das matrizes resultantes.
  • CNN (5×5) com 16 filtros e stride=1.
  • POOL (2×2), stride=2.
    • Os mapas de características são achatados (flatten), formando 400 nós (5x5x16) para a próxima camanda FC.
  • FC com 120 nós.
  • FC com 84 nós.

Se você pegar para ler o artigo, vai reparar que as funções de ativação referenciadas foram SIGMOID e TANH, entretanto eu vou usar a ativação RELU, que nos dá uma precisão bem melhor!  Outro observação, na época que o paper foi escrito, usava-se muito mais o average pooling do que max pooling. No modelo que vamos implementar, vou utilizar o max pooling.

Implementando uma CNN com Python + Keras

Antes de entrar no código propriamente dito, veja como o projeto foi dividio. Para deixar o código organizado – afinal, não tem nada pior que escrever tudo em um único arquivo! -, criei um módulo cnn contendo a classe LeNet-5. Já a aplicação da CNN ao dataset MNIST está em um arquivo separado  lenet5_mnist.py.  Segue a estrutura do projeto:

Ao estruturar um projeto em módulos e sub-módulos, você permite que estes sejam mais fáceis de escalar, além de adotar uma boa prática de programação e deixar tudo mais legível para os outros.

Escrevendo a classe LeNet5

Dentro do módulo cnn, crie um arquivo __init__.py para implementar a classe LeNet5. Importe os pacotes necessários e declare a classe:

Vou definir o método estático build (usando o decorador @staticmethod )dentro da classe LeNet5 , assim não será necessário criar uma instância para chamar o método. São fornecidos ao método os argumentos referentes ao tamanho da imagem, quantidade de canais e classes, e esse empilhará os layers da CNN, retornando o modelo.

A CNN é instanciada pela classe Sequential, e cada layer é adicionado na sequência do outro, seguindo a arquitetura já detalhada acima.

Não esqueça que para executar a Linha 48 é preciso antes transformar o mapa de característica em um vetor com 400 neurônios executando Flatten(). Na Linha 49, o FC com 84 nós ira se conectar a cada um dos 400 nós que “achatamos”.

Na última camada, é adicionada uma camada do tipo FC de tamanho 10, que é exatamente o número de classes do problema. Usamos a função de ativação softmax pois queremos como output a probabilidade associada a cada classe.

De maneira resumida, a arquitetura da CNN ficou assim:

Só isso! A classe LeNet5 está 100% implementada, pronta para ser usada não apenas no MNIST, mas qualquer outro problema de classificação de imagens.

Aplicando a CNN no MNIST

No diretório raíz do seu projeto, crie um arquivo lenet5_mnist.py, importe as bibliotecas que serão usadas, e não esqueça da recém-criada classe LeNet5. Exatamente como fizemos no artigo anterior, importe e normalize o dataset MNIST:

Lembra da Figura 2, que mostra que o input da CNN deve ser uma imagem com largura e altura? Então, quando importamos o dataset diretamente pelo sklearn, ele reduziu automaticamente as dimensões das imagens, transformando-as em um vetores com 784 valores.

Para a CNN funcionar adequadamente, temos que converter esse vetor em um array do tipo (28x28x1). Isso pode ser feito facilmente pelo método reshape:

Um leitor atento vai perceber que tenho duas situações possíveis, minha figura pode ser redimensionada para o shape (28x28x1) ou para (1x28x28). Isso é por que o Keras por usar tanto Theano quanto Tensorflow no backend. Como na comunidade Theano usa-se o ordenamento channels first e na comunidade Tensorflow é adotado channels last, é muito importante fazer essa verificação para garantir a compatibilidade da aplicação.

Para saber o que você está usando no backend, é só dar uma olhada no arquivo de configuração ~/.keras/keras.json:

Próximo passo é dividir o conjunto de dados entre treino (75%) e teste (25%), usando o método train_test_split, e transformar os números interior dos labels de trainYtestY para o formato de vetor binário, com auxílio do método to_categorical:

Treinando a CNN

Classe LeNet5 implementada, dados de entrada tratados corretamente, e agora é hora de compilar a CNN e treiná-la com os dígitos do MNIST:

Na Linha 39, ao passar os argumentos para o método estático LeNet5.build, a Rede Convolucional LeNet-5 é atribuída à variável model. Eu compilo o modelo na Linha 40 usando o algoritmo Stochastic Gradient Descent (SGD) para otimização e loss function igual a categorical_crossentropy, dado que são múltiplas classes no output.

Na Linha 45 é iniciado o treinamento da Rede Neural Convolucional, processo que pode demorar um pouco mais caso você não esteja usando nenhuma GPU.

Avaliando a CNN

Para avaliar o desempenho da nossa CNN,  chamamos o método model.predict para gerar previsões em cima do dataset de teste. O desafio do modelo é fazer a previsão para as 17.500 imagens que compõe o conjunto de teste, atribuindo um label de 0-9 para cada uma delas:

Por fim, após o relatório de desempenho obtido, vamos querer plotar a accuracy e loss ao longo das iterações:

Executando a CNN LeNet5 MNIST

Eu rodei o script em uma instância p2.xlarge da AWS, e demorou cerca de 1 minuto para que a CNN fosse treinada. Essa instância P2 utiliza 1 GPU NVIDIA K80 e 4 vCPUs, ao custo de $0,90/hora. No entanto, o nosso código roda normalmente em uma máquina sem GPU, pois é uma rede pequena processando um dataset também pequeno.

Para um comparativo, com GPU da AWS e epochs=20 demorou cerca de 60 segundos para treinar a LeNet-5. Já minha máquina sem GPU (apenas CPU), demorou 360 segundos.

Vá em frente e execute python lenet5_mnist.py :

Depois de todo esse trabalho, vamos dar uma olhada no desempenho da nossa primeira CNN e comparar com a rede neural simples que implementamos no post anterior.

Conseguimos uma precisão de 98% nas previsões feitas com a LeNet-5 treinada. Mesmo sendo uma arquitetura antiga (a primeira CNN implementada com sucesso) e sem fazer grandes alterações, ela bateu facilmente o desempenho da outra rede neural simples – que tinha conseguido 92% de precisão.

Desempenho da CNN LeNet-5 no dataset MNIST.

Por fim, o script forneceu o plot da accuracy e loss em função da epoch. Quando a gente treina um modelo de Deep Learning, o gráfico que se espera é basicamente desse tipo, training e validation loss com curvas bem similares, assim como ambas accuracy com comportamento similar – um padrão que indica que não há overfitting.

No entanto, esse tipo de gráfico é bem difícil de ser alcançado em problema mais complexos. O dataset MNIST foi amplamente pré-processado e normalizado – e a gente sabe que não é isso que nos espera no mundo real – e por isso gera um gráfico tão bonito assim. Ou seja, o pré-processamento do seu conjunto de dados é extremamente importante para o desempenho do algoritmo.

Resumo

A LeNet-5 é tida como uma rede shallow (rasa) quando comparada com as arquiteturas deep (profundas) modernas. Como vimos, ela possui apenas 4 camadas (2 CONV e 2 POOL), pouco para os padrões atuais. Hoje em dia, uma arquitetura classificada como estado-da-arte pode ultrapassar facilmente 100 camadas (como a ResNet).

Entretanto, vimos que mesmo essa estrutura simples de CNN foi capaz de atingir 98% de acurácia no dataset MNIST, tornando-a um ótimo exemplo inicial para implementação.

Espero que tenha gostado do artigo, e conseguido entender melhor a estrutura da clássica LeNet-5. No próximo post irei fazer um overview sobre os principais layers de uma rede convolucional.

Hey,

o que você achou deste conteúdo? Conte nos comentários.

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Entre para nossa lista e receba conteúdos exclusivos e com prioridade

Junte-se a mais de 3.500 pessoas

Comentários
    FAQ