Gosto de usar exemplos práticos e projetos para me ajudar a memorizar a teoria durante meu estudo de Redes Neurais Profundas em clínica veterinária. Um excelente recurso para encontrar esses projetos práticos é o Kaggle. Kaggle é uma comunidade online de cientistas de dados e profissionais de aprendizado de máquina.

O Kaggle permite pesquisar e publicar conjuntos de dados, explorar e construir modelos. Você pode executar essas funções em um ambiente baseado na web. O Kaggle também oferece competições de aprendizado de máquina com problemas reais e oferece prêmios aos vencedores.

Atualmente, estou estudando Deep Learning com TensorFlow. Um dos assuntos que quero aprender é reconhecimento de imagem. Este artigo descreve minha tentativa de resolver uma competição anterior do Kaggle de 2013, chamada “Cães vs. Gatos”. Para implementar a solução, usei Python 3.8 e TensorFlow 2.3.0.

O objetivo da competição original “Cães vs. Gatos” era escrever um algoritmo para classificar se as imagens contêm um cão ou um gato. Observe que em 2013 não havia TensorFlow ou outra estrutura como o PyTorch para ajudar.
Embora a competição tenha terminado, ainda é possível fazer o upload e deixar Kaggle marcar suas previsões.

Preparando os dados de treinamento

Antes de criar e treinar um modelo, devemos preparar os dados de treinamento. Os dados de treinamento para esta competição envolvem 25.000 imagens de cães e gatos. O nome do arquivo de cada imagem especifica se a imagem é um cachorro ou um gato.

Para criar um envio para a competição, você deve criar uma previsão de cada uma das 12.500 imagens do conjunto de teste. Você tem que prever se um cachorro ou um gato está na imagem. Você pode pontuar o envio fazendo o upload de um CSV que contém uma linha para cada uma das 12.500 imagens. Cada linha deve conter o id da imagem e a previsão se é um cachorro ou um gato (1 = cachorro, 0 = gato).

Lendo os dados das imagens

O Kaggle fornece os dados de treinamento por meio de um único arquivo zip que contém todas as imagens de cães e gatos.

Para inserir os dados da imagem no modelo durante o treinamento, primeiro temos que carregar uma imagem do disco e transformá-la em um array de bytes. O programa de treinamento então alimenta esse array de bytes junto com o rótulo “gato” ou “cachorro” na rede neural para saber se é um gato ou um cachorro.

Em vez de escrever um programa Python para ler os arquivos do disco, uso ImageDataGenerator do módulo Tensorflow.Keras.Preprocessing. Essa classe pode carregar imagens do disco e gerar lotes de dados de imagem que o processo de treinamento pode usar diretamente. Ele também pode fazer outras coisas, que mostrarei a você mais tarde, quando otimizarmos o modelo.

Para usar o ImageDataGenerator, você deve estruturar seus dados de uma determinada maneira no disco. Para cada rótulo ou categoria que você deseja reconhecer, você deve criar um subdiretório com o mesmo nome do rótulo. Nesse diretório, você coloca as imagens dessa categoria específica.

Distribuindo as imagens de treinamento

Temos que copiar as imagens do zip do Kaggle para a estrutura de diretórios, conforme discutido antes. Além disso, para obter uma indicação de quão bom é nosso modelo, dividimos as imagens de treinamento que obtivemos do Kaggle em um conjunto de treinamento e validação. Por exemplo, reservamos 5.000 fotos das 25.000 para validar o modelo treinado.

A seguinte função Python distribui as imagens de gatos e cachorros da pasta onde você descompactou o zip com as imagens do Kaggle. O processo cria uma estrutura que pode ser usada diretamente pelo ImageDataGenerator.
A função recupera um único parâmetro chamado validation_size. O tamanho da validação é um número entre 0,0 e 1,0, representando a proporção do conjunto de validação na divisão.

Distribuir e dividir os dados de treinamento
A função embaralha a lista de nomes de imagens antes de copiá-los para o diretório correto. Com essa estrutura de diretório, podemos começar a usar o ImageDataGenerator.

Preparando o ImageDataGenerator

Usaremos o método flow_from_directory do ImageDataGenerator. Este método cria um iterador que recupera e retorna imagens do diretório especificado.

Criar o ImageDataGenerator para disponibilizar as imagens ao pipeline de processamento
Eu uso o parâmetro de redimensionamento ao criar uma instância para redimensionar os pixels da imagem automaticamente. Em vez de usar valores RGB que variam de 0 a 255, nós os redimensionamos para um número de ponto flutuante de 0 a 1. Essa redimensionamento torna mais fácil treinar o modelo de aprendizado profundo.

Usamos os seguintes parâmetros com o método flow_from_directory:

diretório – O diretório que contém os subdiretórios com as categorias.

target_size – Cada imagem deve ter o mesmo tamanho antes de ser enviada para o pipeline de aprendizado profundo. O ImageDataGenerator pode fazer isso instantaneamente.

batch_size – enviamos as imagens para o pipeline de aprendizado profundo em lotes. Isso define o tamanho dos lotes individuais.

class_mode –

Uma dica do tipo de dados que lemos. Em nosso caso, ‘binário’ para mostrar que usaremos duas categorias.
Quando executamos o código, o flow_from_directory relata o número de imagens encontradas em quais categorias. Em nosso caso, o gerador de treinamento reporta 20.000 imagens e o gerador de validação reporta 5.000 imagens.

O flow_from_directory reporta as imagens e classes que foram encontradas, imagem do autor
Como o ImageDataGenerator está pronto, podemos começar a criar e treinar o modelo de Deep Learning
Criação e treinamento do modelo de aprendizado profundo

Como preparamos os dados, agora podemos começar a construir o modelo de aprendizado profundo usando o TensorFlow. Adicionamos as camadas usando a instância Sequential.

Defina as várias camadas da Rede Neural Convolucional

Camadas convolucionais

Dividimos o modelo em três partes principais. Primeiro, existem três combinações das camadas Conv2D e MaxPool2D. Estas são chamadas de camadas de convolução. Uma camada Conv2D aplica um filtro à imagem original para amplificar certos recursos da imagem. A camada MaxPool2D reduz o tamanho da imagem e reduz o número de parâmetros necessários necessários. Reduzir o tamanho da imagem aumentará a velocidade de treinamento da rede.

A segunda e real Deep Learning Network começa na linha oito, onde nivelamos a matriz. Criamos uma camada Densa oculta com 512 unidades e usamos a Unidade Linear Retificada (relu) como função de ativação.
A última parte é a camada de saída. Esta última camada na linha dez tem um único neurônio de saída. O neurônio de saída conterá um valor de 0 a 1, onde 0 representa um gato e 1 um cachorro.

Compilando o modelo

Antes de começarmos com o treinamento, temos que compilar o modelo usando o método de compilação. Compilar configura o modelo para treinamento.

Compilar o modelo para configurar

Eu escolho o otimizador Adam, pois este é um excelente padrão para começar. A função de perda é binary_cross_entropy porque estamos treinando um modelo de classificação com duas saídas possíveis. Quero relatar a precisão durante o treinamento; portanto, adicionei precisão como uma métrica.

A segunda linha chama summary () no modelo. Chamar o método de resumo produz uma visão geral das camadas e do número de parâmetros. Esta é uma ótima maneira de validar se você criou tudo o que pretendia criar.

Mostrando o resumo do modelo

O que reconhecemos no resumo é a diminuição do tamanho da imagem nas várias camadas. A imagem começa como uma imagem 150×150 e é reduzida através de várias camadas para uma imagem de tamanho 17×17 antes de entrar na Rede Neural Profunda.

Treinando o modelo

Depois de compilar o modelo, podemos começar a treiná-lo. Podemos iniciar o treinamento chamando o método fit na instância do modelo.

Comece a treinar por 50 épocas

O primeiro parâmetro é o iterador que criamos chamando o flow_from_directory do ImageDataGenerator para os dados de treinamento. O segundo parâmetro é o iterador dos dados de validação.

O parâmetro epoch mostra quantas vezes o modelo processará todo o conjunto de treinamento. Aqui, desejo processar todas as 20.000 imagens de treinamento 50 vezes.

Os steps_per_epoch mostram quantos lotes ele deve processar antes de terminar a época. Estabelecemos anteriormente um tamanho de lote de 200 no flow_from_directory, o que dá 200 * 100 = 20.000 – o número de imagens de nosso conjunto de treinamento.

O mesmo vale para validation_steps, 50 * 100 = 5.000, o número de imagens em nosso conjunto de validação.
Durante o treinamento, o método de ajuste imprime várias métricas. Abaixo, você pode ver que a precisão em nosso conjunto de treinamento ao final do treinamento é de 0,99. A precisão do conjunto de validação é de 0,79.

Saída do console do método de ajuste

O método model.fit retorna um objeto de histórico que contém todos os dados de treinamento assim que o treinamento é concluído. Este objeto de histórico pode ser usado para visualizar as métricas.

Visualizando o resultado do treinamento

Podemos usar o objeto de histórico em combinação com a biblioteca Matplotlib para visualizar a perda e a precisão por época. A função abaixo cria dois gráficos, um que mostra a acurácia do treinamento e validação e outro que mostra a perda do treinamento e validação.

Usando Matplotlib para visualizar o resultado do treinamento

A função plot_result mostra os dois gráficos a seguir se você treinar o modelo para 50 épocas.

A parte interessante é que a precisão do conjunto de treinamento aumenta lentamente para quase 1 (100%). No entanto, a precisão no conjunto de validação aumenta para cerca de 0,8 nas primeiras cinco épocas e, em seguida, flatlines em 0,8. Isso é o mesmo para a perda do conjunto de validação. Ele diminui nas primeiras quatro épocas e, em seguida, aumenta novamente.

Este é um caso óbvio de overfitting. A precisão do conjunto de treinamento aumenta, mas não pode ser generalizada, claramente visível pela estagnação da precisão do conjunto de validação.

Sobreajuste

O sobreajuste ocorre quando o modelo aprende os dados de treinamento tão bem que só pode prever as imagens do conjunto de treinamento. Se o alimentarmos com imagens que não sejam do conjunto de treinamento, ele terá um desempenho ruim.

Podemos superar o overfitting usando mais dados em seu conjunto de dados de treinamento. Aqui, isso é difícil, pois não temos mais imagens de cães e gatos do Kaggle. Felizmente para nós, existem outras possibilidades.

Otimizando o modelo

Antes de criar e enviar uma previsão ao Kaggle, quero ver se podemos otimizar o modelo.

Aumento de imagem

Vimos que a precisão no conjunto de validação diminuiu durante o treinamento. Também discutimos que você poderia melhorar a precisão aumentando a quantidade de dados de treinamento.

Em vez de adicionar mais dados de treinamento, também podemos usar o aumento de imagem. O aumento de imagem é uma técnica para criar novos dados de treinamento artificiais a partir dos dados existentes. O aumento da imagem altera a imagem de treinamento original, redimensionando, girando, recortando e invertendo-a. Ele cria imagens artificialmente para treinamento.

O ImageDataGenerator que usamos pode aumentar as imagens na memória depois de carregá-las do disco. Podemos definir as opções de aumento de imagem quando instanciamos o ImageDataGenerator.

Anteriormente, já usamos o ImageDataGenerator para redimensionar as imagens do disco. Agora, adicionamos opções adicionais para aumentar as imagens.

Adicionando opções de aumento de imagem ao ImageDataGenerator

Instruímos o ImageDataGenerator a girar, deslocar, aplicar zoom, inverter e distorcer dentro de faixas específicas. O ImageDataGenerator executa o aumento aleatoriamente. Se treinarmos novamente o modelo por 50 épocas usando o ImageDataGenerator, veremos os seguintes gráficos de precisão e perda.

Visualizando o resultado do treinamento usando Image Augmentation, imagem do autor

Olhando para o gráfico à esquerda que mostra a precisão da validação, vemos que ela não para em 80%, como vimos no treinamento anterior. Em vez disso, ele sobe lentamente para uma precisão de 90%.

O gráfico à direita mostra a perda para o conjunto de treinamento e validação. Aqui também vemos que eles lenta e firmemente

diminuir.
Um resultado muito melhor!
Criação de uma previsão
Antes de prosseguir com a otimização do modelo, que é algo para outro artigo, devemos fazer o upload de nossas previsões do conjunto de testes para Kaggle para ver como nosso modelo se sai. Depois de treinar o modelo, salvei-o usando o método save no modelo.

No método load_and_predict, primeiro carregamos o modelo e, em seguida, criamos um novo ImageDataGenerator para servir as imagens do conjunto de teste ao modelo. Em seguida, chamamos o método de previsão no modelo para criar as previsões do conjunto de teste.

Carregando o modelo salvo e criando as previsões

O envio ao Kaggle deve estar no formato CSV. Usamos o Pandas para criar um DataFrame a partir da matriz de previsões. Este DataFrame é salvo em um arquivo CSV e carregado no Kaggle. Cada envio é pontuado usando a seguinte função de perda de log.

Minha apresentação do modelo usando a CNN e o aumento de imagem marcou 0,26211. Isso me colocaria em torno do 833º lugar na classificação pública.

Carregando a submissão e visualizando a partitura, imagem do autor
Este é um bom começo, mas quero ver se posso melhorar ainda mais a precisão. Mas isso é algo para outro artigo.

Conclusão

Neste artigo, descrevi como criar e usar uma rede neural de convolução usando o TensorFlow. Esta rede neural foi usada para reconhecer cães e gatos a partir de imagens. As imagens dos cães e gatos foram tiradas desta antiga competição Kaggle Cats vs Dogs.

Embora a competição não esteja mais acontecendo, você ainda pode fazer upload e pontuar suas previsões do conjunto de teste. Minha CNN obteve uma pontuação de 0,26211, o que nos colocou no 833º lugar na classificação pública.

Em um próximo artigo, descreverei como podemos aumentar ainda mais a precisão usando o aprendizado por transferência.

O código-fonte deste artigo pode ser encontrado no GitHub. O repositório é muito grande, pois inclui todas as imagens de treinamento e teste.

Obrigado por ler!