import os # operational system para manipulação de arquivos.
import cv2 # opencv para manipulação de imagens.
import random
import numpy as np # numpy para manipulação de matrizes e arrays
import matplotlib.pyplot as plt # pyplot para plotagem de gráficos e imagens
import zipfile # zipfile para lidar com arquivos compactados
import urllib.request as url # urllib para baixar arquivos via HTTPS
from tensorflow.keras import layers # módulo de camadas do keras
from tensorflow.keras import callbacks # módulo de callbacks do keras
from tensorflow.keras import optimizers # módulo de otimizadores do keras
# classe de modelos sequenciais para construir as redes neurais
from tensorflow.keras.models import Sequential
# classe de modelos sequenciais para construir as redes neurais
from tensorflow.keras.applications import VGG16
# gerador de dados do keras, utilizado para carregar imagens em tempo de execução
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# método para importar modelos gerados pelo tensorflow
from tensorflow.keras.models import load_model
# extraindo a base de dados do arquivo compactado
url.urlretrieve('https://github.com/Alyssonmach/pdi-labs/raw/main/data.zip',
'data.zip')
url.urlretrieve('https://github.com/Alyssonmach/pdi-labs/raw/main/model.h5',
'model.h5')
with zipfile.ZipFile('data.zip', 'r') as zip_ref:
zip_ref.extractall('')
Vamos utilizar o banco de dados Cats vs Dogs, que foi utilizado no Desafio Prático;
Cada instância do banco de dados corresponde a uma imagem rotulada de um Cachorro ou um Gato;
As imagens do banco de dados são coloridas e foram redimensionadas para 150x150;
Imagens de Cachorro têm rótulo 0 e as de Gato têm rótulo 1;
A versão que estamos usando tem 3000 imagens, 2000 para treino, 500 para validação e 500 para teste;
Todas as partições da base de dados são balanceadas;
O Keras disponibiliza um objeto ImageDataGenerator para o carregamento de dados em tempo real durante o treinamento de modelos. São necessárias duas etapas para configurar o objeto:
val_datagen = ImageDataGenerator(rescale = 1./255)
val_generator = val_datagen.flow_from_directory(os.path.join(".", "data", "val"), target_size = (150, 150),
batch_size = 20, class_mode = "binary")
# Atributo do generator que fornece o número de amostras detectadas
val_samples = val_generator.samples
print(val_samples, "amostras detectadas")
# Atributo do generator que fornece o mapeamento de classe para índice
# Repare que os índices são definidos pelo generator com base nos diretórios de arquivos em ordem alfabética
class_to_idx_dict = val_generator.class_indices
print( "Mapeamento Classes -> Índices:", class_to_idx_dict )
# Construção de um novo dicionário que inverte o mapeamento
idx_to_class_dict = { v: k for k, v in class_to_idx_dict.items() }
print( "Mapeamento Índices -> Classes:", idx_to_class_dict )
Found 1000 images belonging to 2 classes. 1000 amostras detectadas Mapeamento Classes -> Índices: {'Cachorro': 0, 'Gato': 1} Mapeamento Índices -> Classes: {0: 'Cachorro', 1: 'Gato'}
Outras transformações possíveis no ImageDataGenerator viabilizam o aumento de dados. Nesse sentido, é possível definir transformações aleatórias que são realizadas conforme os dados são carregados para simular um banco de dados maior.
train_datagen = ImageDataGenerator(
rescale = 1. / 255, # normalizando as imagens
rotation_range = 45, # Rotação aleatória de até 40°
width_shift_range = 0.2, # Translação horizontal de até 20% da largura
height_shift_range = 0.2, # Translação vertical de até 20% da altura
zoom_range = 0.2, # Zoom aleatório de até 20%
shear_range = 0.2, # Deformação de 20%
horizontal_flip = True, # Espelhamento horizontal aleatório
vertical_flip = False, # Espelhamento vertical aleatório
fill_mode = "nearest") # Preenchimentod e buracos pelo pixel mais próximo
train_generator = train_datagen.flow_from_directory(
os.path.join(".", "data", "train"), target_size = (150, 150),
batch_size = 20, class_mode = "binary")
# Atributo do generator que fornece o número de amostras detectadas
train_samples = train_generator.samples
print(train_samples)
Found 2000 images belonging to 2 classes. 2000
model = load_model("model.h5")
model.summary()
Model: "sequential_4" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= vgg16 (Functional) (None, 4, 4, 512) 14714688 flatten_4 (Flatten) (None, 8192) 0 dense_8 (Dense) (None, 256) 2097408 dense_9 (Dense) (None, 1) 257 ================================================================= Total params: 16,812,353 Trainable params: 2,097,665 Non-trainable params: 14,714,688 _________________________________________________________________
No Keras podemos acessar as camadas do modelo individualmente através do atributo layers de um modelo, que retorna uma lista das suas camadas. É possível verificar e alterar alguns dos atribudos das camadas, a exemplo de:
for layer in model.layers:
status = "Treinável" if layer.trainable else "Congelada"
print("Camada '{}' - Status: {} - Entrada: {} - Saída: {}".format(layer.name,
status,
layer.input_shape,
layer.output_shape))
Camada 'vgg16' - Status: Treinável - Entrada: (None, 150, 150, 3) - Saída: (None, 4, 4, 512) Camada 'flatten_4' - Status: Treinável - Entrada: (None, 4, 4, 512) - Saída: (None, 8192) Camada 'dense_8' - Status: Treinável - Entrada: (None, 8192) - Saída: (None, 256) Camada 'dense_9' - Status: Treinável - Entrada: (None, 256) - Saída: (None, 1)
Também é possível acessar camadas através da função get_layer, que retorna uma camada a partir de uma referência ao seu nome.
conv_base = model.get_layer("vgg16")
for layer in conv_base.layers:
status = "Treinável" if layer.trainable else "Congelada"
print("Camada '{}' - Status: {} - Entrada: {} - Saída: {}".format(layer.name,
status,
layer.input_shape,
layer.output_shape))
Camada 'input_8' - Status: Treinável - Entrada: [(None, 150, 150, 3)] - Saída: [(None, 150, 150, 3)] Camada 'block1_conv1' - Status: Congelada - Entrada: (None, 150, 150, 3) - Saída: (None, 150, 150, 64) Camada 'block1_conv2' - Status: Congelada - Entrada: (None, 150, 150, 64) - Saída: (None, 150, 150, 64) Camada 'block1_pool' - Status: Congelada - Entrada: (None, 150, 150, 64) - Saída: (None, 75, 75, 64) Camada 'block2_conv1' - Status: Congelada - Entrada: (None, 75, 75, 64) - Saída: (None, 75, 75, 128) Camada 'block2_conv2' - Status: Congelada - Entrada: (None, 75, 75, 128) - Saída: (None, 75, 75, 128) Camada 'block2_pool' - Status: Congelada - Entrada: (None, 75, 75, 128) - Saída: (None, 37, 37, 128) Camada 'block3_conv1' - Status: Congelada - Entrada: (None, 37, 37, 128) - Saída: (None, 37, 37, 256) Camada 'block3_conv2' - Status: Congelada - Entrada: (None, 37, 37, 256) - Saída: (None, 37, 37, 256) Camada 'block3_conv3' - Status: Congelada - Entrada: (None, 37, 37, 256) - Saída: (None, 37, 37, 256) Camada 'block3_pool' - Status: Congelada - Entrada: (None, 37, 37, 256) - Saída: (None, 18, 18, 256) Camada 'block4_conv1' - Status: Congelada - Entrada: (None, 18, 18, 256) - Saída: (None, 18, 18, 512) Camada 'block4_conv2' - Status: Congelada - Entrada: (None, 18, 18, 512) - Saída: (None, 18, 18, 512) Camada 'block4_conv3' - Status: Congelada - Entrada: (None, 18, 18, 512) - Saída: (None, 18, 18, 512) Camada 'block4_pool' - Status: Congelada - Entrada: (None, 18, 18, 512) - Saída: (None, 9, 9, 512) Camada 'block5_conv1' - Status: Congelada - Entrada: (None, 9, 9, 512) - Saída: (None, 9, 9, 512) Camada 'block5_conv2' - Status: Congelada - Entrada: (None, 9, 9, 512) - Saída: (None, 9, 9, 512) Camada 'block5_conv3' - Status: Congelada - Entrada: (None, 9, 9, 512) - Saída: (None, 9, 9, 512) Camada 'block5_pool' - Status: Congelada - Entrada: (None, 9, 9, 512) - Saída: (None, 4, 4, 512)
No Keras, camadas de uma rede podem ter seus pesos congelados, fazendo com que eles não sejam modificados durante o treinamento.
# definindo todas as camadas do modelo como treináveis
model.trainable = True
# obtendo as camadas convolucionais da arquitetura VGG16
conv_base = model.get_layer("vgg16")
# criando uma flag de estado de treinamento e inicializando-a como falsa (estado não treinável)
set_trainable = False
# para cada uma das camadas convolucionais da VGG16...
for layer in conv_base.layers:
# se a camada for igual a 'block5_conv2'...
if layer.name == "block5_conv2":
# a flag de estado de treinamento muda de estado para estado treinável
set_trainable = True
# todas as camadas posteriores a 'block5_conv2' são configuradas como treinável
# e as camadas anteriores como não treináveis
layer.trainable = set_trainable
model.compile(optimizer=optimizers.Adam(learning_rate = 1e-5),
loss = "binary_crossentropy",
metrics = ["acc"])
model.summary()
Model: "sequential_4" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= vgg16 (Functional) (None, 4, 4, 512) 14714688 flatten_4 (Flatten) (None, 8192) 0 dense_8 (Dense) (None, 256) 2097408 dense_9 (Dense) (None, 1) 257 ================================================================= Total params: 16,812,353 Trainable params: 6,817,281 Non-trainable params: 9,995,072 _________________________________________________________________
model_checkpoint = callbacks.ModelCheckpoint("model_ft.h5", monitor = "val_acc",
save_best_only = True, verbose = 1)
reduce_lr_on_plateau = callbacks.ReduceLROnPlateau(monitor = "val_acc", factor = 0.75,
patience = 3, verbose = 1)
# Repare que ao utilizar mais de 1 callback elas devem ser organizadas em uma lista
callback_list = [model_checkpoint, reduce_lr_on_plateau]
O treinamento é realizado a partir da função fit, que recebe dados de treino e de validação além de hiperparâmetros como o número de épocas e o tamanho dos lotes de dados (batchsize). Nesse caso, como estamos utilizando generators, não é preciso fornecer os exemplos e os gabaritos separadamente e nem o batchsize:
history = model.fit(train_generator, steps_per_epoch = 100,
epochs = 30, callbacks = callback_list,
validation_data = val_generator, validation_steps = 50)
model.load_weights("model_ft.h5")
history_dict = history.history
Epoch 1/30 100/100 [==============================] - ETA: 0s - loss: 0.2954 - acc: 0.8705 Epoch 1: val_acc improved from -inf to 0.89300, saving model to model_ft.h5 100/100 [==============================] - 22s 125ms/step - loss: 0.2954 - acc: 0.8705 - val_loss: 0.2375 - val_acc: 0.8930 - lr: 1.0000e-05 Epoch 2/30 100/100 [==============================] - ETA: 0s - loss: 0.2731 - acc: 0.8790 Epoch 2: val_acc improved from 0.89300 to 0.90300, saving model to model_ft.h5 100/100 [==============================] - 13s 134ms/step - loss: 0.2731 - acc: 0.8790 - val_loss: 0.2411 - val_acc: 0.9030 - lr: 1.0000e-05 Epoch 3/30 100/100 [==============================] - ETA: 0s - loss: 0.2543 - acc: 0.8880 Epoch 3: val_acc improved from 0.90300 to 0.91100, saving model to model_ft.h5 100/100 [==============================] - 12s 124ms/step - loss: 0.2543 - acc: 0.8880 - val_loss: 0.2278 - val_acc: 0.9110 - lr: 1.0000e-05 Epoch 4/30 100/100 [==============================] - ETA: 0s - loss: 0.2337 - acc: 0.9055 Epoch 4: val_acc did not improve from 0.91100 100/100 [==============================] - 12s 119ms/step - loss: 0.2337 - acc: 0.9055 - val_loss: 0.2393 - val_acc: 0.8940 - lr: 1.0000e-05 Epoch 5/30 100/100 [==============================] - ETA: 0s - loss: 0.2220 - acc: 0.9075 Epoch 5: val_acc did not improve from 0.91100 100/100 [==============================] - 12s 119ms/step - loss: 0.2220 - acc: 0.9075 - val_loss: 0.2191 - val_acc: 0.9070 - lr: 1.0000e-05 Epoch 6/30 100/100 [==============================] - ETA: 0s - loss: 0.2129 - acc: 0.9100 Epoch 6: val_acc improved from 0.91100 to 0.91200, saving model to model_ft.h5 100/100 [==============================] - 12s 123ms/step - loss: 0.2129 - acc: 0.9100 - val_loss: 0.2200 - val_acc: 0.9120 - lr: 1.0000e-05 Epoch 7/30 100/100 [==============================] - ETA: 0s - loss: 0.1932 - acc: 0.9125 Epoch 7: val_acc did not improve from 0.91200 100/100 [==============================] - 12s 119ms/step - loss: 0.1932 - acc: 0.9125 - val_loss: 0.2173 - val_acc: 0.9100 - lr: 1.0000e-05 Epoch 8/30 100/100 [==============================] - ETA: 0s - loss: 0.1872 - acc: 0.9240 Epoch 8: val_acc improved from 0.91200 to 0.92100, saving model to model_ft.h5 100/100 [==============================] - 12s 122ms/step - loss: 0.1872 - acc: 0.9240 - val_loss: 0.1926 - val_acc: 0.9210 - lr: 1.0000e-05 Epoch 9/30 100/100 [==============================] - ETA: 0s - loss: 0.1884 - acc: 0.9225 Epoch 9: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 120ms/step - loss: 0.1884 - acc: 0.9225 - val_loss: 0.2192 - val_acc: 0.9040 - lr: 1.0000e-05 Epoch 10/30 100/100 [==============================] - ETA: 0s - loss: 0.1790 - acc: 0.9275 Epoch 10: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 119ms/step - loss: 0.1790 - acc: 0.9275 - val_loss: 0.2169 - val_acc: 0.9110 - lr: 1.0000e-05 Epoch 11/30 100/100 [==============================] - ETA: 0s - loss: 0.1762 - acc: 0.9275 Epoch 11: val_acc did not improve from 0.92100 Epoch 11: ReduceLROnPlateau reducing learning rate to 7.499999810534064e-06. 100/100 [==============================] - 12s 120ms/step - loss: 0.1762 - acc: 0.9275 - val_loss: 0.2366 - val_acc: 0.9050 - lr: 1.0000e-05 Epoch 12/30 100/100 [==============================] - ETA: 0s - loss: 0.1648 - acc: 0.9325 Epoch 12: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 118ms/step - loss: 0.1648 - acc: 0.9325 - val_loss: 0.2028 - val_acc: 0.9150 - lr: 7.5000e-06 Epoch 13/30 100/100 [==============================] - ETA: 0s - loss: 0.1478 - acc: 0.9415 Epoch 13: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 119ms/step - loss: 0.1478 - acc: 0.9415 - val_loss: 0.2018 - val_acc: 0.9150 - lr: 7.5000e-06 Epoch 14/30 100/100 [==============================] - ETA: 0s - loss: 0.1690 - acc: 0.9330 Epoch 14: val_acc did not improve from 0.92100 Epoch 14: ReduceLROnPlateau reducing learning rate to 5.624999857900548e-06. 100/100 [==============================] - 12s 120ms/step - loss: 0.1690 - acc: 0.9330 - val_loss: 0.2147 - val_acc: 0.9110 - lr: 7.5000e-06 Epoch 15/30 100/100 [==============================] - ETA: 0s - loss: 0.1395 - acc: 0.9380 Epoch 15: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 121ms/step - loss: 0.1395 - acc: 0.9380 - val_loss: 0.2098 - val_acc: 0.9130 - lr: 5.6250e-06 Epoch 16/30 100/100 [==============================] - ETA: 0s - loss: 0.1369 - acc: 0.9440 Epoch 16: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 120ms/step - loss: 0.1369 - acc: 0.9440 - val_loss: 0.2088 - val_acc: 0.9190 - lr: 5.6250e-06 Epoch 17/30 100/100 [==============================] - ETA: 0s - loss: 0.1312 - acc: 0.9530 Epoch 17: val_acc did not improve from 0.92100 Epoch 17: ReduceLROnPlateau reducing learning rate to 4.2187500639556674e-06. 100/100 [==============================] - 12s 120ms/step - loss: 0.1312 - acc: 0.9530 - val_loss: 0.2005 - val_acc: 0.9140 - lr: 5.6250e-06 Epoch 18/30 100/100 [==============================] - ETA: 0s - loss: 0.1320 - acc: 0.9470 Epoch 18: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 119ms/step - loss: 0.1320 - acc: 0.9470 - val_loss: 0.2047 - val_acc: 0.9130 - lr: 4.2188e-06 Epoch 19/30 100/100 [==============================] - ETA: 0s - loss: 0.1386 - acc: 0.9485 Epoch 19: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 118ms/step - loss: 0.1386 - acc: 0.9485 - val_loss: 0.2040 - val_acc: 0.9150 - lr: 4.2188e-06 Epoch 20/30 100/100 [==============================] - ETA: 0s - loss: 0.1354 - acc: 0.9445 Epoch 20: val_acc did not improve from 0.92100 Epoch 20: ReduceLROnPlateau reducing learning rate to 3.164062718497007e-06. 100/100 [==============================] - 12s 119ms/step - loss: 0.1354 - acc: 0.9445 - val_loss: 0.1970 - val_acc: 0.9170 - lr: 4.2188e-06 Epoch 21/30 100/100 [==============================] - ETA: 0s - loss: 0.1299 - acc: 0.9495 Epoch 21: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 120ms/step - loss: 0.1299 - acc: 0.9495 - val_loss: 0.2005 - val_acc: 0.9120 - lr: 3.1641e-06 Epoch 22/30 100/100 [==============================] - ETA: 0s - loss: 0.1151 - acc: 0.9610 Epoch 22: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 118ms/step - loss: 0.1151 - acc: 0.9610 - val_loss: 0.2126 - val_acc: 0.9110 - lr: 3.1641e-06 Epoch 23/30 100/100 [==============================] - ETA: 0s - loss: 0.1204 - acc: 0.9555 Epoch 23: val_acc did not improve from 0.92100 Epoch 23: ReduceLROnPlateau reducing learning rate to 2.3730470388727554e-06. 100/100 [==============================] - 12s 120ms/step - loss: 0.1204 - acc: 0.9555 - val_loss: 0.2012 - val_acc: 0.9130 - lr: 3.1641e-06 Epoch 24/30 100/100 [==============================] - ETA: 0s - loss: 0.1167 - acc: 0.9580 Epoch 24: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 118ms/step - loss: 0.1167 - acc: 0.9580 - val_loss: 0.2058 - val_acc: 0.9190 - lr: 2.3730e-06 Epoch 25/30 100/100 [==============================] - ETA: 0s - loss: 0.1200 - acc: 0.9520 Epoch 25: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 119ms/step - loss: 0.1200 - acc: 0.9520 - val_loss: 0.2091 - val_acc: 0.9120 - lr: 2.3730e-06 Epoch 26/30 100/100 [==============================] - ETA: 0s - loss: 0.1166 - acc: 0.9540 Epoch 26: val_acc did not improve from 0.92100 Epoch 26: ReduceLROnPlateau reducing learning rate to 1.7797852365220024e-06. 100/100 [==============================] - 12s 120ms/step - loss: 0.1166 - acc: 0.9540 - val_loss: 0.2258 - val_acc: 0.9140 - lr: 2.3730e-06 Epoch 27/30 100/100 [==============================] - ETA: 0s - loss: 0.1171 - acc: 0.9595 Epoch 27: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 118ms/step - loss: 0.1171 - acc: 0.9595 - val_loss: 0.2068 - val_acc: 0.9210 - lr: 1.7798e-06 Epoch 28/30 100/100 [==============================] - ETA: 0s - loss: 0.1251 - acc: 0.9495 Epoch 28: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 119ms/step - loss: 0.1251 - acc: 0.9495 - val_loss: 0.2057 - val_acc: 0.9180 - lr: 1.7798e-06 Epoch 29/30 100/100 [==============================] - ETA: 0s - loss: 0.1200 - acc: 0.9550 Epoch 29: val_acc did not improve from 0.92100 Epoch 29: ReduceLROnPlateau reducing learning rate to 1.3348388847589376e-06. 100/100 [==============================] - 12s 120ms/step - loss: 0.1200 - acc: 0.9550 - val_loss: 0.2073 - val_acc: 0.9180 - lr: 1.7798e-06 Epoch 30/30 100/100 [==============================] - ETA: 0s - loss: 0.1097 - acc: 0.9600 Epoch 30: val_acc did not improve from 0.92100 100/100 [==============================] - 12s 119ms/step - loss: 0.1097 - acc: 0.9600 - val_loss: 0.2077 - val_acc: 0.9160 - lr: 1.3348e-06
fig, axes = plt.subplots(1, 2, squeeze = False, figsize = (16, 8))
# Loss
train_loss_values = history_dict["loss"]
val_loss_values = history_dict["val_loss"]
# Epochs
epochs = range(1, len(train_loss_values) + 1)
# Accuracy
train_acc_values = history_dict["acc"]
val_acc_values = history_dict["val_acc"]
ax = axes.flat[0]
ax.plot(epochs, train_loss_values, "r", label = "Training loss")
ax.plot(epochs, val_loss_values, "b", label = "Validation loss")
ax.set_title("Training and validation Loss")
ax.set_xlabel("Epochs")
ax.set_ylabel("Loss")
ax.legend()
ax = axes.flat[1]
ax.plot(epochs, train_acc_values, "r", label = "Training acc")
ax.plot(epochs, val_acc_values, "b", label = "Validation acc")
ax.set_title("Training and validation Accuracy")
ax.set_xlabel("Epochs")
ax.set_ylabel("Accuracy")
ax.legend()
<matplotlib.legend.Legend at 0x7fc07029f390>
O teste do modelo pode ser realizado a partir da função evaluate, que também suporta generators.
test_datagen = ImageDataGenerator( rescale = 1./255 )
test_generator = test_datagen.flow_from_directory( os.path.join(".", "data", "test"), target_size = (150, 150),
batch_size = 1, class_mode = "binary",
shuffle = False)
# Atributo do generator que fornece o número de amostras detectadas
test_samples = test_generator.samples
test_loss, test_acc = model.evaluate( test_generator )
print("Test Accuracy:", 100 * test_acc, "%")
print("Acertos: {} - Erros: {}".format(round(test_samples * test_acc),
round(test_samples * (1 - test_acc))))
Found 1000 images belonging to 2 classes. 1000/1000 [==============================] - 6s 6ms/step - loss: 0.1985 - acc: 0.9220 Test Accuracy: 92.1999990940094 % Acertos: 922 - Erros: 78
def show_results(paths, ytest, ypred, labels, num = 25, tipo = "rand"):
if tipo == "acertos":
fltr_idx = [i for i in range(ytest.shape[0]) if ypred[i] == ytest[i]]
else:
fltr_idx = [i for i in range(ytest.shape[0]) if ypred[i] != ytest[i]]
indices = np.random.choice(fltr_idx, min(num, len(fltr_idx)), replace=False)
rows = int(num/5)
fig, axs = plt.subplots(nrows = rows, ncols = 5, figsize=(20, 5*rows))
for i, idx in enumerate(indices):
path = os.path.join(".", "data", "test", paths[idx])
img = cv2.imread(path)[:,:,::-1]
if ypred[idx] == ytest[idx]:
axs[i//5][i%5].set_title(labels[ytest[idx]], color = "green", fontsize = 20)
else:
axs[i//5][i%5].set_title("Pred: {}\n Gabarito: {}".format(labels[ypred[idx]],
labels[ytest[idx]]), color = "red",
fontsize = 20)
axs[i//5][i%5].imshow(img, vmin = 0, vmax = 255, cmap = "gray")
axs[i//5][i%5].axis('off')
return
test_generator.reset()
filenames = test_generator.filenames
labels = test_generator.labels
pred_labels = model.predict(test_generator, verbose = 0)
preds = [int(pred[0] > 0.5) for pred in pred_labels]
show_results(filenames, labels, preds, idx_to_class_dict, tipo = "acertos")
show_results(filenames, labels, preds, idx_to_class_dict, tipo = "erros")