Sobre este codelab
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:
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:
Veja o que a colab produz:
Apesar de ser um gráfico de desenho animado, ele ainda se classifica corretamente.
A imagem a seguir também classifica corretamente:
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:
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.