Tutorial: autoencoder como classificador usando o dataset Fashion-MNIST
Neste tutorial, você vai aprender e entender como usar um autoencoder como classificador em Python com Keras. Vamos usar o dataset Fashion-MNIST como exemplo.
Observação: este tutorial foca principalmente na implementação prática de classificação usando rede neural convolucional e autoencoder convolucional. Se você ainda não está familiarizado com convolutional neural network (CNN) e autoencoder, vale dar uma olhada nos tutoriais de CNN e Autoencoder.
Mais especificamente, você vai abordar os seguintes tópicos neste tutorial:
Em seguida, você vai preprocessar os dados: aprender a redimensionar, reescalar, verificar os tipos das imagens e dividir seu conjunto em treino e validação.
Com tudo pronto, você constrói o modelo de autoencoder convolucional: aprende a modelar os dados e montar a rede. Depois, compila e treina o modelo, visualiza as curvas de acurácia e loss e, por fim, salva o modelo.
Na sequência, você vai segmentar os dados do Fashion-MNIST: primeiro converte os rótulos para one-hot encoding, divide as imagens de treino e validação com seus respectivos rótulos. Depois define a mesma função de encoder usada na arquitetura do autoencoder e acrescenta camadas totalmente conectadas.
Você aprenderá a carregar os pesos de um modelo treinado em algumas camadas do novo modelo, conferir as matrizes de pesos do modelo treinado e do novo, “congelar” algumas camadas do novo modelo e, por fim, compilar o novo modelo de classificação, treiná-lo e salvar os pesos.
Você vai re-treinar o modelo com todas as camadas treináveis, avaliar o modelo, visualizar as curvas de acurácia e loss, fazer previsões nos dados de teste, converter probabilidades em rótulos de classe e plotar algumas amostras de teste que o modelo classificou corretamente e incorretamente.
Por fim, você vai visualizar o relatório de classificação, que dá uma visão mais profunda sobre quais classes foram (in)corretamente classificadas pelo seu modelo.
O conjunto de dados Fashion-MNIST
Antes de carregar e processar o dataset, é bom ter uma noção do tipo de dados, suas dimensões e quantas classes diferentes existem.
O Fashion-MNIST é um conjunto de 70.000 imagens em tons de cinza 28x28 de produtos de moda em 10 categorias, com 7.000 imagens por categoria. O conjunto de treino tem 60.000 imagens, e o de teste tem 10.000. O Fashion-MNIST foi proposto como substituto do MNIST original para produzir resultados mais desafiadores; as dimensões das imagens e as divisões de treino e teste são semelhantes às do MNIST. O dataset está disponível gratuitamente neste link e pode ser carregado tanto com TensorFlow quanto com Keras sem precisar baixá-lo manualmente para sua máquina.
Assim como o MNIST, o Fashion-MNIST também tem 10 classes, mas em vez de dígitos manuscritos, temos 10 tipos de itens de moda, como sandálias, camisa, calças etc.
A tarefa aqui é treinar um autoencoder convolucional e usar a parte do encoder, combinada com camadas totalmente conectadas, para reconhecer corretamente novas amostras do conjunto de teste.
Dica: se você quer aprender a implementar um Multi-Layer Perceptron (MLP) para tarefas de classificação com o MNIST, confira este tutorial.
No código abaixo, definimos variáveis de ambiente no notebook usando os.environ. É uma boa prática fazer isso antes de inicializar o Keras para limitar o backend TensorFlow a usar a primeira GPU. Se a máquina em que você treina tiver a GPU em 0, certifique-se de usar 0 em vez de 1. Você pode verificar rodando um comando simples no terminal, por exemplo: nvidia-smi
import os
os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"]="0"#model will be trained on GPU 0Tecnologia
Carregando os dados
Agora, importe todos os módulos necessários como numpy, matplotlib e, principalmente, Keras, já que neste tutorial usaremos Keras como framework!
import keras
from matplotlib import pyplot as plt
import numpy as np
import gzip
%matplotlib inline
from keras.models import Model
from keras.optimizers import RMSprop
from keras.layers import Input,Dense,Flatten,Dropout,merge,Reshape,Conv2D,MaxPooling2D,UpSampling2D,Conv2DTranspose
from keras.layers.normalization import BatchNormalization
from keras.models import Model,Sequential
from keras.callbacks import ModelCheckpoint
from keras.optimizers import Adadelta, RMSprop,SGD,Adam
from keras import regularizers
from keras import backend as K
from keras.utils import to_categorical
Tecnologia
Using TensorFlow backend./usr/local/lib/python2.7/dist-packages/h5py/__init__.py:34: FutureWarning: Conversion of the second argument of issubdtype from`float` to `np.floating` is deprecated. In future, it will be treated as`np.float64 == np.dtype(float).type`.
from ._conv import register_converters as _register_converters
Tecnologia
Aqui, definimos uma função que abre o arquivo gzip e lê o conteúdo com bytestream.read(). Você passa a dimensão da imagem e o número total de imagens para essa função. Em seguida, com np.frombuffer(), converte a string armazenada em buf em um array NumPy do tipo float32.
Depois de convertido em array NumPy, remodelamos o array para três dimensões (tensor), onde a primeira é o número de imagens e a segunda e a terceira são as dimensões da imagem. Por fim, retornamos o array NumPy data.
defextract_data(filename, num_images):with gzip.open(filename)as bytestream:
bytestream.read(16)
buf = bytestream.read(28*28* num_images)
data = np.frombuffer(buf, dtype=np.uint8).astype(np.float32)
data = data.reshape(num_images,28,28)return data
Tecnologia
Agora chamamos a função extract_data() passando os arquivos de treino e teste com seus respectivos números de imagens.
De forma semelhante, definimos uma função para extrair rótulos que abre o arquivo gzip, lê o conteúdo com bytestream.read(), passando a dimensão do rótulo (1) e o número total de imagens. Com np.frombuffer(), convertemos a string em buf para um array NumPy do tipo int64.
Dessa vez, não precisamos remodelar o array, pois labels retornará um vetor coluna de dimensão 60.000 x 1. Por fim, retornamos o array NumPy labels.
Com os dados de treino e teste carregados, já dá para analisar os dados e ganhar intuição sobre o dataset com que vamos trabalhar neste tutorial!
Exploração de dados
Vamos ver como são algumas imagens do dataset e checar as dimensões com o atributo .shape de NumPy:
# Shapes do conjunto de treinoprint("Training set (images) shape: {shape}".format(shape=train_data.shape))# Shapes do conjunto de testeprint("Test set (images) shape: {shape}".format(shape=test_data.shape))Tecnologia
Training set(images) shape:(60000,28,28)
Test set(images) shape:(10000,28,28)Tecnologia
Pelo output acima, o treino tem shape 60000 x 28 x 28, pois há 60.000 amostras de treino, cada uma uma matriz 28 x 28. O teste tem shape 10000 x 28 x 28 porque há 10.000 amostras de teste.
Observação: na tarefa de reconstrução com autoencoder convolucional você não precisa de rótulos de treino e teste. Suas imagens de treino serão tanto a entrada quanto o ground truth, de forma análoga aos rótulos em tarefas de classificação.
Mas, para a tarefa de classificação, você também vai precisar dos rótulos junto com as imagens — isso será feito mais adiante. Mesmo que aqui lidemos apenas com imagens de treino e teste, para fins de exploração, que ajudam a entender melhor os dados, usaremos os rótulos.
Vamos criar um dicionário com os nomes das classes e seus rótulos categóricos correspondentes:
# Dicionário de classes-alvo
label_dict ={0:'A',1:'B',2:'C',3:'D',4:'E',5:'F',6:'G',7:'H',8:'I',9:'J',}Tecnologia
Agora, vamos dar uma olhada em algumas imagens do dataset:
plt.figure(figsize=[5,5])# Mostra a primeira imagem no treino
plt.subplot(121)
curr_img = np.reshape(train_data[10],(28,28))
curr_lbl = train_labels[10]
plt.imshow(curr_img, cmap='gray')
plt.title("(Label: "+str(label_dict[curr_lbl])+")")# Mostra a primeira imagem no teste
plt.subplot(122)
curr_img = np.reshape(test_data[10],(28,28))
curr_lbl = test_labels[10]
plt.imshow(curr_img, cmap='gray')
plt.title("(Label: "+str(label_dict[curr_lbl])+")")Tecnologia
O resultado dos dois gráficos acima mostra uma amostra do treino e uma do teste. Essas imagens recebem rótulos de classe como 0 ou A e 4 ou E. Outras letras terão rótulos diferentes, mas a mesma letra compartilha o mesmo rótulo. Isso significa que todas as 6.000 imagens da classe E terão rótulo 4.
Pré-processamento de dados
As imagens são em tons de cinza, com valores de pixel entre 0 e 255 e dimensão 28 x 28. Antes de alimentar o modelo, é fundamental preprocessar. Primeiro, vamos converter cada imagem 28 x 28 do treino e do teste em uma matriz 28 x 28 x 1, que pode ser enviada à rede:
Depois, verifique o tipo dos arrays de treino e teste. Eles devem estar em float32; se não estiverem, converta. Como já convertemos na leitura, não é preciso repetir. Também é preciso reescalar os pixels para a faixa 0–1. Vamos lá!
Depois disso, é importante particionar os dados. Para o modelo generalizar bem, dividimos o treino em duas partes: treino e validação. Vamos treinar em 80% e validar em 20% dos dados de treino.
Isso também ajuda a reduzir overfitting, pois o modelo é validado em dados que ele não viu durante o treino.
Use o train_test_split do scikit-learn para dividir corretamente:
from sklearn.model_selection import train_test_split
train_X,valid_X,train_ground,valid_ground = train_test_split(train_data,
train_data,
test_size=0.2,
random_state=13)Tecnologia
Observação: você usará essa divisão duas vezes: na reconstrução com autoencoder convolucional, em que não precisa de rótulos — por isso passamos as imagens de treino duas vezes, atuando como entrada e como ground truth — e, depois, na classificação, em que você também passará os rótulos.
Pronto para definir a rede e alimentar os dados. Vamos ao próximo passo!
O autoencoder convolucional
As imagens têm tamanho 28 x 28 x 1. Convertendo a matriz em array, reescalando para 0 a 1, remodelando para 28 x 28 x 1, alimentamos isso como entrada da rede.
Usaremos batch size 128 (valores maiores como 256 ou 512 também podem funcionar melhor, depende do seu sistema). Esse parâmetro impacta fortemente o aprendizado e a acurácia.
batch_size =64
epochs =200
inChannel =1
x, y =28,28
input_img = Input(shape =(x, y, inChannel))
num_classes =10Tecnologia
Como você já deve saber, o autoencoder tem duas partes: encoder e decoder.
Encoder: 4 blocos convolucionais; cada bloco tem uma camada convolucional seguida de batch normalization. Camadas de max-pooling são usadas após o primeiro e o segundo blocos.
O primeiro bloco tem 32 filtros 3 x 3, seguido de downsampling (max-pooling),
O segundo bloco tem 64 filtros 3 x 3, seguido de outro downsampling,
O terceiro bloco do encoder tem 128 filtros 3 x 3,
O quarto bloco do encoder tem 256 filtros 3 x 3.
Decoder: 3 blocos convolucionais; cada bloco tem uma camada convolucional seguida de batch normalization. Camadas de upsampling são usadas após o segundo e o terceiro blocos.
O primeiro bloco tem 128 filtros 3 x 3,
O segundo bloco tem 64 filtros 3 x 3 seguido de upsampling,
O terceiro bloco tem 32 filtros 3 x 3 seguido de upsampling,
A camada final do encoder tem 1 filtro 3 x 3 que reconstrói a entrada com um único canal.
O max-pooling reduz a entrada pela metade a cada uso; o upsampling aumenta pela metade a cada uso.
Observação: número de filtros, tamanho do filtro, número de camadas, épocas de treino etc. são hiperparâmetros e devem ser escolhidos com base na sua intuição. Sinta-se à vontade para experimentar e medir a performance. É assim que você desenvolve o “tato” em deep learning!
Vamos criar funções separadas de encoder e decoder, já que os pesos do encoder serão usados depois na classificação!
defencoder(input_img):#encoder#input = 28 x 28 x 1 (wide and thin)
conv1 = Conv2D(32,(3,3), activation='relu', padding='same')(input_img)#28 x 28 x 32
conv1 = BatchNormalization()(conv1)
conv1 = Conv2D(32,(3,3), activation='relu', padding='same')(conv1)
conv1 = BatchNormalization()(conv1)
pool1 = MaxPooling2D(pool_size=(2,2))(conv1)#14 x 14 x 32
conv2 = Conv2D(64,(3,3), activation='relu', padding='same')(pool1)#14 x 14 x 64
conv2 = BatchNormalization()(conv2)
conv2 = Conv2D(64,(3,3), activation='relu', padding='same')(conv2)
conv2 = BatchNormalization()(conv2)
pool2 = MaxPooling2D(pool_size=(2,2))(conv2)#7 x 7 x 64
conv3 = Conv2D(128,(3,3), activation='relu', padding='same')(pool2)#7 x 7 x 128 (small and thick)
conv3 = BatchNormalization()(conv3)
conv3 = Conv2D(128,(3,3), activation='relu', padding='same')(conv3)
conv3 = BatchNormalization()(conv3)
conv4 = Conv2D(256,(3,3), activation='relu', padding='same')(conv3)#7 x 7 x 256 (small and thick)
conv4 = BatchNormalization()(conv4)
conv4 = Conv2D(256,(3,3), activation='relu', padding='same')(conv4)
conv4 = BatchNormalization()(conv4)return conv4
defdecoder(conv4):#decoder
conv5 = Conv2D(128,(3,3), activation='relu', padding='same')(conv4)#7 x 7 x 128
conv5 = BatchNormalization()(conv5)
conv5 = Conv2D(128,(3,3), activation='relu', padding='same')(conv5)
conv5 = BatchNormalization()(conv5)
conv6 = Conv2D(64,(3,3), activation='relu', padding='same')(conv5)#7 x 7 x 64
conv6 = BatchNormalization()(conv6)
conv6 = Conv2D(64,(3,3), activation='relu', padding='same')(conv6)
conv6 = BatchNormalization()(conv6)
up1 = UpSampling2D((2,2))(conv6)#14 x 14 x 64
conv7 = Conv2D(32,(3,3), activation='relu', padding='same')(up1)# 14 x 14 x 32
conv7 = BatchNormalization()(conv7)
conv7 = Conv2D(32,(3,3), activation='relu', padding='same')(conv7)
conv7 = BatchNormalization()(conv7)
up2 = UpSampling2D((2,2))(conv7)# 28 x 28 x 32
decoded = Conv2D(1,(3,3), activation='sigmoid', padding='same')(up2)# 28 x 28 x 1return decoded
Tecnologia
Após criar o modelo, compile-o usando o otimizador RMSProp.
Também é preciso especificar a função de loss. Aqui usamos mean squared error, pois o loss após cada batch será calculado entre a saída prevista e o ground truth, pixel a pixel:
Hora de treinar com fit() do Keras! O modelo treina por 200 épocas. A função fit() retorna um objeto de histórico; guardando o resultado em autoencoder_train, você poderá depois plotar as curvas de loss de treino e validação para analisar a performance visualmente.
Pronto! Você treinou o modelo no Fashion-MNIST por 100 épocas. Agora, vamos plotar as curvas de loss de treino e validação para visualizar a performance.
Repare que as curvas de loss de treino e validação estão alinhadas. Isso indica que seu modelo não está sofrendo overfitting: o loss de validação cai e não aumenta, e há pouca diferença entre as curvas ao longo do treino.
Podemos dizer que a capacidade de generalização está boa.
Mas a tarefa aqui é usar o encoder do modelo treinado para classificar as imagens do Fashion-MNIST. Vamos para a próxima parte!
Salvar o modelo
Como você vai precisar dos pesos do encoder na classificação, vamos salvar primeiro os pesos completos do autoencoder. Em seguida, veremos como extrair os pesos do encoder.
Agora vamos usar o “topo” do autoencoder treinado, isto é, o encoder, e carregar os pesos do autoencoder apenas na parte do encoder do novo modelo.
Depois, adicionaremos algumas camadas densas totalmente conectadas ao encoder para classificar as imagens do Fashion-MNIST.
Primeiro, vamos converter os rótulos para vetores one-hot.
Para quem não conhece one-hot encoding:
No one-hot encoding, você converte dados categóricos em um vetor numérico. Fazemos isso porque algoritmos de machine learning não trabalham diretamente com categorias. Criamos uma coluna booleana para cada categoria/classe; apenas uma delas terá valor 1 por amostra — daí o nome one-hot.
No nosso problema, o one-hot será um vetor linha 1 x 10 para cada imagem. O detalhe importante: o vetor tem zeros em todas as posições, exceto na posição da classe correspondente, que vale 1.
Vamos converter os rótulos para one-hot:
# Converte os rótulos categóricos para one-hot
train_Y_one_hot = to_categorical(train_labels)
test_Y_one_hot = to_categorical(test_labels)# Exibe a mudança após o one-hot encodingprint('Original label:', train_labels[0])print('After conversion to one-hot:', train_Y_one_hot[0])Tecnologia
('Original label:',9)('After conversion to one-hot:',array([0.,0.,0.,0.,0.,0.,0.,0.,0.,1.]))Tecnologia
Bem claro, né?
Agora vamos repetir a divisão em treino e validação, como fizemos na fase do autoencoder. Use o mesmo random_state de antes e, desta vez, passe também os rótulos já convertidos para one-hot.
for l1,l2 inzip(full_model.layers[:19],autoencoder.layers[0:19]):
l1.set_weights(l2.get_weights())Tecnologia
Observação: o próximo passo é muito importante. Para garantir que os pesos do encoder do autoencoder são iguais aos carregados no encoder do modelo de classificação, sempre imprima os pesos de alguma camada correspondente em ambos os modelos. Se não forem iguais, não vale a pena usar a estratégia de classificação com autoencoder.
Vamos imprimir os pesos da primeira camada de ambos os modelos.
Perfeito! Os arrays são idênticos. Então vamos compilar e começar o treino.
Agora, vamos tornar a parte do encoder — as primeiras dezenove camadas — não treinável. Como o encoder já foi treinado, não precisamos ajustá-lo; vamos treinar apenas a parte totalmente conectada.
for layer in full_model.layers[0:19]:
layer.trainable =FalseTecnologia
Hora de treinar com fit() do Keras! O modelo treina por 10 épocas. A função fit() retorna um histórico; guardando em fashion_train (aqui classify_train), você poderá depois plotar as curvas de acurácia e loss de treino e validação para avaliar a performance visualmente.
Pronto! Você treinou o modelo no Fashion-MNIST por apenas 10 épocas e, observando a acurácia e o loss de treino, dá para dizer que o modelo foi muito bem: após 10 épocas a acurácia de treino está em 99% e a validação em 98%.
Nos dois gráficos acima, dá para ver overfitting, pois há um grande gap entre os losses de treino e validação. Para reduzir overfitting, experimente técnicas de regularização como Dropout. Você pode seguir este tutorial de CNN em Python com Keras.
Avaliação no conjunto de teste
Por fim, vamos avaliar o modelo nos dados de teste e ver como ele se sai!
Como as previsões são valores de ponto flutuante, não é prático comparar diretamente com os rótulos verdadeiros. Vamos arredondar a saída para inteiros e usar np.argmax() para selecionar o índice com maior valor em cada linha.
Por exemplo, se a previsão para uma imagem for [0 1 0 0 0 0 0 0 0 0], o rótulo previsto deve ser 1.
O relatório de classificação ajuda a identificar com mais detalhe as classes com erros. Você poderá observar em quais classes o modelo teve pior desempenho entre as dez disponíveis.
from sklearn.metrics import classification_report
target_names =["Class {}".format(i)for i inrange(num_classes)]print(classification_report(test_labels, predicted_classes, target_names=target_names))Tecnologia
precision recall f1-score support
Class 00.830.890.861000
Class 10.990.990.991000
Class 20.890.870.881000
Class 30.920.930.921000
Class 40.850.900.881000
Class 50.990.990.991000
Class 60.810.720.761000
Class 70.960.980.971000
Class 80.980.980.981000
Class 90.980.960.971000
avg / total 0.920.920.9210000Tecnologia
Quer se aprofundar?
Este tutorial foi um ótimo começo para usar autoencoder e uma rede neural convolucional totalmente conectada com Python e Keras. Se você acompanhou tranquilo (ou com um pouco de esforço), mandou bem! Tente fazer alguns experimentos, talvez com a mesma arquitetura, mas usando outros datasets públicos.
Ainda há muito o que explorar. Que tal fazer o curso Deep Learning in Python da DataCamp? Enquanto isso, não deixe de consultar a documentação do Keras, se ainda não fez isso. Lá você encontra mais exemplos e informações sobre funções, argumentos, camadas etc. É um recurso indispensável para quem está aprendendo a trabalhar com redes neurais em Python!
Se você prefere um livro que explica os fundamentos de deep learning (com Keras) e como aplicar na prática, vale muito ler Deep Learning in Python, do François Chollet.
Aprenda os princípios básicos, as aplicações e os benefícios práticos dos autoencodificadores variacionais e acompanhe uma implementação passo a passo com o PyTorch.
Observação: este tutorial foca principalmente na implementação prática de classificação usando rede neural convolucional e autoencoder convolucional. Se você ainda não está familiarizado com convolutional neural network (CNN) e autoencoder, vale dar uma olhada nos tutoriais de CNN e Autoencoder.
Mais especificamente, você vai abordar os seguintes tópicos neste tutorial:
O conjunto de dados Fashion-MNIST
Antes de carregar e processar o dataset, é bom ter uma noção do tipo de dados, suas dimensões e quantas classes diferentes existem.
O Fashion-MNIST é um conjunto de 70.000 imagens em tons de cinza 28x28 de produtos de moda em 10 categorias, com 7.000 imagens por categoria. O conjunto de treino tem 60.000 imagens, e o de teste tem 10.000. O Fashion-MNIST foi proposto como substituto do MNIST original para produzir resultados mais desafiadores; as dimensões das imagens e as divisões de treino e teste são semelhantes às do MNIST. O dataset está disponível gratuitamente neste link e pode ser carregado tanto com TensorFlow quanto com Keras sem precisar baixá-lo manualmente para sua máquina.
Assim como o MNIST, o Fashion-MNIST também tem 10 classes, mas em vez de dígitos manuscritos, temos 10 tipos de itens de moda, como sandálias, camisa, calças etc.
A tarefa aqui é treinar um autoencoder convolucional e usar a parte do encoder, combinada com camadas totalmente conectadas, para reconhecer corretamente novas amostras do conjunto de teste.
Dica: se você quer aprender a implementar um Multi-Layer Perceptron (MLP) para tarefas de classificação com o MNIST, confira este tutorial.
No código abaixo, definimos variáveis de ambiente no notebook usando os.environ. É uma boa prática fazer isso antes de inicializar o Keras para limitar o backend TensorFlow a usar a primeira GPU. Se a máquina em que você treina tiver a GPU em 0, certifique-se de usar 0 em vez de 1. Você pode verificar rodando um comando simples no terminal, por exemplo: nvidia-smi
Carregando os dados
Agora, importe todos os módulos necessários como numpy, matplotlib e, principalmente, Keras, já que neste tutorial usaremos Keras como framework!
Aqui, definimos uma função que abre o arquivo
gzipe lê o conteúdo combytestream.read(). Você passa a dimensão da imagem e o número total de imagens para essa função. Em seguida, comnp.frombuffer(), converte a string armazenada embufem um array NumPy do tipofloat32.Depois de convertido em array NumPy, remodelamos o array para três dimensões (tensor), onde a primeira é o número de imagens e a segunda e a terceira são as dimensões da imagem. Por fim, retornamos o array NumPy
data.Agora chamamos a função
extract_data()passando os arquivos de treino e teste com seus respectivos números de imagens.De forma semelhante, definimos uma função para extrair rótulos que abre o arquivo
gzip, lê o conteúdo combytestream.read(), passando a dimensão do rótulo (1) e o número total de imagens. Comnp.frombuffer(), convertemos a string embufpara um array NumPy do tipoint64.Dessa vez, não precisamos remodelar o array, pois
labelsretornará um vetor coluna de dimensão 60.000 x 1. Por fim, retornamos o array NumPylabels.Agora chamamos a função para extrair rótulos passando os arquivos de rótulos de treino e teste com seus respectivos números de imagens.
Com os dados de treino e teste carregados, já dá para analisar os dados e ganhar intuição sobre o dataset com que vamos trabalhar neste tutorial!
Exploração de dados
Vamos ver como são algumas imagens do dataset e checar as dimensões com o atributo
.shapede NumPy:Pelo output acima, o treino tem shape 60000 x 28 x 28, pois há 60.000 amostras de treino, cada uma uma matriz 28 x 28. O teste tem shape 10000 x 28 x 28 porque há 10.000 amostras de teste.
Observação: na tarefa de reconstrução com autoencoder convolucional você não precisa de rótulos de treino e teste. Suas imagens de treino serão tanto a entrada quanto o ground truth, de forma análoga aos rótulos em tarefas de classificação.
Mas, para a tarefa de classificação, você também vai precisar dos rótulos junto com as imagens — isso será feito mais adiante. Mesmo que aqui lidemos apenas com imagens de treino e teste, para fins de exploração, que ajudam a entender melhor os dados, usaremos os rótulos.
Vamos criar um dicionário com os nomes das classes e seus rótulos categóricos correspondentes:
Agora, vamos dar uma olhada em algumas imagens do dataset:
O resultado dos dois gráficos acima mostra uma amostra do treino e uma do teste. Essas imagens recebem rótulos de classe como
0ouAe4ouE. Outras letras terão rótulos diferentes, mas a mesma letra compartilha o mesmo rótulo. Isso significa que todas as 6.000 imagens da classeEterão rótulo4.Pré-processamento de dados
As imagens são em tons de cinza, com valores de pixel entre 0 e 255 e dimensão 28 x 28. Antes de alimentar o modelo, é fundamental preprocessar. Primeiro, vamos converter cada imagem 28 x 28 do treino e do teste em uma matriz 28 x 28 x 1, que pode ser enviada à rede:
Depois, verifique o tipo dos arrays de treino e teste. Eles devem estar em float32; se não estiverem, converta. Como já convertemos na leitura, não é preciso repetir. Também é preciso reescalar os pixels para a faixa 0–1. Vamos lá!
Não esqueça de verificar os tipos:
Agora, reescale os dados de treino e teste pelo valor máximo de pixel:
Vamos confirmar que o valor máximo agora é 1.0:
Depois disso, é importante particionar os dados. Para o modelo generalizar bem, dividimos o treino em duas partes: treino e validação. Vamos treinar em 80% e validar em 20% dos dados de treino.
Isso também ajuda a reduzir overfitting, pois o modelo é validado em dados que ele não viu durante o treino.
Use o
train_test_splitdo scikit-learn para dividir corretamente:Observação: você usará essa divisão duas vezes: na reconstrução com autoencoder convolucional, em que não precisa de rótulos — por isso passamos as imagens de treino duas vezes, atuando como entrada e como ground truth — e, depois, na classificação, em que você também passará os rótulos.
Pronto para definir a rede e alimentar os dados. Vamos ao próximo passo!
O autoencoder convolucional
As imagens têm tamanho 28 x 28 x 1. Convertendo a matriz em array, reescalando para 0 a 1, remodelando para 28 x 28 x 1, alimentamos isso como entrada da rede.
Usaremos batch size 128 (valores maiores como 256 ou 512 também podem funcionar melhor, depende do seu sistema). Esse parâmetro impacta fortemente o aprendizado e a acurácia.
Como você já deve saber, o autoencoder tem duas partes: encoder e decoder.
Encoder: 4 blocos convolucionais; cada bloco tem uma camada convolucional seguida de batch normalization. Camadas de max-pooling são usadas após o primeiro e o segundo blocos.
Decoder: 3 blocos convolucionais; cada bloco tem uma camada convolucional seguida de batch normalization. Camadas de upsampling são usadas após o segundo e o terceiro blocos.
O max-pooling reduz a entrada pela metade a cada uso; o upsampling aumenta pela metade a cada uso.
Observação: número de filtros, tamanho do filtro, número de camadas, épocas de treino etc. são hiperparâmetros e devem ser escolhidos com base na sua intuição. Sinta-se à vontade para experimentar e medir a performance. É assim que você desenvolve o “tato” em deep learning!
Vamos criar funções separadas de encoder e decoder, já que os pesos do encoder serão usados depois na classificação!
Após criar o modelo, compile-o usando o otimizador RMSProp.
Também é preciso especificar a função de loss. Aqui usamos mean squared error, pois o loss após cada batch será calculado entre a saída prevista e o ground truth, pixel a pixel:
Vamos visualizar as camadas com summary(). Isso mostra o número de parâmetros (pesos e vieses) por camada e o total do modelo.
Hora de treinar com fit() do Keras! O modelo treina por 200 épocas. A função fit() retorna um objeto de histórico; guardando o resultado em autoencoder_train, você poderá depois plotar as curvas de loss de treino e validação para analisar a performance visualmente.
Treinar o modelo
Pronto! Você treinou o modelo no Fashion-MNIST por 100 épocas. Agora, vamos plotar as curvas de loss de treino e validação para visualizar a performance.
Repare que as curvas de loss de treino e validação estão alinhadas. Isso indica que seu modelo não está sofrendo overfitting: o loss de validação cai e não aumenta, e há pouca diferença entre as curvas ao longo do treino.
Podemos dizer que a capacidade de generalização está boa.
Mas a tarefa aqui é usar o encoder do modelo treinado para classificar as imagens do Fashion-MNIST. Vamos para a próxima parte!
Salvar o modelo
Como você vai precisar dos pesos do encoder na classificação, vamos salvar primeiro os pesos completos do autoencoder. Em seguida, veremos como extrair os pesos do encoder.
Segmentando as imagens do Fashion-MNIST
Agora vamos usar o “topo” do autoencoder treinado, isto é, o encoder, e carregar os pesos do autoencoder apenas na parte do encoder do novo modelo.
Depois, adicionaremos algumas camadas densas totalmente conectadas ao encoder para classificar as imagens do Fashion-MNIST.
Para quem não conhece one-hot encoding:
No one-hot encoding, você converte dados categóricos em um vetor numérico. Fazemos isso porque algoritmos de machine learning não trabalham diretamente com categorias. Criamos uma coluna booleana para cada categoria/classe; apenas uma delas terá valor 1 por amostra — daí o nome one-hot.
No nosso problema, o one-hot será um vetor linha 1 x 10 para cada imagem. O detalhe importante: o vetor tem zeros em todas as posições, exceto na posição da classe correspondente, que vale 1.
Vamos converter os rótulos para one-hot:
Bem claro, né?
Mais uma checada nos shapes de treino e validação:
Agora vamos definir o modelo de classificação. Lembre-se: usaremos exatamente o mesmo encoder da arquitetura do autoencoder.
Agora, as camadas totalmente conectadas que serão empilhadas após o encoder.
Observação: o próximo passo é muito importante. Para garantir que os pesos do encoder do autoencoder são iguais aos carregados no encoder do modelo de classificação, sempre imprima os pesos de alguma camada correspondente em ambos os modelos. Se não forem iguais, não vale a pena usar a estratégia de classificação com autoencoder.
Vamos imprimir os pesos da primeira camada de ambos os modelos.
Perfeito! Os arrays são idênticos. Então vamos compilar e começar o treino.
Agora, vamos tornar a parte do encoder — as primeiras dezenove camadas — não treinável. Como o encoder já foi treinado, não precisamos ajustá-lo; vamos treinar apenas a parte totalmente conectada.
Vamos compilar o modelo!
Vamos imprimir o summary. Devem existir parâmetros não treináveis, já que tornamos as primeiras camadas não treináveis.
Treinar o modelo
Hora de treinar com fit() do Keras! O modelo treina por 10 épocas. A função fit() retorna um histórico; guardando em fashion_train (aqui classify_train), você poderá depois plotar as curvas de acurácia e loss de treino e validação para avaliar a performance visualmente.
Pronto! Você treinou o modelo no Fashion-MNIST por apenas 10 épocas e, observando a acurácia e o loss de treino, dá para dizer que o modelo foi muito bem: após 10 épocas a acurácia de treino está em 99% e a validação em 98%.
Vamos salvar o modelo de classificação!
Em seguida, vamos re-treinar o modelo tornando as primeiras dezenove camadas treináveis (True) em vez de mantê-las congeladas. Vamos lá.
Agora vamos treinar o modelo completo pela última vez!
Vamos salvar o modelo pela última vez.
Agora, vamos colocar a avaliação em perspectiva e plotar as curvas de acurácia e loss de treino e validação:
Nos dois gráficos acima, dá para ver overfitting, pois há um grande gap entre os losses de treino e validação. Para reduzir overfitting, experimente técnicas de regularização como Dropout. Você pode seguir este tutorial de CNN em Python com Keras.
Avaliação no conjunto de teste
Por fim, vamos avaliar o modelo nos dados de teste e ver como ele se sai!
Prever rótulos
Como as previsões são valores de ponto flutuante, não é prático comparar diretamente com os rótulos verdadeiros. Vamos arredondar a saída para inteiros e usar np.argmax() para selecionar o índice com maior valor em cada linha.
Por exemplo, se a previsão para uma imagem for [0 1 0 0 0 0 0 0 0 0], o rótulo previsto deve ser 1.
Relatório de classificação
O relatório de classificação ajuda a identificar com mais detalhe as classes com erros. Você poderá observar em quais classes o modelo teve pior desempenho entre as dez disponíveis.
Quer se aprofundar?
Este tutorial foi um ótimo começo para usar autoencoder e uma rede neural convolucional totalmente conectada com Python e Keras. Se você acompanhou tranquilo (ou com um pouco de esforço), mandou bem! Tente fazer alguns experimentos, talvez com a mesma arquitetura, mas usando outros datasets públicos.
Ainda há muito o que explorar. Que tal fazer o curso Deep Learning in Python da DataCamp? Enquanto isso, não deixe de consultar a documentação do Keras, se ainda não fez isso. Lá você encontra mais exemplos e informações sobre funções, argumentos, camadas etc. É um recurso indispensável para quem está aprendendo a trabalhar com redes neurais em Python!
Se você prefere um livro que explica os fundamentos de deep learning (com Keras) e como aplicar na prática, vale muito ler Deep Learning in Python, do François Chollet.