Notas Sobre Redes Neurais Convolucionais

Publicado em qua 22 abril 2020 na categoria notas • 5 min read

Há um tempo atrás ministrei um pequeno crash course para uma turma de graduação da Faculdade Senai sobre classificação de cães e gatos utilizando Redes Neurais Convolucionais. Nesse curso expliquei um pouco do básico do funcionamento de tais redes, como ela aprende e etc. Como o notebook que criei ficou minimamente decente, resolvi utilizar como post inaugural para o blog. Descobri também que criar notebooks utilizando código e teoria no mesmo lugar é uma forma bastante efetiva de se aprender ou relembrar algum assunto.

O código utilizado é baseado nesse excelente kernel do kaggle: https://www.kaggle.com/uysimty/keras-cnn-dog-or-cat-classification

Toda a teoria aplicada aqui é baseada no lendário paper do Yann Lecun: http://yann.lecun.com/exdb/publis/pdf/lecun-99.pdf

In [ ]:
import numpy as np
from keras.preprocessing.image import ImageDataGenerator, load_img
from keras.utils import to_categorical
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import random
import os
from skimage import io
import pandas as pd

Visualizando Imagens e Preprando os dados

In [ ]:
#Carregando imagens
img = io.imread('./train/dog.10013.jpg')
In [ ]:
img.shape
In [9]:
plt.imshow(img)
Out[9]:
<matplotlib.image.AxesImage at 0x7f31bfa4b908>
In [28]:
filenames = os.listdir("./train/")
categories = []
for filename in filenames:
    category = filename.split('.')[0]
    if category == 'dog':
        categories.append(1)
    else:
        categories.append(0)

df = pd.DataFrame({
    'filename': filenames,
    'category': categories
})

df.head()
Out[28]:
filename category
0 cat.1466.jpg 0
1 cat.10175.jpg 0
2 dog.3446.jpg 1
3 cat.6857.jpg 0
4 dog.4505.jpg 1

Redes Neurais Convolucionais

title

Uma CNN (Convolutional Neural Network) é um algoritmo de Deep Learning criado especialmente para se trabalhar com imagens. Esse tipo de arquitetura nos permite fazer o "encoding" de certas propriedades de imagens e reduzir grandemente o número de parâmetros para se treinar o modelo.

Uma ConvNet combina 3 ideias arquiteturais para garantir algum grau de invariância para mudança, escala e distorção em imagens. Essas ideias arquiteturais são:

  • Campos receptivos locais;
  • Pesos compartilhados;
  • E sub-sampling espacial.

Ela é constituída de duas grandes "sub arquiteturas". Elas são:

  • Extração de características;
  • Classificação de características. </font>

Input

title

Os inputs de uma CNN são imagens RGB (red, green, blue). São, basicamente, matrizes carregando informações de pixels das imagens.

A função de uma ConvNet é reduzir as imagens, sem perder características importantes, de forma a facilitar o processamento e assim realizar a sua tarefa.

Camada Convolucional (Kernels)

title

title

Cada unidade de uma camada convolucional é conectada a uma pequena região dos dados da camada anterior, dessa forma criando **campos receptivos locais**. Com esses campos, as unidades aprendem a extrair características visuais elementares, tais como: arestas orientadas, bordas e cantos. Essas características são combinadas em camadas posteriores, gerando assim características de mais alto nível.

As unidades em uma camada são organizadas em planos aonde todas as unidades do plano compartilham o mesmo conjunto de pesos. A esses planos é dado o nome de **feature maps**. Unidades dentro de um feature map realizam a mesma operação em diferentes partes da imagem.

O funcionamento das camadas de convolução podem ser visualizadas no gif abaixo:

title

Uma vez que as operações de convolução são completadas, utilizamos a função de ativação.

title

Camada de Pooling

Uma característica importante desse modelo de rede neural é que a localização exata de uma característica é irrelevante para identificação do padrão na imagem. Por causa disso podemos reduzir a a resolução espacial do feature map. Isso é feito utlizando-se camadas de **sub sambpling espacial**. Através dessa camada reduzimos a sensibilidade da rede para mudanças e distorções, gerando assim uma melhor generalização de aprendizado.

Na figura abaixo podemos ver dois modos de se fazer o sub sampling de um feature map.

title

Camada Totalmente Conectada (Classificação)

title

Aqui uma rede neural clássica é implementada. Utilizada para classificar as features extraídas pela rede convolucional.

Os inputs são as características extraídas pelas camadas convolucionais, elas são "achatadas" e usadas como input. </font>

Código

In [29]:
FAST_RUN = False
IMAGE_WIDTH=128
IMAGE_HEIGHT=128
IMAGE_SIZE=(IMAGE_WIDTH, IMAGE_HEIGHT)
IMAGE_CHANNELS=3
In [30]:
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense, Activation, BatchNormalization

model = Sequential()

model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_CHANNELS)))
model.add(BatchNormalization()) #normalização da distribuição de input pra cada camada
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

####### FIM EXTRACAO DE FEATURE #################

####### COMEÇA CLASSIFICACAO #############

model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(2, activation='softmax')) # 2 classes de classificação

model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])

model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 126, 126, 32)      896       
_________________________________________________________________
batch_normalization_1 (Batch (None, 126, 126, 32)      128       
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 63, 63, 32)        0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 63, 63, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 61, 61, 64)        18496     
_________________________________________________________________
batch_normalization_2 (Batch (None, 61, 61, 64)        256       
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 30, 30, 64)        0         
_________________________________________________________________
dropout_2 (Dropout)          (None, 30, 30, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 28, 28, 128)       73856     
_________________________________________________________________
batch_normalization_3 (Batch (None, 28, 28, 128)       512       
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 14, 14, 128)       0         
_________________________________________________________________
dropout_3 (Dropout)          (None, 14, 14, 128)       0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 25088)             0         
_________________________________________________________________
dense_1 (Dense)              (None, 512)               12845568  
_________________________________________________________________
batch_normalization_4 (Batch (None, 512)               2048      
_________________________________________________________________
dropout_4 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 2)                 1026      
=================================================================
Total params: 12,942,786
Trainable params: 12,941,314
Non-trainable params: 1,472
_________________________________________________________________

Função de Ativação Softmax

Vai transformar números (logits) em probabilidades que vão se somar a 1. Essa função de ativação tem como output um vetor que representa a distribuição de probabilidade de uma lista de possíveis saídas.

title

Funções de Perda Categorical Cross Entropy

É a função utilizada para avaliar uma solução candidata (um conjunto de pesos). Tipicamente, qunado treinamos redes neurais, buscamos **MINIMIZAR** o erro, ou seja, também queremos **MINIMIZAR** a função de perda.

Então, de forma geral, queremos minimizar a diferença entre a distribuição de probabilidade predita pelo modelo e a distribuição de probabilidade real.

title

Otimizadores

São as funções utlizadas para a **atualização dos pesos de uma rede neural**. Ou seja, os otimizadores são usados para tentar garantir a minimização da função de custo.

title

$$\theta = \theta - \eta \cdot \nabla J(\theta; \, x, \, y)\Leftrightarrow\theta = \theta - \eta \cdot \frac{\partial C}{\partial \theta}$$

$$\theta= pesos \ da \ rede$$

$$\eta= taxa \ de \ aprendizado$$

$$\partial C= funcao \ de \ custo$$

In [31]:
from keras.callbacks import EarlyStopping, ReduceLROnPlateau

earlystop = EarlyStopping(patience=10)



learning_rate_reduction = ReduceLROnPlateau(monitor='val_acc', 
                                            patience=2, 
                                            verbose=1, 
                                            factor=0.5, 
                                            min_lr=0.00001)

callbacks = [earlystop, learning_rate_reduction]

Separação em conjuntos de teste e treino:

In [32]:
df["category"] = df["category"].replace({0: 'cat', 1: 'dog'}) 


train_df, validate_df = train_test_split(df, test_size=0.20, random_state=42)
train_df = train_df.reset_index(drop=True)
validate_df = validate_df.reset_index(drop=True)
In [33]:
train_df.head()
Out[33]:
filename category
0 cat.8132.jpg cat
1 dog.5349.jpg dog
2 dog.5623.jpg dog
3 dog.292.jpg dog
4 cat.4764.jpg cat
In [34]:
total_train = train_df.shape[0]
total_validate = validate_df.shape[0]
batch_size=15
In [36]:
total_validate
Out[36]:
5000
In [37]:
train_datagen = ImageDataGenerator(
    rotation_range=15,
    rescale=1./255,
    shear_range=0.1,
    zoom_range=0.2,
    horizontal_flip=True,
    width_shift_range=0.1,
    height_shift_range=0.1
)

train_generator = train_datagen.flow_from_dataframe(
    train_df, 
    "./train", 
    x_col='filename',
    y_col='category',
    target_size=IMAGE_SIZE,
    class_mode='categorical',
    batch_size=batch_size
)



validation_datagen = ImageDataGenerator(rescale=1./255)
validation_generator = validation_datagen.flow_from_dataframe(
    validate_df, 
    "./train", 
    x_col='filename',
    y_col='category',
    target_size=IMAGE_SIZE,
    class_mode='categorical',
    batch_size=batch_size
)
Found 20000 validated image filenames belonging to 2 classes.
Found 5000 validated image filenames belonging to 2 classes.
In [ ]:
epochs=3 if FAST_RUN else 50
history = model.fit_generator(
    train_generator, 
    epochs=epochs,
    validation_data=validation_generator,
    validation_steps=total_validate//batch_size,
    steps_per_epoch=total_train//batch_size,
    callbacks=callbacks
)