Usar redes neurais convolucionais (CNNs) com imagens complexas

1. Antes de começar

Neste codelab, você usará as convoluções para classificar imagens de cavalos e humanos. Você usará o TensorFlow neste laboratório para criar uma CNN treinada para reconhecer imagens de cavalos e humanos e classificá-las.

Pré-requisitos

Se você nunca criou convoluções com o TensorFlow, recomendamos concluir o codelab Criar convoluções e realizar pools, em que apresentamos as convoluções e os pools, e Criar redes neurais convolucionais (CNNs) para melhorar a visão computacional, onde discutimos como tornar os computadores mais eficientes no reconhecimento de imagens.

O que você aprenderá

  • Como treinar computadores para reconhecer recursos em uma imagem em que o assunto não está claro

O que você vai criar

  • Uma rede neural convolucional que pode distinguir entre cavalos e humanos

O que é necessário

O código do restante do codelab está sendo executado no Colab.

Você também precisará do TensorFlow instalado e das bibliotecas instaladas no codelab anterior.

2. Primeiros passos: adquirir os dados

Você fará isso construindo um classificador de cavalos ou humanos que informará se uma determinada imagem contém um cavalo ou um ser humano, onde a rede será treinada para reconhecer as características que determinam quais deles são. Você precisará processar os dados antes do treinamento.

Primeiro, faça o download dos dados:

!wget --no-check-certificate https://storage.googleapis.com/laurencemoroney-blog.appspot.com/horse-or-human.zip  -O /tmp/horse-or-human.zip

O código Python a seguir usará a biblioteca do SO para usar as bibliotecas do sistema operacional, o que fornece acesso ao sistema de arquivos e à biblioteca de arquivos ZIP, permitindo que você descompacte os dados.

import os
import zipfile
 
local_zip = '/tmp/horse-or-human.zip'
zip_ref = zipfile.ZipFile(local_zip, 'r')
zip_ref.extractall('/tmp/horse-or-human')
zip_ref.close()

O conteúdo do arquivo ZIP é extraído para o diretório base /tmp/horse-or-human, que contém cavalos e subdiretórios humanos.

Em resumo, o conjunto de treinamento são os dados usados para informar ao modelo de rede neural que "como um cavalo é", e esse é o humano.

3. Usar o ImageGenerator para rotular e preparar os dados

As imagens não são rotuladas explicitamente como cavalos ou humanos.

Mais tarde, você verá algo chamado ImageDataGenerator, que está sendo usado. Ele lê imagens de subdiretórios e as rotula automaticamente do nome desse subdiretório. Por exemplo, você tem um diretório de treinamento que contém um diretório de cavalos e um diretório de humanos. O ImageDataGenerator rotula as imagens corretamente, reduzindo uma etapa de codificação.

Defina cada um desses diretórios.

# Directory with our training horse pictures
train_horse_dir = os.path.join('/tmp/horse-or-human/horses')
 
# Directory with our training human pictures
train_human_dir = os.path.join('/tmp/horse-or-human/humans')

Agora veja como os nomes de arquivo aparecem nos diretórios de treinamento de cavalos e humanos:

train_horse_names = os.listdir(train_horse_dir)
print(train_horse_names[:10])
train_human_names = os.listdir(train_human_dir)
print(train_human_names[:10])

Encontre o número total de imagens de cavalos e humanos nos diretórios:

print('total training horse images:', len(os.listdir(train_horse_dir)))
print('total training human images:', len(os.listdir(train_human_dir)))

4. Explorar os dados

Veja algumas fotos para entender melhor como elas são.

Primeiro, configure os parâmetros matplot:

%matplotlib inline
 
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
 
# Parameters for our graph; we'll output images in a 4x4 configuration
nrows = 4
ncols = 4
 
# Index for iterating over images
pic_index = 0

Agora, exiba um lote de oito fotos de cavalos e oito imagens humanas. Execute novamente a célula para ver um lote novo todas as vezes.

# Set up matplotlib fig, and size it to fit 4x4 pics
fig = plt.gcf()
fig.set_size_inches(ncols * 4, nrows * 4)
 
pic_index += 8
next_horse_pix = [os.path.join(train_horse_dir, fname) 
                for fname in train_horse_names[pic_index-8:pic_index]]
next_human_pix = [os.path.join(train_human_dir, fname) 
                for fname in train_human_names[pic_index-8:pic_index]]
 
for i, img_path in enumerate(next_horse_pix+next_human_pix):
  # Set up subplot; subplot indices start at 1
  sp = plt.subplot(nrows, ncols, i + 1)
  sp.axis('Off') # Don't show axes (or gridlines)
 
  img = mpimg.imread(img_path)
  plt.imshow(img)
 
plt.show()
 

Veja alguns exemplos de imagens que mostram cavalos e humanos em diferentes posições e orientações:

6b6ebbc6e694ccd2.png

5. Definir o modelo

Comece definindo o modelo.

Comece importando o TensorFlow:

import tensorflow as tf

Depois, adicione camadas convolucionais e nivele o resultado final para alimenta-lo nas camadas muito conectadas. Por fim, adicione as camadas densamente conectadas.

Observe que, como você está enfrentando um problema de classificação de duas classes (um problema de classificação binária), sua rede será encerrada com uma ativação sigmoid (link em inglês) para que a saída da sua rede seja um único escalar entre 0 e 1, codificando a probabilidade de a imagem atual ser a classe 1 (em vez da classe 0).

model = tf.keras.models.Sequential([
    # Note the input shape is the desired size of the image 300x300 with 3 bytes color
    # This is the first convolution
    tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(300, 300, 3)),
    tf.keras.layers.MaxPooling2D(2, 2),
    # The second convolution
    tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The third convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The fourth convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The fifth convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # Flatten the results to feed into a DNN
    tf.keras.layers.Flatten(),
    # 512 neuron hidden layer
    tf.keras.layers.Dense(512, activation='relu'),
    # Only 1 output neuron. It will contain a value from 0-1 where 0 for 1 class ('horses') and 1 for the other ('humans')
    tf.keras.layers.Dense(1, activation='sigmoid')
])

A chamada de método model.summary() imprime um resumo da rede.

model.summary()

Veja os resultados aqui:

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 298, 298, 16)      448       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 149, 149, 16)      0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 147, 147, 32)      4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 73, 73, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 71, 71, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 35, 35, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 33, 33, 64)        36928     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 16, 16, 64)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 14, 14, 64)        36928     
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 7, 7, 64)          0         
_________________________________________________________________
flatten (Flatten)            (None, 3136)              0         
_________________________________________________________________
dense (Dense)                (None, 512)               1606144   
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 513       
=================================================================
Total params: 1,704,097
Trainable params: 1,704,097
Non-trainable params: 0

A coluna de forma de saída mostra como o tamanho do mapa de elementos evolui em cada camada sucessiva. As camadas de convolução reduzem um pouco o tamanho dos mapas de recursos devido ao padding, e cada camada de pool reduz pela metade as dimensões.

6. Compilar o modelo

Em seguida, configure as especificações para o treinamento do modelo. Treine seu modelo com a perda binary_crossentropy porque isso é um problema de classificação binária e a ativação final é um sigmoide. Para relembrar as métricas sobre perdas, leia Como reduzir o ML. Use o otimizador rmsprop com uma taxa de aprendizado de 0,001. Durante o treinamento, monitore a precisão da classificação.

from tensorflow.keras.optimizers import RMSprop
 
model.compile(loss='binary_crossentropy',
              optimizer=RMSprop(lr=0.001),
              metrics=['acc'])

7. Treinar o modelo com geradores

Configure geradores de dados que leem imagens nas pastas de origem, as convertem em tensores float32 e os alimentam (com as etiquetas) na sua rede.

Você terá um gerador para as imagens de treinamento e outro para as imagens de validação. Seus geradores gerarão lotes de imagens de tamanho 300 x 300 e rótulos (binários).

Como você já deve saber, os dados que vão para as redes neurais geralmente precisam ser normalizados de forma a facilitar o processamento pela rede. Não é comum alimentar pixels brutos em uma CNN. No seu caso, você processará previamente as imagens normalizando os valores de pixel para o intervalo [0, 1] (originalmente todos os valores estão no intervalo [0, 255]).

Na Keras, isso pode ser feito pela classe keras.preprocessing.image.ImageDataGenerator usando o parâmetro de redimensionamento. Essa classe ImageDataGenerator permite instanciar geradores de lotes de imagens aumentadas (e os respectivos rótulos) usando .flow(data, labels) ou .flow_from_directory(directory). Esses geradores podem ser usados com os métodos de modelo Keras que aceitam geradores de dados como entradas: fit_generator, evaluate_generator e predict_generator.

from tensorflow.keras.preprocessing.image import ImageDataGenerator
 
# All images will be rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
 
# Flow training images in batches of 128 using train_datagen generator
train_generator = train_datagen.flow_from_directory(
        '/tmp/horse-or-human/',  # This is the source directory for training images
        target_size=(300, 300),  # All images will be resized to 150x150
        batch_size=128,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')

8. Faça o treinamento

Treine por 15 períodos. Isso pode levar alguns minutos.

history = model.fit(
      train_generator,
      steps_per_epoch=8,  
      epochs=15,
      verbose=1)

Observe os valores por período.

Perda e Precisão são uma ótima indicação do progresso do treinamento. Ele está tentando adivinhar a classificação dos dados de treinamento e, em seguida, medindo-os em relação ao rótulo conhecido, calculando o resultado. A precisão é a porção correta.

Epoch 1/15
9/9 [==============================] - 9s 1s/step - loss: 0.8662 - acc: 0.5151
Epoch 2/15
9/9 [==============================] - 8s 927ms/step - loss: 0.7212 - acc: 0.5969
Epoch 3/15
9/9 [==============================] - 8s 921ms/step - loss: 0.6612 - acc: 0.6592
Epoch 4/15
9/9 [==============================] - 8s 925ms/step - loss: 0.3135 - acc: 0.8481
Epoch 5/15
9/9 [==============================] - 8s 919ms/step - loss: 0.4640 - acc: 0.8530
Epoch 6/15
9/9 [==============================] - 8s 896ms/step - loss: 0.2306 - acc: 0.9231
Epoch 7/15
9/9 [==============================] - 8s 915ms/step - loss: 0.1464 - acc: 0.9396
Epoch 8/15
9/9 [==============================] - 8s 935ms/step - loss: 0.2663 - acc: 0.8919
Epoch 9/15
9/9 [==============================] - 8s 883ms/step - loss: 0.0772 - acc: 0.9698
Epoch 10/15
9/9 [==============================] - 9s 951ms/step - loss: 0.0403 - acc: 0.9805
Epoch 11/15
9/9 [==============================] - 8s 891ms/step - loss: 0.2618 - acc: 0.9075
Epoch 12/15
9/9 [==============================] - 8s 902ms/step - loss: 0.0434 - acc: 0.9873
Epoch 13/15
9/9 [==============================] - 8s 904ms/step - loss: 0.0187 - acc: 0.9932
Epoch 14/15
9/9 [==============================] - 9s 951ms/step - loss: 0.0974 - acc: 0.9649
Epoch 15/15
9/9 [==============================] - 8s 877ms/step - loss: 0.2859 - acc: 0.9338

9. Testar o modelo

Agora, execute uma previsão usando o modelo. Com o código, você pode escolher um ou mais arquivos do seu sistema. Em seguida, ele fará upload e execução dos modelos no modelo, indicando se o objeto é uma pessoa ou um cavalo.

É possível fazer o download de imagens da Internet para seu sistema de arquivos. Você pode notar que a rede comete muitos erros, apesar de a precisão do treinamento estar acima de 99%.

Isso acontece devido a algo chamado overfitting, que significa que a rede neural é treinada com dados muito limitados. Há apenas cerca de 500 imagens de cada classe. Portanto, ele reconhece muito bem imagens que se parecem com as do conjunto de treinamento, mas pode falhar muito em imagens que não estão no conjunto de treinamento.

Esse é um ponto de dados que prova que, quanto mais dados você treinar, melhor será sua rede final.

Há muitas técnicas que podem ser usadas para melhorar seu treinamento, apesar de os dados serem limitados, incluindo algo chamado ampliação de imagem, que está além do escopo deste codelab.

import numpy as np
from google.colab import files
from keras.preprocessing import image
 
uploaded = files.upload()
 
for fn in uploaded.keys():
 
  # predicting images
  path = '/content/' + fn
  img = image.load_img(path, target_size=(300, 300))
  x = image.img_to_array(img)
  x = np.expand_dims(x, axis=0)
 
  images = np.vstack([x])
  classes = model.predict(images, batch_size=10)
  print(classes[0])
  if classes[0]>0.5:
    print(fn + " is a human")
  else:
    print(fn + " is a horse")

Por exemplo, digamos que você queira testar com esta imagem:

9e07a57ff3be7a82.jpeg

Veja o que a colab produz:

77b678e70b00862a.png

Apesar de ser um gráfico de desenho animado, ele ainda se classifica corretamente.

A imagem a seguir também classifica corretamente:

c9213173d9f3d83c.jpeg

f2844da737a1a2f2.png

Teste algumas imagens próprias e explore!

10. Visualizar representações intermediárias

Para ter uma ideia dos tipos de recursos que sua CNN aprendeu, é interessante ver como uma entrada passa pela rede.

Escolha uma imagem aleatória do conjunto de treinamento, gere uma figura em que cada linha é a saída de uma camada e cada imagem na linha é um filtro específico desse mapa de atributos de saída. Execute novamente essa célula para gerar representações intermediárias para uma variedade de imagens de treinamento.

import numpy as np
import random
from tensorflow.keras.preprocessing.image import img_to_array, load_img
 
# Let's define a new Model that will take an image as input, and will output
# intermediate representations for all layers in the previous model after
# the first.
successive_outputs = [layer.output for layer in model.layers[1:]]
#visualization_model = Model(img_input, successive_outputs)
visualization_model = tf.keras.models.Model(inputs = model.input, outputs = successive_outputs)
# Let's prepare a random input image from the training set.
horse_img_files = [os.path.join(train_horse_dir, f) for f in train_horse_names]
human_img_files = [os.path.join(train_human_dir, f) for f in train_human_names]
img_path = random.choice(horse_img_files + human_img_files)
 
img = load_img(img_path, target_size=(300, 300))  # this is a PIL image
x = img_to_array(img)  # Numpy array with shape (150, 150, 3)
x = x.reshape((1,) + x.shape)  # Numpy array with shape (1, 150, 150, 3)
 
# Rescale by 1/255
x /= 255
 
# Let's run our image through our network, thus obtaining all
# intermediate representations for this image.
successive_feature_maps = visualization_model.predict(x)
 
# These are the names of the layers, so can have them as part of our plot
layer_names = [layer.name for layer in model.layers]
 
# Now let's display our representations
for layer_name, feature_map in zip(layer_names, successive_feature_maps):
  if len(feature_map.shape) == 4:
    # Just do this for the conv / maxpool layers, not the fully-connected layers
    n_features = feature_map.shape[-1]  # number of features in feature map
    # The feature map has shape (1, size, size, n_features)
    size = feature_map.shape[1]
    # We will tile our images in this matrix
    display_grid = np.zeros((size, size * n_features))
    for i in range(n_features):
      # Postprocess the feature to make it visually palatable
      x = feature_map[0, :, :, i]
      x -= x.mean()
      if x.std()>0:
        x /= x.std()
      x *= 64
      x += 128
      x = np.clip(x, 0, 255).astype('uint8')
      # We'll tile each filter into this big horizontal grid
      display_grid[:, i * size : (i + 1) * size] = x
    # Display the grid
    scale = 20. / n_features
    plt.figure(figsize=(scale * n_features, scale))
    plt.title(layer_name)
    plt.grid(False)
    plt.imshow(display_grid, aspect='auto', cmap='viridis')

Veja alguns exemplos de resultados:

e078d1bc9662c93f.png

Como podemos ver, você vai dos pixels brutos das imagens para representações cada vez mais abstratas e compactas. As representações downstream começam a destacar o que a rede presta atenção e mostram cada vez menos recursos sendo "ativados." A maioria deles está definida como zero. Isso é chamado de sparsidade. A esparsidade da representatividade é um recurso essencial do aprendizado profundo.

Essas representações têm cada vez menos informações sobre os pixels originais da imagem, mas cada vez mais sobre a classe da imagem. Pense em uma CNN (ou uma rede profunda em geral) como um pipeline de destilação de informações.

11. Parabéns

Você aprendeu a usar CNNs para aprimorar imagens complexas. Para saber como aprimorar ainda mais seus modelos de visão computacional, consulte Usar redes neurais convolucionais (CNNs) com grandes conjuntos de dados para evitar o overfitting.