Classificação de Imagens com Redes Neurais Convolucionais

Posted by Rafael Morais on October 26, 2020

Classificação de Imagens com Redes Neurais Convolucionais

Run in Google Colab View source on GitHub

Onde queremos chegar:

Nosso objetivo aqui é entender, de forma ilustrada, como podemos passar uma imagem para um modelo de Inteligência Artificial e receber classificação.

O diagnóstico de exames de Raios-X é um ótimo exemplo de classificação de imagem. Por exemplo, com a radiografia dos pulmões o médico pode identificar se o paciente possui ou não câncer. Um cientista de dados não tem o conhecimento médico, mas caso tenhamos uma grande base de dados com imagens de pulmões juntamente de seus diagnósticos, saudável ou não, podemos treinar um modelo que aprenda a classificá-los. Ajustado o modelo, podemos retornar a probabilidade de um exame de pulmão apresentar câncer com base em milhares de casos já classificados, isso poderia auxiliar nos prognósticos médicos.

O reconhecimento de imagem ou vídeo possuí diversas outras aplicações sendo uma das mais claras a categorização de fotos do Google Photos em sua galeria. Como podemos ver ele utiliza a classificação para agrupar e organizar as fotos em elementos em comum. Nem sempre o reconhecimento é perfeito e até por isso é comum aparecer uma mensagem do aplicativo solicitando para confirmar algumas fotos. Ao fazermos isto estamos melhorando o poder de classificação do modelo utilizado.

![Algumas das Classificações atribuidas pelo Photos](img/img1.jpg)

Exemplo de Classificação imagens na galeria do Google Photos

Neste texto vamos abordar como são representadas as imagens em dados para o modelo, as estruturas que compõe uma Rede Neural Convolucional e finalizamos com uma aplicação no conjunto de dados conhecido como CIFAR-10.

Todos os códigos ao longo do texto estão disponíveis no Google Colab e GitHub. Caso você possua uma conta Gmail vale muito acessar o Colab. Nele é possível executar códigos Python direto do navegador.

Trabalhando com Imagens

Uma forma útil de representar as imagens é utilizando matrizes compostas de porções de pixel, isso pode se dar em escala de cinzas, para imagens em preto e branco, ou escala RGB, para imagens coloridas. De forma bem simplificada, podemos entender o pixel como a menor unidade da imagem. Por exemplo, uma imagem de proporções 60x40 possuí 2400 pixels. Já o RGB ou Red, Green, Blue é um sistema que utiliza a combinação entre as cores vermelho, verde e azul para gerar todas as cores.

![](img/img2.gif)

Fonte: Getting The RGB Color Value Of An Image Pixel

A imagem gif acima ilustra os diferentes pixels sendo escolhidos, a cada clique a altera-se a coordenada do pixel (“Pixek Offset”) em termos de largura e altura ou linha e coluna se visualizarmos como uma matriz. Os valores RGB que cada pixel pode assumir variam entre 0 e 255 para o verde, vermelho e azul. Duas cores se destacam por serem os extremos da escala, são elas o preto, representado por {0,0,0} e o branco, representado por {250,250,250}. Para imagens em preto e branco a matriz possuirá apenas uma dimensão de cor, também chamado de canal. Já para imagens coloridas teremos 3 matrizes de mesma altura e largura, variando apenas o canal para uma das cores básicas. A seguir temos um exemplo de diferentes tonalidades de cores sendo representadas em escala RGB.

No código vemos como decompor uma imagem nos três canais de cores

Podemos decompor uma imagem colorida em três novas, uma para cada canal de cor. É interessante notar que do ponto de vista de informação as três dimensões são importantes para recriar a imagem como conhecemos. Se na imagem acima utilizássemos apenas o filtro azul teríamos dificuldade de enxergar os detalhes do cachorro. Caso a figura fosse toda representada em tons de preto e branco a mesma imagem seria apresentada nos três canais e tornando redundante manter as 3 dimensões.

Modelo

O problema de classificar uma imagem em categorias é tarefa para um modelo de aprendizado de máquina supervisionado de classificação. Isto implica na necessidade de haver um conjunto de imagens previamente classificadas para que possamos treinar e calibrar o modelo. Inclusive, para que o modelo consiga generalizar a classificação é necessário um conjunto grande de dados de cada uma das categorias do modelo.

Mais sobre tipos de modelos de Machine Learning em: Os Três Tipos de Aprendizado de Máquina

Redes Neurais Convolucionais - RNC (Convolutional Neural Network - CNN) é um caso de aprendizado profundo, em que temos a estrutura de rede neural com a presença de ao menos uma camada convolucional. Em outras palavras, assim como nas Redes Neurais tradicionais temos as diversas camadas ocultas entre os dados de entrada e saída, aqui temos essas camadas sucessivas com a introdução de duas novas operações: Convolução (Convolution) e Agrupamento (Pooling).

Mais sobre Redes Neurais: Uma Abordagem Intuitiva às Redes Neurais

Para ilustrar um típico modelo temos a seguir uma figura que apresenta uma RNC estruturada de pares de Convoluções e Agrupamentos seguido de duas camadas totalmente conectadas para finalmente ter a resposta em probabilidade em cada categoria:

Fonte: Resolvendo CAPTCHAs com Redes Neurais Convolucionais

Camada de Convolução

O que seria a convolução? Na matemática podemos resumir que é a transformação resultante de duas funções. No caso de redes neurais temos a transformação entre o filtro de convolução e a matriz de entrada.

O filtro de convolução, também chamado de kernel, é uma matriz quadrada que vai “varrer” todos toda a figura de entrada a passos (stride) fixos. Por exemplo, na imagem a seguir temos a aplicação do kernel de dimensões 3x3 à uma matriz de 5x5. O kernel multiplica elemento a elemento de cada submatriz 3x3 da imagem e soma seus valores, o que resulta na matriz de características de dimensões 3x3. Note também que cada submatriz é dada ao ir para a imediata próxima coluna ou linha, nosso passo aqui é de 1.

![](img/img5.gif)

Fonte: A Comprehensive Guide to Convolutional Neural Networks

Caso a imagem esteja em RGB, isto é, possua 3 dimensões, a convolução será aplicada a cada canal de cor e a matriz resultante é a soma dos 3 canais. Tanto imagens coloridas quanto em preto e branco apresentarão as mesmas dimensões de saída.

Os valores de cada elemento do Kernel são definidos durante a etapa de treinamento do modelo. Alguns Kernels são conhecidos por resultarem em imagens com realce de borda, aumento do contraste, efeito desfoque na imagem e etc. Um site incrível para visualizar a sensibilidade da imagem resultante com diferentes parâmetros de kernel é este aqui.

Na construção do modelo nos precisamos especificar três parâmetros para cada camada convolucional. Primeiramente, definimos a quantidade de filtros que devem ser utilizados, ou seja, quantos diferentes kernels serão utilizados. Em seguida, especificamos a dimensão de cada kernel, por exemplo 2x2, 3x3, 5x5 e etc. Finalmente, o tamanho do passo que deve ser considerado na operação de convolução, o mais usual é utilizar passos de tamanho 1.

Vale ressaltar que após a operação de convolução a imagem terá uma dimensão menor do que a de entrada. Isso se dá pela relação entre a dimensão do kernel e tamanho do passo. Na especificação do modelo podemos especificar a opção de preenchimento (Padding). Com essa opção o modelo vai incluir uma borda de forma que a convolução resulte em dimensões iguais a de entrada.

\[O = \frac{W - K}{S} +1\]

Em que:

O: representa o tamanho de saída, altura/lagura;

W: é a dimensão de entrada, altura/largura;

K: tamanho do kernel;

S: tamanho do passo.

No exemplo da figura temos W=5, K=3 e S=1 resultando em O=3 tanto para altura quanto para largura. Portanto, aplicada a convolução sem a opção de preenchimento teremos uma imagem 3x3 como resultado desta operação.

Camada de Agrupamento

O próprio nome já nos dá uma intuição do que acontecerá aqui. Similar a camada de convolução, nessa camada vamos varrer matrizes de entrada considerando um tamanho e passo fixo, com a diferença que ao invés da convolução agruparemos os dados pelo valor máximo (Max Pooling) ou médio (Agerage Pooling).

Esse método de agrupamento será responsável por destacar as informações dominantes, ao passo que também reduz o ruído das imagens. A seguir um exemplo dos dois métodos de agrupamento considerando submatrizes de 2x2 e passo de tamanho 2.

![](img/img6.jpg)

Fonte: A Comprehensive Guide to Convolutional Neural Networks

A dimensão resultante dessa operação é expressa pela mesma relação apresentada na seção sobre convolução. Nesse caso, os parâmetros foram W=4, K=2 e S=2 resultando em O=2 para altura e largura.

Vale destacar que o mais usual é utilizar o tamanho do kernel igual ao tamanho da matriz, dessa forma as submatrizes não possuem elementos em comum e nem deixamos elementos de fora da operação.

O tamanho do kernel influenciará muito na imagem de saída e consequentemente na resolução. Na figura abaixo originalmente tínhamos 384x512 de resolução. Com o filtro 2x2 a imagem de saída terá metade do tamanho original e sem grandes perdas de informação. Em um caso mais extremo, utilizando um filtro 15x15 a imagem de saída será bem menor, mas ao custo da qualidade da imagem, ainda que a forma e cores lembrem a imagem original.

![]img/img7.png)

Aplicação: CIFAR-10

O conjunto de dados conhecido como CIFAR-10 é muito popular quando começamos a estudar RNC. Nossa aplicação é baseada no tutorial do Tensorflow com essa base. Vamos utilizar o pacote Keras com a estrutura Sequential para organizar as camadas.

import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt

Download e Preparação de dados

O dataset CIFAR10 possui 60.000 imagens coloridas classificadas em 10 categorias balanceadas, isto é, cada categoria possui 6 mil imagens. Os dados estão divididos em 50.000 imagens para treinamento e 10.000 para testes.

Vale ressaltar que as classes são mutualmente exclusivas, não há imagens com mais de uma categoria presente. Pela documentação sabemos que essas imagens devem ser classificadas em uma das seguintes categorias: avião, automóvel, pássaro, gato, cervo, cachorro, sapo, cavalo, navio e caminhão.

(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()

# Normalização de Pixels entre 0 e 1
train_images, test_images = train_images / 255.0, test_images / 255.0

As imagens possuem dimensão 32x32x3 ou seja, 32 pixels de largura e altura e 3 canais de cores. Vamos plotar uma amostra dos dados de treinamento juntamente com a classificação dada:

# Visualizando uma amostra dos dados
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
               'dog', 'frog', 'horse', 'ship', 'truck']

plt.figure(figsize=(10,10))
for i in range(16):
    plt.subplot(4,4,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(train_images[i], cmap=plt.cm.binary)

    plt.xlabel(class_names[train_labels[i][0]], fontsize=15)
plt.show()
![](img/img8.png)

Modelo

Nossa estrutura de modelo é composta de dois pares de convolução e agrupamento seguido de duas camadas totalmente conectadas. O resultado do model.summary() nos resume muito bem os parâmetros que fixamos no modelo juntamente das dimensões do dado em cada camada:

model = models.Sequential(name='CNN-CIFAR10')
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10))

model.summary()

A camada layers.Flatten() serve para empilhar os elementos que estão disposto em várias matrizes em um único vetor. Ao todo nosso modelo possui 167.562 parâmetros para serem calibrados.

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

history = model.fit(train_images, train_labels, epochs=10, 
                    validation_data=(test_images, test_labels))

Avaliando Modelo

Em apenas 10 épocas de treino nos encontramos um modelo com 68,9% de acurácia nos dados de teste! Isso um modelo simples que pode ser executado em minutos.

plt.figure(figsize=(10,4))

plt.subplot(1,2,1)
plt.plot(history.history['accuracy'], label='Acurácia de Treino')
plt.plot(history.history['val_accuracy'], label = 'Acurácia de Validação')
plt.xlabel('Época')
plt.ylabel('Acurácia')
plt.ylim([0.5, 1])
plt.legend(loc='upper right')

plt.subplot(1,2,2)
plt.plot(history.history['loss'], label='Perda de Treino')
plt.plot(history.history['val_loss'], label = 'Perda de Validação')
plt.xlabel('Época')
plt.ylabel('Perda')
plt.legend(loc='upper right')

plt.show()

Uma visualização interessante é observar a predição da imagem juntamente da probabilidade associada a cada categoria.

probability_model = tf.keras.Sequential([model, tf.keras.layers.Softmax()])
predictions = probability_model.predict(test_images)

def plot_image(i, predictions_array, true_label, img):
  predictions_array, true_label, img = predictions_array, true_label[i][0], img[i]
  plt.grid(False)
  plt.xticks([])
  plt.yticks([])

  plt.imshow(img, cmap=plt.cm.binary)

  predicted_label = np.argmax(predictions_array)
  if predicted_label == true_label:
    color = 'blue'
  else:
    color = 'red'

  plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],
                                100*np.max(predictions_array),
                                class_names[true_label]),
                                color=color)

def plot_value_array(i, predictions_array, true_label):
  predictions_array, true_label = predictions_array, true_label[i][0]
  plt.grid(False)
  plt.xticks(range(10))
  plt.yticks([])
  thisplot = plt.bar(range(10), predictions_array, color="#777777")
  plt.ylim([0, 1])
  predicted_label = np.argmax(predictions_array)

  thisplot[predicted_label].set_color('red')
  thisplot[true_label].set_color('blue')
# Previsões corretas são marcadas em azul, erradas em vermelho
num_rows = 5
num_cols = 3
num_images = num_rows*num_cols
plt.figure(figsize=(2*2*num_cols, 2*num_rows))
for i in range(num_images):
  plt.subplot(num_rows, 2*num_cols, 2*i+1)
  plot_image(i, predictions[i], test_labels, test_images)
  plt.subplot(num_rows, 2*num_cols, 2*i+2)
  plot_value_array(i, predictions[i], test_labels)
plt.tight_layout()
plt.show()
![](img/img12.png)

É interessante notar que os erros cometidos geralmente estão associados a probabilidades de classificação mais dispersas. Isso indica uma incerteza do modelo frente às imagens mal classificadas.

Muitos outros modelos já foram aplicados a esse conjunto de dados com diferentes estratégias documentas. Através deste Ranking podemos ver que o melhor modelo chega a uma acurácia de 96,5%.

Considerações finais

Treinamos rapidamente um modelo capaz de classificar imagens do zero, mas como já é de se esperar podemos incluir vários procedimentos para aumentar o poder de generalização da máquina. Desde experimentar diferentes combinações de camadas e seus parâmetros até técnicas de regularização como dropout, batch_normalization e data augmentation.

Por fim, caso queira acompanhar um projeto do LAMFO que está utilizando Redes Neurais Convolucionais para classificar CAPTCHAS dê uma olhada no nosso repositório.

Referências

  • [Texto] https://towardsdatascience.com/a-comprehensive-guide-to-convolutional-neural-networks-the-eli5-way-3bd2b1164a53
  • [Texto] https://matheusfacure.github.io/2017/03/12/cnn-captcha/
  • [Vídeo] [But what is a Neural Network? Deep learning, chapter 1](https://www.youtube.com/watch?v=aircAruvnKk)
  • [Vídeo] Convolution Neural Networks - EXPLAINED
  • [Tutorial] Convolutional Neural Network (CNN)