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 20 mil 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.

redes neurais convolucionais python

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 *

2 Replies to “Redes Neurais Convolucionais com Python”

Luis Antônio

Muito bom o artigo, rico de informações e com os códigos usados

Carlos Melo

Muito obrigado pelo feedback! Espero que aproveite bem o conteúdo do site. Abraços!

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

Junte-se a mais de 20 mil pessoas

FAQ e Curso
Curso Data Science
Carlos Melo