Pedro's dev blog

Cães x Gatos : Classificação de imagens

DXC.png
Published on
/36 mins read/---

Imagine que queremos aprender a tocar melódica, um instrumento de sopro em forma de teclado portátil. Sem formação musical, e sendo a melódica o nosso primeiro instrumento, poderemos levar alguns meses para nos tornarmos proficientes em tocá-la. Por outro lado, se já tivéssemos habilidade para tocar outro instrumento, como o piano, isso poderia levar apenas alguns dias, dada a semelhança entre os dois instrumentos. Pegar os aprendizados de uma tarefa e ajustá-los em uma tarefa semelhante é algo que fazemos frequentemente na vida real . Quanto mais semelhantes forem as duas tarefas, mais fácil será adaptar o aprendizado de uma tarefa para outra.

Podemos aplicar esse fenômeno da vida real ao mundo do aprendizado profundo.Iniciar um projeto de aprendizagem profunda pode ser relativamente rápido quando se utiliza um modelo pré-treinado, que reutiliza o conhecimento aprendido durante o treinamento e o adapta à tarefa em questão. Este processo é conhecido como aprendizagem por transferência.

Nesse Tutorial, usamos a aprendizagem por transferência para modificar modelos existentes, treinando nosso próprio classificador em minutos usando Keras. Ao final deste capítulo, teremos diversas ferramentas em nosso arsenal para criar classificadores de imagens de alta precisão para qualquer tarefa. ![[Pasted image 20240824145811.png]]

Adaptação de modelos pré-treinados a novas tarefas

Antes de discutirmos o processo de aprendizagem por transferência, é importante revisarmos as principais razões que levaram ao boom da aprendizagem profunda:

  1. Disponibilidade de conjuntos de dados maiores e de melhor qualidade, como o [[[[Imagenet]]]].
  2. Melhorias na computação disponível: GPUs mais rápidas e acessíveis.
  3. Avanços em algoritmos: melhorias em arquiteturas de modelo, otimizadores e procedimentos de treinamento.
  4. Disponibilidade de modelos pré-treinados: modelos que levaram meses para serem treinados, mas que podem ser rapidamente reutilizados.

O último ponto é, provavelmente, uma das maiores razões para a adoção generalizada da aprendizagem profunda. Se cada tarefa de treinamento durasse um mês, apenas um pequeno número de pesquisadores com grandes recursos financeiros estaria trabalhando nessa área. Graças à aprendizagem por transferência, o herói subestimado do treinamento de modelos, agora podemos adaptar um modelo existente para uma nova tarefa em apenas alguns minutos.

Por exemplo, Sabemos que o modelo [[[[ResNet-50]]]] pré-treinado, treinado no [[Imagenet]], pode prever raças felinas e caninas, entre milhares de outras categorias. Portanto, se quisermos apenas classificar entre as categorias de “gato” e “cachorro” de alto nível (e não as raças de nível inferior), podemos começar com o modelo [[ResNet-50]] e retreinar rapidamente este modelo para classificar gatos e cães . Tudo o que precisamos fazer é mostrar um conjunto de dados com essas duas categorias durante o treinamento, o que deve levar de alguns minutos a algumas horas. Em comparação, se tivéssemos que treinar um modelo de gato versus cachorro sem um modelo pré-treinado, isso poderia levar de várias horas a dias.

DA MESA DO CRIADOR

Por Jeremy Howard, cofundador do fast.ai e ex-cientista-chefe da Kaggle Centenas de milhares de estudantes estudaram aprendizagem profunda por meio do fast.ai. Nosso objetivo é colocá-los em funcionamento o mais rápido possível, resolvendo problemas reais rapidamente. Então, qual é a primeira coisa que ensinamos? É aprendizagem por transferência!

Milhares de estudantes já compartilharam suas histórias de sucesso em nosso fórum (http://forums.fast.ai), descrevendo como, com apenas 30 imagens, eles criaram classificadores de imagens 100% precisos. Também ouvimos relatos de estudantes que quebraram recordes acadêmicos em muitas áreas e criaram modelos comercialmente valiosos usando essa técnica simples.

Há cinco anos, criei a [[Enlitic]], a primeira empresa com foco em aprendizagem profunda para medicina.Como prova inicial de conceito, decidi desenvolver um classificador de tumores pulmonares a partir de tomografias computadorizadas. Você provavelmente pode adivinhar qual técnica usamos...

Sim, foi aprendizagem por transferência! Em nossa biblioteca fast.ai de código aberto, tornamos a aprendizagem por transferência trivialmente fácil,são apenas três linhas de código e as práticas recomendadas mais importantes estão integradas.

Um Mergulho Superficial em Redes Neurais Convolucionais.

Temos usado o termo “modelo” para nos referirmos à parte da IA que faz nossas previsões. No aprendizado profundo para visão computacional, esse modelo geralmente é um tipo especial de rede neural chamada CNN.

Embora exploremos as CNNs com mais detalhes posteriormente,aqui as veremos muito brevemente em relação ao treinamento por meio de aprendizagem por transferência.

No aprendizado de máquina, precisamos converter os dados em um conjunto de recursos discerníveis e, em seguida, adicionar um algoritmo de classificação para classificá-los. É o mesmo com as CNNs. Eles consistem em duas partes: camadas convolucionais e camadas totalmente conectadas. A tarefa das camadas convolucionais é pegar o grande número de pixels de uma imagem e convertê-los em uma representação muito menor. isto é, extração de Cáracteristicas.

As camadas totalmente conectadas convertem esses Carácteristicas em probabilidades. Uma camada totalmente conectada é na verdade uma rede neural com camadas ocultas. Em resumo, as camadas convolucionais atuam como extratores de Caracteristicas,enquanto as camadas totalmente conectadas atuam como classificadores.

A Abaixo mostra uma visão geral de alto nível de uma CNN.

![[Pasted image 20240825123804.png]]

Imagine que queremos detectar um rosto humano.Poderíamos querer usar uma CNN para classificar uma imagem e determinar se ela contém um rosto.Tal CNN seria composta por diversas camadas conectadas uma após a outra.Essas camadas representam operações matemáticas.A saída de uma camada é a entrada para próxima.A primeira camada(ou a a mais inferior) é chamada de Camada de entrada ,onde a imagem de entrada é alimentada .A última (ou superior) camada é a camada de saída ,que fornece as previsões.

A forma como funciona é que a imagem é alimentada na CNN e passa por uma série de camadas,cada uma realizando uma operação matemática e passando o resultado para camada subsequente.A saída resultante é uma lista de classes de objetos e suas probabilidades. Por exemplo ,categorias como bola 65%,grama 20% e assim por diante .Se a saída de uma imagem contém uma classe "face" com 70% de probabilidade ,concluimos que há 70% de probabilidde de a imagem conter um rosto humano.

Observação

Uma maneira intuitiva (e excessivamente simplificada) de observar as CNNS é vêlas como uma série de filtros.Como a palvra filtro indica,cada camada atua como peneira de informações,deixando algo "passar " apenas se for reconhecido.(Se você já ouviu falar de filtros passa-alto e passa baixo em eletronica ,isso pod elhe parecer familiar).Dizemos que a camada foi "ativada" para essa informação para essa informação.Cada camada é ativada ára padrões visuais que lembram partes de gatos, cẽs ,carros e assim por diante.Se uma camada não rwconhece informações (devido ao que aprendeu durante o treinamento),sua saída será próxima de zero.As CNNs são os "seguranças" do mundo do aprendizado profundo !


![[Pasted image 20240825143253.png]]

No exemplo de detecção facial, as camadas de nível inferior a camada mais próxima da imagem de entrada são "ativadas" para formas mais simples, por exemplo,arestas,curvas.Como essas camadas são ativadas apenas para dormas básicas,elas podem ser facilmente reutilizadas para uma finalidade diferente do reconhecimento facila,como dtectar um carro (Afinal,toda imagem é composta de bordas e curvas).As camadas de nível médio (b) são ativadas para fromas mais complexas ,como olhos ,nariz e lábios . Essas camadas não são tão reutilizáveis quanto as camadas de nível inferior.Eles podem não ser tão úteis para detectar um carro , mas ainda podem ser úteis para detectar animais.E as camadas de nível superior (c) são ativadas para formas ainda mais complexas,por exemplo,a maior parte do rosto humano.Essas camadas tendem a ser mais especificas para tarefas e ,portanto,menos reutilizáveis em outros problemas de classificação de imagens.


A complexidade e o poder daquilo que uma camada pode reconhecer aumentam à medida que nos aproximamos das camadas finais. Por outro lado, a capacidade de reutilização de uma camada diminui à medida que nos aproximamos da saída. Isso ficará aparente muito em breve quando observarmos o que essas camadas aprendem.

Transfer Learning(Aprendizado por transferencia)

Se quisermos transferir conhecimento de um modelo para outro,queremos reutilizar mais camadas genéricas (mais próxima da entrada) e menos camadas Específicas da tarefa (mais próximas da Saida).Em outras palavras,queremos rmeover as últimas camadas (normalmente as camadas totalmente conectadas)para que possamos utilizar as mais genéricas e adicionar camadas voltadas para nossa tarefa específica de classificação.Uma vez iniciado o treinamento,as camadas genéricas(que formam a maior parte do nosso novo modelo) são mantidas congeladas (ou seja ,nção podem ser modificiadas), enquanto as camadas específicas da tarefa recém adicionadas podem ser modificadas.É assim que a aprendizagem por transferência ajuada a treinar novos modelos rapidamente.AFigura ilustra esse processo para um modelo pré-treinado para Tarefa X adpatado á tarefa .

![[Pasted image 20240826220558.png]]

Fine - Tunning (Ajuste fino)

Aprendizgem por tranferêncoa básica nos leva até certo ponto .Normalmente Adicionamos apenas duas a três camadas totalmente conectadas após as camadas genéricas para formar o novo modelo do classificador.Se quisermos maior precisão,devemos permitir que mais camadas sejam treinadas.Isso significa descongelar algumas das camadas que outra forma teriam sifo congeladas na aprendizagem por transferência.

Isso é conhecido como [[FineTunning - Ajuste Fino]],Abaixo vemos um exemplo onde algumas camadas convolucionais próximas á cabeça/topo sã descongeladas e treinadas para tarefa em questão.

![[Pasted image 20240826221210.png]]

É obvio, que em comparação com o aprendizado de transferência básico,mais camadas são ajustadas em nosso conjunto de dados durante o ajuste fino.Como um número maior de camadas se adaptou á nossa tarefa em comparação com a aprendizagem por tranferência ,podemos obter maior precisão em nossa tarefa.A decisão sobre quantas camadas ajustar depende da quantidade de dados disponiveis,bem como da semelhança da tarefa alvo com o conjunto de dados original no qual o modelo pré-treinado foi treinado.

Frequentemente ouvimos cientistas de dado dizendo "Ah,ajustei meu modelo",Oque significa que eles pegaram um modelo pré-treinado ,removeram tarefas e camadas específicas e adicionaram novas,congelaram as camadas inferiores e treinaram a parte superior da rede com novos conjuntos de dados que possuíam.


Observações:

Na linguagem diária, a aprendizagem por transferência e o ajuste fino são usados de forma intercambiável. Quando falado, a aprendizagem por transferência é usada mais como um conceito geral, enquanto o ajuste fino é referido como sua implementação.

Quando fazer FineTunning (Ajuste-fino) ?

Quantas camadas de uma CNN devemos ajustar ? isso pode ser guiado pelos seguintes dois fatores :

Quantos dados temos ?

Se tivermos algumas centenas de imagens rotuladas,será díficil treinar e testar um modelo recém-definido do zero(ou seja,definir uma arquitetura de modelos com pesos iniciais aléatorios) porque precisamos de muito mais dados.O perigo de treinar com uma quantidade tão pequenas de dados é que essas redes poderosas podem potencialmente meorizá-los,levando a um [[Overffiting]] indesejável .Em vez disso,pegaremos emprestada uma rede pré-treinada e ajustaremos as últimas camadas.Mas se tivéssemos um milhão de imagens rotuladas,seria viável ajustar todas as camadas da rede e ,se necessário, treinar do zero.

Quão semelhantes são os dados ?

Se os dados específicos da tarefa forem semelhantes aos dados usados para rede pré-treinada,podemos ajustar as últimas camadas.Mas se nossa tarefa é identificar diferentes ossos em uma imagem de raio X e quisermos começar a partir de uma rede pré treinada [[ImageNet]],a alta dissimilaridade entre ImageNet regulares e imagens de raios X exigiria que quase todas as camadas fossem treinadas .

Alta Similaridade ao Longo dos conjuntos de dadosBaixa Simillaridade entre os DataSets
Grande quantidade de Dados de treinamentoAjuste Fino para todas as camadasTreinar do zero,ou Ajuste fino em todas as camadas
Pequenas quantidade de Dados de treinamentoAjuste as ultimas camadasAzar! Treine em uma rede menor com grande aumento de dados ou, de alguma forma, obtenha mais dados

Contruindo um classificador personalizado em Keras com transfer learning.

Etapas de Projeto

  1. Organização de dados: Baixe imagens rotuladas de cães e gatos e divida as imagens em pastas de treinamento e validação.
  2. Contrua o pipeline de dados : Defina uma pipeline para leitura de daods,incluindo o pré-processamento das imagens (por exmeplo, redimencionamento) e o agrupamento de várias imagens em lotes.
  3. Aumente os dados: Na ausência de muito treinamento de imagens,faça peqeunas alterações (aumento) com rotação ,zoom e assim por diante para aumentar a variação no dados de treinamento.
  4. Defina o modelo : Pegue um modleo pré-teinado ,remova as últimas camadas éspecificas da tarefa e anexe uma nova camada classificadora.Congele os pesos das camadas originais (ou seja,torne-as inalteráveis).Selecione um algoritimo otimizador e uma métrica para rastrear (como Precisão ou acurácia).
  5. Treino e Teste : Treine por algumas iterações até a acurácia estiver alta.Salve o modelo para eventualmente carregar como parte de qualquer aplicação para predição.

Organize os dados

É Essencial compreender a distinção entre dados de treinamento, validação e teste. Vejamos uma analogia do mundo real de um aluno se preparando para exames padronizados (por exemplo, ENEM, Tribunais,Concursos Específicos)

As instruções em sala de aula e as tarefas de casa são análogas ao processo de treinamento.Os questionários, provas intermediárias e outros testes na escola equivalem à validação o aluno pode realizá-los com frequência, avaliar o desempenho e fazer melhorias em seu plano de estudos. Em última análise, eles estão otimizando seu desempenho no exame final padronizado, para o qual têm apenas uma chance.

O exame final equivale ao conjunto de testes,o aluno não tem oportunidade de melhorar aqui(ignorando a possibilidade de refazer o teste). Esta é a única chance de mostrar o que aprenderam.

Da mesma forma, nosso objetivo é fornecer as melhores previsões do mundo real. Para permitir isso, dividimos nossos dados em três partes:

  1. Treinamento
  2. Validação
  3. Teste

Uma distribuição típica seria 80% para treinamento, 10% para validaçãoe 10% para teste. Observe que dividimos aleatoriamente nossos dados nesses três conjuntos para garantir a menor quantidade de viés que possa surgir inconscientemente. A precisão final do modelo é determinada pela precisão do conjunto de teste, assim como a pontuação do aluno é determinada apenas pelo seu desempenho no exame padronizado.

O modelo aprende com os dados de treinamento e usa o conjunto de validação para avaliar seu desempenho. Os profissionais de aprendizado de máquina consideram esse desempenho como feedback para encontrar oportunidades de melhorar seus modelos continuamente, semelhante à forma como os alunos melhoram sua preparação com a ajuda de questionários.

Existem vários formas que podemos ajustar para melhorar o desempenho,por exemplo : o número de camadas a serem treinadas.

Em muitas competições de pesquisa (incluindo Kaggle.com), os concorrentes recebem um conjunto de testes separado dos dados que podem usar para construir o modelo. Isso garante uniformidade em toda a concorrência no que diz respeito à precisão dos relatórios.

Cabe aos competidores dividam os dados disponíveis em conjuntos de treinamento e validação. Da mesma forma, durante nossos experimentos nesse tutorial,continuaremos a dividir os dados nesses dois conjuntos, tendo em mente que um conjunto de dados de teste ainda é essencial para relatar números do mundo real.

Então, por que usar um conjunto de validação? Às vezes, os dados são difíceis de obter, então por que não usar todas as amostras disponíveis para treinamento e depois relatar a precisão delas? Claro, quando o modelo começar a aprender, ele fornecerá gradualmente previsões de maior precisão no conjunto de dados de treinamento (chamado de precisão de treinamento). Mas, por serem tão poderosas, as redes neurais profundas podem potencialmente memorizar os dados de treinamento, às vezes resultando em 100% de precisão nos dados de treinamento. No entanto, o seu desempenho no mundo real será bastante fraco. É como se o aluno soubesse as questões que constariam da prova antes de realizá-la. É por isso que um conjunto de validação, não utilizado para treinar o modelo, fornece uma avaliação realista do desempenho do modelo.

Mesmo que possamos atribuir 10-15% dos dados como um conjunto de validação, isso ajudará muito a nos orientar sobre quão bom nosso modelo realmente é.

Para o processo de treinamento, precisamos armazenar nosso conjunto de dados na estrutura de pastas adequada. Dividiremos as imagens em dois conjuntos:

treinamento e validação. Para um arquivo de imagem, Keras atribuirá automaticamente o nome da classe (categoria) com base no nome da pasta pai.

![[Pasted image 20240827105037.png]]

Sequência de comandos para baixar os dados e chegar na estrutura especifica

wget https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/download/train.zip
unzip train.zip
mv train data
cd data 
mkdir train val
mkdir train/cat train/dog
mkdir val/cat val/dog

Os 25.000 arquivos na pasta de dados são prefixados com “cat” e "dog". Agora, mova os arquivos para seus respectivos diretórios.Para manter nosos primeiro experimento pequeno, iremos escolher 250 arquivos aleatórios por classe e coloque-os em pastas de treinamento e validação. Podemos aumentar/diminuir esse número a qualquer momento, para experimentar um compensação entre precisão e velocidade:

$ ls | grep cat | sort -R | head -250 | xargs -I {} mv {} train/cat/
$ ls | grep dog | sort -R | head -250 | xargs -I {} mv {} train/dog/
$ ls | grep cat | sort -R | head -250 | xargs -I {} mv {} val/cat/
$ ls | grep dog | sort -R | head -250 | xargs -I {} mv {} val/dog/

Contruindo pipeline de dados

Para começar nosso progama python,Iremos importar os pacotes necessários :

import tensorflow as tf
from tf.keras.preprocessing.image import ImageDataGenerator
from tf.keras.models import Model
from tf.keras.layers import Input, Flatten, Dense, Dropout,
GlobalAveragePooling2D
from tf.keras.applications.mobilenet import MobileNet, preprocess_input
import math

Coloque as seguintes linha sde configuração que se pode modificar baseado no nosso conjunto de dados :

TRAIN_DATA_DIR =  'data/train_data/'
VALIDATION_DATA_DIR = 'data/val_data'
TRAIN_SAMPLES = 500
VALIDATION_SAMPLES =500 
NUM_CLASSES = 2
IMG_WIDTH, IMG_HEIGHT = 244, 244
BATCH_SIZE = 64 

Número de Classes

Classificação Binária

Para uma tarefa de classificação binária, o problema pode ser entendido como "Gatos X Cães" sendo efetivamente "gato X não gato". Um cão pode ser classificado como "não gato", assim como uma mesa ou uma bola. Para uma determinada imagem, o modelo fornecerá um único valor de probabilidade correspondente à classe "gato". A probabilidade de "não gato" será então 1 menos a probabilidade de "gato".

Se a probabilidade de "gato" for superior a 0,5, previsivelmente será "gato". Caso contrário, a previsão será "não gato". Assumimos aqui que o conjunto de testes conterá apenas imagens de gatos ou cachorros.

Como "gatos X não gatos" é uma tarefa de classificação binária, definimos o número de classes como 1, que é "gato". Qualquer coisa que não possa ser classificada como "gato" será classificada como "não gato".

Observação: O Keras processa os dados de entrada em ordem alfabética dos nomes das pastas. Como “gato” vem antes de “cachorro” em ordem alfabética, nossa primeira classe de previsão será “gato”. Para uma tarefa multiclasse, podemos aplicar o mesmo conceito e inferir cada identificador de classe (índice) com base na ordem de classificação da pasta. O índice da classe começa em 0 para a primeira classe.

Classificação Multiclasse

Em um cenário hipotético com apenas cães e gatos, "não gato" poderia simplesmente ser substituído por "cachorro". No entanto, no mundo real, temos mais de dois tipos de objetos. Portanto, "não gato" poderia incluir uma bola ou um sofá, o que seria incorreto. Assim, para um cenário real, tratar isso como uma tarefa de classificação multiclasse é mais apropriado.

Para uma tarefa de classificação multiclasse, prevemos valores de probabilidade separados para cada classe, e a classe com a maior probabilidade é nossa previsão final. No caso de "Gatos X Cães", definimos o número de classes como 2. Para manter o código reutilizável em tarefas futuras, trataremos isso como uma tarefa de classificação multiclasse.

Tamanho do Lote (Batch Size)

O processo de treinamento inclui as seguintes etapas:

  1. Faça Revisões nas Imagens
  2. Determine Quais Previsões Estavam Incorretas e propague de volta a diferença entre a previsão e o valor verdadeiro (Retropropagação).
  3. Enxágue e Repita até que as previsões se tornem suficientemente precisas.

É provável que a iteração inicial tenha precisão próxima de 0%. Repetir o processo várias vezes pode resultar em um modelo altamente preciso (>90%).

O tamanho do lote define quantas imagens são vistas pelo modelo de uma vez. É importante que cada lote tenha uma boa variedade de imagens de diferentes classes para evitar grandes flutuações na métrica de precisão entre as iterações. Um tamanho de lote suficientemente grande é necessário para isso. No entanto, não se deve definir o tamanho do lote muito grande, pois um lote muito grande pode não caber na memória da GPU, resultando em uma falha de "falta de memória". Normalmente, os tamanhos dos lotes são definidos como potências de 2. Um bom número para começar é 64 para a maioria dos problemas, e o número pode ser ajustado para melhorar o desempenho.

Aumento de Dados (Data Augmentation)

Normalmente, quando pensamos em deep learning, associamos com milhões de imagens. Portanto, 500 imagens, como as que temos, pode ser um número baixo para treinamento em situações reais. Embora essas redes neurais profundas sejam poderosas, elas são um pouco poderosas demais para pequenas quantidades de dados. O perigo de ter um conjunto limitado de imagens de treinamento é que a rede neural pode memorizar nossos dados de treinamento, apresentando um ótimo desempenho nas previsões sobre o conjunto de treinamento, mas uma baixa acurácia no conjunto de validação. Em outras palavras, o modelo foi supertreinado e não generaliza bem em imagens previamente não vistas. E definitivamente não queremos isso.

DICA

Frequentemente, quando tentamos treinar uma rede neural com uma pequena quantidade de dados, o resultado é um modelo que funciona muito bem com os próprios dados de treinamento, mas faz previsões bastante ruins com dados que ele não viu antes. Um modelo assim seria descrito como um modelo superajustado (overfitted), e o problema em si é conhecido como sobreajuste ([[Overfitting]]).

A imagem ilustra esse fenômeno para uma distribuição de pontos próximos a uma curva senoidal (com pouco ruído). Os pontos representam os dados de treinamento visíveis para nossa rede, e as cruzes representam os dados de teste que não foram vistos durante o treinamento. Em um extremo (subajuste), um modelo não sofisticado, como um preditor linear, não será capaz de representar bem a distribuição subjacente e resultará em uma alta taxa de erro tanto nos dados de treinamento quanto nos dados de teste. No outro extremo (superajuste), um modelo poderoso (como uma rede neural profunda) pode ter a capacidade de memorizar os dados de treinamento, o que resultaria em um erro muito baixo nos dados de treinamento, mas ainda um erro alto nos dados de teste. O que queremos é o meio termo ideal, onde o erro de treinamento e o erro de teste sejam ambos modestamente baixos, o que garante, idealmente, que nosso modelo funcionará tão bem no mundo real quanto durante o treinamento.

![[Pasted image 20240904211529.png]] Underfitting, overfitting e ajuste ideal para pontos próximos a um seno curva


Com grande poder vem grande responsabilidade. É nossa responsabilidade garantir que nossa poderosa rede neural profunda não sofra de sobreajuste (overfitting) em nossos dados. O sobreajuste é comum quando temos poucos dados de treinamento. Podemos reduzir essa probabilidade de algumas maneiras diferentes:

  • Conseguir mais dados
  • Aumentar fortemente os dados existentes
  • Ajustar finamente menos camadas

Frequentemente, há situações nas quais não há dados suficientes disponíveis. Talvez estejamos lidando com um problema de nicho e os dados sejam difíceis de obter. Mas há algumas maneiras de aumentar artificialmente nosso conjunto de dados para classificação:

  • Rotação
    Em nosso exemplo, podemos querer rotacionar as 500 imagens aleatoriamente por 20 graus em qualquer direção, resultando em até 20.000 imagens únicas possíveis.

  • Deslocamento Aleatório
    Deslocar as imagens ligeiramente para a esquerda ou para a direita.

  • Zoom
    Ampliar e reduzir ligeiramente a imagem.

Ao combinar rotação, deslocamento e zoom, o programa pode gerar um número quase infinito de imagens únicas. Esse passo importante é chamado de aumento de dados (data augmentation). O aumento de dados é útil não apenas para adicionar mais dados, mas também para treinar modelos mais robustos para cenários do mundo real. Por exemplo, nem todas as imagens têm o gato centralizado corretamente no meio ou a um ângulo perfeito de 0 graus. O Keras fornece a função ImageDataGenerator que aumenta os dados enquanto eles estão sendo carregados do diretório.

Para ilustrar como são as ampliações de imagens, a Figura Abaixo apresenta exemplos de ampliações geradas pela biblioteca imgaug para uma imagem de amostra. (Observe que não usaremos o imgaug para nosso treinamento real.)

![[Pasted image 20240904211739.png]] Possíveis aumentos de imagem gerados a partir de uma única imagem

Imagens coloridas geralmente têm três canais: vermelho, verde e azul. Cada canal tem um valor de intensidade variando de 0 a 255. Para normalizar esses valores (ou seja, escalá-los para que fiquem entre 0 e 1), usamos a função preprocess_input (que, entre outras coisas, divide cada pixel por 255):

train_datagen = ImageDataGenerator(
    preprocessing_function=preprocess_input,
    rotation_range=20,
    width_shift_range=0.2,
    height_shift_range=0.2,
    zoom_range=0.2
)
val_datagen = ImageDataGenerator(
    preprocessing_function=preprocess_input
)

DICA

Às vezes, conhecer o rótulo de uma imagem de treinamento pode ser útil para determinar maneiras apropriadas de aumentá-la. Por exemplo, ao treinar um reconhecedor de dígitos, você pode estar confortável com a ampliação por inversão vertical para uma imagem do dígito “8”, mas não para “6” e “9.”

Ao contrário do nosso conjunto de treinamento, não queremos aumentar nosso conjunto de validação. A razão é que, com ampliação dinâmica, o conjunto de validação continuaria mudando a cada iteração, e a métrica de acurácia resultante seria inconsistente e difícil de comparar entre as diferentes iterações.

É hora de carregar os dados a partir de seus diretórios. Treinar uma imagem por vez pode ser bastante ineficiente, então podemos agrupá-las em lotes. Para introduzir mais aleatoriedade durante o processo de treinamento, manteremos as imagens em cada lote embaralhadas. Para trazer reprodutibilidade durante múltiplas execuções do mesmo programa, daremos um valor de semente ao gerador de números aleatórios:

train_generator = train_datagen.flow_from_directory(
    TRAIN_DATA_DIR,
    target_size=(IMG_WIDTH, IMG_HEIGHT),
    batch_size=BATCH_SIZE,
    shuffle=True,
    seed=12345,
    class_mode='categorical'
)
validation_generator = val_datagen.flow_from_directory(
    VALIDATION_DATA_DIR,
    target_size=(IMG_WIDTH, IMG_HEIGHT),
    batch_size=BATCH_SIZE,
    shuffle=False,
    class_mode='categorical'
)

Definição do Modelo
Agora que os dados estão configurados, chegamos ao componente mais crucial do nosso processo de treinamento: o modelo. No código a seguir, reutilizamos uma CNN previamente treinada no conjunto de dados ImageNet (MobileNet, no nosso caso), descartamos as últimas camadas, chamadas camadas totalmente conectadas (ou seja, camadas de classificadores específicas do ImageNet), e as substituímos pelo nosso próprio classificador adequado à tarefa em questão.

Para o aprendizado por transferência, “congelamos” os pesos do modelo original; isto é, definimos essas camadas como não modificáveis, de forma que apenas as camadas do novo classificador (que adicionaremos) possam ser modificadas. Usamos o MobileNet aqui para manter as coisas rápidas, mas esse método funcionará igualmente bem para qualquer rede neural. As linhas a seguir incluem alguns termos como Dense, Dropout, e assim por diante. Embora não exploremos esses termos neste capítulo, você pode encontrar explicações no Apêndice A.

def model_maker():
    base_model = MobileNet(include_top=False, input_shape=(IMG_WIDTH, IMG_HEIGHT, 3))
    for layer in base_model.layers[:]:
        layer.trainable = False # Congelar as camadas
    input = Input(shape=(IMG_WIDTH, IMG_HEIGHT, 3))
    custom_model = base_model(input)
    custom_model = GlobalAveragePooling2D()(custom_model)
    custom_model = Dense(64, activation='relu')(custom_model)
    custom_model = Dropout(0.5)(custom_model)
    predictions = Dense(NUM_CLASSES, activation='softmax')(custom_model)
    return Model(inputs=input, outputs=predictions)

Treinar o modelo

Definir Parâmetros de Treinamento
Com os dados e o modelo prontos, tudo o que resta é treinar o modelo. Isso também é conhecido como ajustar o modelo aos dados. Para treinar um modelo, precisamos selecionar e modificar alguns parâmetros de treinamento diferentes:

  • Função de Perda
    A função de perda é a penalidade que impomos ao modelo por previsões incorretas durante o processo de treinamento. É o valor dessa função que buscamos minimizar. Por exemplo, em uma tarefa para prever preços de imóveis, a função de perda poderia ser o erro quadrático médio.

  • Otimizador
    Esse é um algoritmo que ajuda a minimizar a função de perda. Usamos o Adam, um dos otimizadores mais rápidos disponíveis.

  • Taxa de Aprendizado
    O aprendizado é incremental. A taxa de aprendizado informa ao otimizador o tamanho do passo a ser dado em direção à solução; em outras palavras, onde a perda é mínima. Dar um passo muito grande pode fazer com que oscile muito e ultrapasse o alvo. Dar um passo muito pequeno pode levar muito tempo até eventualmente atingir o valor de perda alvo. É importante definir uma taxa de aprendizado ideal para garantir que alcancemos nosso objetivo de aprendizado em um tempo razoável. Em nosso exemplo, definimos a taxa de aprendizado em 0,001.

  • Métrica
    Escolha uma métrica para julgar o desempenho do modelo treinado. A acurácia é uma boa métrica explicável, especialmente quando as classes não estão desbalanceadas (ou seja, há quantidades aproximadamente iguais de dados para cada classe). Observe que essa métrica não está relacionada à função de perda e é usada principalmente para relatórios e não como feedback para o modelo.

No trecho de código a seguir, criamos o modelo personalizado usando a função model_maker que escrevemos anteriormente. Usamos os parâmetros descritos aqui para personalizar ainda mais esse modelo para a nossa tarefa de distinguir entre gatos e cachorros:

model = model_maker()
model.compile(
    loss='categorical_crossentropy',
    optimizer=tf.train.Adam(lr=0.001),
    metrics=['acc']
)
num_steps = math.ceil(float(TRAIN_SAMPLES) / BATCH_SIZE)
model.fit_generator(
    train_generator,
    steps_per_epoch=num_steps,
    epochs=10,
    validation_data=validation_generator,
    validation_steps=num_steps
)

NOTA
Você pode ter notado o termo "época" no código acima. Uma época representa um passo completo de treinamento onde a rede percorreu todo o conjunto de dados. Uma época pode consistir em vários mini-lotes.

Iniciar o Treinamento
Execute este programa e deixe a mágica começar. Se você não tiver uma GPU, faça uma xícara de café enquanto espera — pode levar de 5 a 10 minutos. Ou, por que esperar, quando você pode rodar os notebooks deste capítulo (postados no GitHub) no Colab com uma execução de GPU gratuita?

Quando o treinamento estiver completo, observe que há quatro estatísticas: perda (loss) e acurácia (acc) tanto para os dados de treinamento quanto para os de validação. Estamos torcendo para o val_acc:

> Epoch 1/100 7/7 [====] - 5s -
loss: 0.6888 - acc: 0.6756 - val_loss: 0.2786 - val_acc: 0.9018
> Epoch 2/100 7/7 [====] - 5s -
loss: 0.2915 - acc: 0.9019 - val_loss: 0.2022 - val_acc: 0.9220
> Epoch 3/100 7/7 [====] - 4s -
loss: 0.1851 - acc: 0.9158 - val_loss: 0.1356 - val_acc: 0.9427
> Epoch 4/100 7/7 [====] - 4s -
loss: 0.1509 - acc: 0.9341 - val_loss: 0.1451 - val_acc: 0.9404
> Epoch 5/100 7/7 [====] - 4s -
loss: 0.1455 - acc: 0.9464 - val_loss: 0.1637 - val_acc: 0.9381
> Epoch 6/100 7/7 [====] - 4s -
loss: 0.1366 - acc: 0.9431 - val_loss: 0.2319 - val_acc: 0.9151
> Epoch 7/100 7/7 [====] - 4s -
loss: 0.0983 - acc: 0.9606 - val_loss: 0.1420 - val_acc: 0.9495
> Epoch 8/100 7/7 [====] - 4s -
loss: 0.0841 - acc: 0.9731 - val_loss: 0.1423 - val_acc: 0.9518
> Epoch 9/100 7/7 [====] - 4s -
loss: 0.0714 - acc: 0.9839 - val_loss: 0.1564 - val_acc: 0.9509
> Epoch 10/100 7/7 [====] - 5s -
loss: 0.0848 - acc: 0.9677 - val_loss: 0.0882 - val_acc: 0.9702

Tudo o que foi necessário foram 5 segundos na primeira época para alcançar 90% de acurácia no conjunto de validação, com apenas 500 imagens de treinamento. Nada mal! E na 10ª época, observamos cerca de 97% de acurácia na validação. Esse é o poder do aprendizado por transferência.

Vamos aproveitar para apreciar o que aconteceu aqui. Com apenas 500 imagens, conseguimos atingir um alto nível de acurácia em poucos segundos e com muito pouco código. Em contraste, se não tivéssemos um modelo previamente treinado no ImageNet, obter um modelo preciso poderia ter levado de algumas horas a vários dias de treinamento, e muito mais dados.

Esse é todo o código necessário para treinar um classificador de ponta para qualquer problema. Coloque os dados em pastas com o nome da classe e altere os valores correspondentes nas variáveis de configuração. Caso nossa tarefa tenha mais de duas classes, devemos usar categorical_crossentropy como a função de perda e substituir a função de ativação na última camada por softmax. A Tabela Abaixo ilustra isso.

Tabela . Decidindo o tipo de perda e ativação com base na tarefa

Tipo de ClassificaçãoClass modeLossAtivação na Última Camada
1 ou 2 classesbinarybinary_crossentropysigmoid
Multiclasse, único rótulocategoricalcategorical_crossentropysoftmax
Multiclasse, múltiplos rótuloscategoricalbinary_crossentropysigmoid

Antes que eu me esqueça, salve o modelo que você acabou de treinar para que possamos usá-lo mais tarde:

model.save('model.h5')

Testar o modelo

Carregar e Usar o Modelo Treinado
Agora que temos um modelo treinado, talvez queremos usá-lo mais tarde em nossa aplicação. Podemos carregar esse modelo a qualquer momento e classificar uma imagem. A função load_model, como o nome sugere, carrega o modelo:

from tensorflow.keras.models import load_model
model = load_model('model.h5')

Vamos tentar carregar nossas imagens de exemplo originais e ver quais resultados obtemos:

from tensorflow.keras.preprocessing import image
import numpy as np
 
img_path = '../../sample_images/dog.jpg'
img = image.load_img(img_path, target_size=(224, 224))
img_array = image.img_to_array(img)
expanded_img_array = np.expand_dims(img_array, axis=0)
preprocessed_img = preprocess_input(expanded_img_array)  # Pré-processar a imagem
prediction = model.predict(preprocessed_img)
print(prediction)
print(validation_generator.class_indices)

Imprimindo o valor da probabilidade, vemos que é 0.996. Esta é a probabilidade da imagem dada pertencer à classe “1”, que é um cachorro. Como a probabilidade é maior que 0.5, a imagem é prevista como um cachorro.

Com isso, temos tudo o que precisamos para treinar nossos próprios classificadores. Ao longo deste livro, você pode esperar reutilizar este código para treinamento com modificações mínimas. Você também pode reutilizar este código em seus próprios projetos. Experimente com o número de épocas e imagens, e observe como isso afeta a acurácia. Além disso, explore qualquer outro dado que você possa encontrar online. Não há nada mais simples do que isso!

Analisando os Resultados
Com nosso modelo treinado, podemos analisar como ele está se saindo no conjunto de validação. Além das métricas de acurácia mais diretas, olhar para as imagens de previsões incorretas deve dar uma ideia se o exemplo era realmente desafiador ou se nosso modelo não é sofisticado o suficiente.

Queremos responder três perguntas para cada categoria (gato, cachorro):

  1. Quais imagens temos mais confiança de que são um gato/cachorro?
  2. Quais imagens temos menos confiança de que são um gato/cachorro?
  3. Quais imagens têm previsões incorretas apesar de termos alta confiança?

Antes de chegar a isso, vamos obter previsões para todo o conjunto de validação. Primeiro, configuramos corretamente o pipeline:

# VARIÁVEIS
IMG_WIDTH, IMG_HEIGHT = 224, 224
VALIDATION_DATA_DIR = 'data/val_data/'
VALIDATION_BATCH_SIZE = 64
 
# GERADORES DE DADOS
validation_datagen = ImageDataGenerator(preprocessing_function=preprocess_input)
validation_generator = validation_datagen.flow_from_directory(
    VALIDATION_DATA_DIR,
    target_size=(IMG_WIDTH, IMG_HEIGHT),
    batch_size=VALIDATION_BATCH_SIZE,
    shuffle=False,
    class_mode='categorical'
)
ground_truth = validation_generator.classes

Então, obtemos as previsões:

predictions = model.predict(validation_generator)

Para facilitar nossa análise, criamos um dicionário armazenando o índice da imagem para a previsão e a verdade conhecida (a previsão esperada) para cada imagem:

# prediction_table é um dicionário com índice, previsão, verdade conhecida
prediction_table = {}
for index, val in enumerate(predictions):
    # obter índice da maior probabilidade
    index_of_highest_probability = np.argmax(val)
    value_of_highest_probability = val[index_of_highest_probability]
    prediction_table[index] = [value_of_highest_probability, index_of_highest_probability, ground_truth[index]]
 
assert len(predictions) == len(ground_truth) == len(prediction_table)

Os próximos dois blocos de código fornecem código padrão que reutilizamos regularmente ao longo do livro. A seguir está a assinatura da função auxiliar que usaremos para encontrar as imagens com o maior/menor valor de probabilidade para uma dada categoria. Além disso, usaremos outra função auxiliar, display(), para exibir as imagens como uma grade na tela:

def display(sorted_indices, message):
    similar_image_paths = []
    distances = []
    for name, value in sorted_indices:
        [probability, predicted_index, gt] = value
        similar_image_paths.append(VALIDATION_DATA_DIR + fnames[name])
        distances.append(probability)
    plot_images(similar_image_paths, distances, message)

Essa função está definida no site GitHub do livro (veja PracticalDeepLearning.ai).

Agora, o que é mais divertido! Quais imagens temos mais confiança de que contêm cachorros? Vamos encontrar imagens com a maior probabilidade de previsão (ou seja, mais próximo de 1.0; veja a Figura 3-9) com a classe prevista como cachorro (ou seja, 1):

# Previsões mais confiantes de 'cachorro'
indices = get_images_with_sorted_probabilities(
    prediction_table,
    get_highest_probability=True,
    label=1,
    number_of_items=10,
    only_false_predictions=False
)
message = 'Imagens com a maior probabilidade de conter cachorros'
display(indices[:10], message)

![[Pasted image 20240904213055.png]]

Imagens com maior probalidade de conter cachorros

Essas imagens são realmente muito parecidas com cachorros. Uma das razões para a probabilidade ser tão alta pode ser o fato de que as imagens contêm múltiplos cachorros, além de vistas claras e não ambíguas. Agora vamos tentar encontrar quais imagens temos menos confiança de que contenham cachorros (veja a Figura 3-10):

# Previsões menos confiantes de 'cachorro'
indices = get_images_with_sorted_probabilities(
    prediction_table,
    get_highest_probability=False,
    label=1,
    number_of_items=10,
    only_false_predictions=False
)
message = 'Imagens com a menor probabilidade de conter cachorros'
display(indices[:10], message)

Este código ajudará a identificar quais imagens o modelo tem menor certeza de que são cachorros, oferecendo uma visão adicional sobre as previsões menos confiantes.

![[Pasted image 20240904213216.png]]

Para repetir, essas são as imagens para as quais nosso classificador tem mais dúvidas sobre a presença de um cachorro. A maioria dessas previsões está no ponto de decisão (ou seja, probabilidade de 0,5) para ser a previsão majoritária. Lembre-se de que a probabilidade de ser um gato é apenas um pouco menor, cerca de 0,49. Em comparação com o conjunto de imagens anterior, os animais que aparecem nessas imagens são frequentemente menores e menos claros. Além disso, essas imagens frequentemente resultam em previsões incorretas — apenas 2 das 10 imagens foram corretamente classificadas. Uma maneira possível de melhorar aqui é treinar com um conjunto maior de imagens.

Se você está preocupado com essas classificações incorretas, não se preocupe. Um truque simples para melhorar a precisão da classificação é ter um limiar mais alto para aceitar os resultados do classificador, digamos 0,75. Se o classificador não tem certeza sobre a categoria de uma imagem, seus resultados são retidos. No Capítulo 5, veremos como encontrar um limiar ideal.

Falando em previsões incorretas, elas são obviamente esperadas quando o classificador tem baixa confiança (ou seja, probabilidade próxima de 0,5 para um problema de duas classes). Mas o que não queremos é errar quando nosso classificador está realmente seguro de suas previsões. Vamos verificar quais imagens o classificador está confiante de que contêm cachorros, apesar de serem gatos (veja a Figura 3-11):

# Previsões incorretas de 'cachorro'
indices = get_images_with_sorted_probabilities(
    prediction_table,
    get_highest_probability=True,
    label=1,
    number_of_items=10,
    only_false_predictions=True
)
message = 'Imagens de gatos com a maior probabilidade de conter cachorros'
display(indices[:10], message)

Este código ajudará a identificar quais imagens o modelo está confiante de que são cachorros, apesar de serem na verdade gatos, permitindo uma análise das falhas do classificador onde ele está mais seguro de suas previsões.

![[Pasted image 20240904213416.png]] Imagens de gatos com maior probabilidade de conter cachorros

Hmm… parece que metade dessas imagens contém tanto gatos quanto cachorros, e nosso classificador está prevendo corretamente a categoria de cachorro porque eles são maiores nas imagens. Assim, o problema não é o classificador, mas os dados que estão incorretos aqui. Isso acontece frequentemente em grandes conjuntos de dados. A outra metade frequentemente contém objetos pouco claros e relativamente pequenos (mas idealmente queremos uma confiança mais baixa para essas imagens difíceis de identificar).

Repetindo o mesmo conjunto de perguntas para a classe de gatos, quais imagens são mais semelhantes a gatos?

# Previsões mais confiantes de 'gato'
indices = get_images_with_sorted_probabilities(
    prediction_table,
    get_highest_probability=True,
    label=0,
    number_of_items=10,
    only_false_predictions=False
)
message = 'Imagens com a maior probabilidade de conter gatos'
display(indices[:10], message)

![[Pasted image 20240904220626.png]]

Imagens com maior probabilidades de conter gatos

Curiosamente, muitas dessas imagens têm vários gatos. Isso confirma nossa hipótese anterior de que várias visualizações claras e inequívocas de gatos podem dar probabilidades mais altas. Por outro lado, quais imagens estamos mais inseguros quanto a conter gatos?

# Previsões menos confiantes de 'gato'
indices = get_images_with_sorted_probabilities(
    prediction_table,
    get_highest_probability=False,
    label=0,
    number_of_items=10,
    only_false_predictions=False
)
message = 'Imagens com a menor probabilidade de conter gatos'
display(indices[:10], message)

![[Pasted image 20240904220708.png]]

Imagens com menor probabilidade de conter gatos

Como visto anteriormente, o tamanho do objeto é pequeno e algumas das imagens são bastante pouco claras, o que significa que há contraste excessivo em alguns casos ou o objeto está muito brilhante, algo que não está alinhado com a maioria das imagens de treinamento. Por exemplo, o flash da câmera na oitava (dog.6680) e na décima (dog.1625) imagem em Figura torna o cachorro difícil de reconhecer. A sexta imagem contém um cachorro na frente de um sofá da mesma cor. Duas imagens contêm gaiolas.

Finalmente, quais imagens o nosso classificador está erroneamente seguro de que contêm gatos?

# Previsões incorretas de 'gato'
indices = get_images_with_sorted_probabilities(
    prediction_table,
    get_highest_probability=True,
    label=0,
    number_of_items=10,
    only_false_predictions=True
)
message = 'Imagens de cachorros com a maior probabilidade de conter gatos'
display(indices[:10], message)

![[Pasted image 20240904220911.png]]

Essas previsões incorretas são o que queremos reduzir. Algumas delas estão claramente erradas, enquanto outras são imagens compreensivelmente confusas. A sexta imagem (dog.4334) na Figura parece estar incorretamente rotulada como cachorro. As imagens sétima e décima são difíceis de distinguir contra o fundo. A primeira e a décima faltam textura suficiente para dar ao classificador poder de identificação. E alguns dos cachorros são muito pequenos, como a segunda e a quarta.

Ao analisar as várias análises, podemos resumir que as previsões incorretas podem ser causadas por baixa iluminação, fundos pouco claros e difíceis de distinguir, falta de textura e área ocupada menor em relação à imagem.

Analisar nossas previsões é uma ótima maneira de entender o que nosso modelo aprendeu e o que ele tem dificuldade, e destaca oportunidades para melhorar seu poder preditivo. Aumentar o tamanho dos exemplos de treinamento e uma augmentação mais robusta ajudarão a melhorar a classificação. Também é importante observar que mostrar imagens do mundo real ao nosso modelo (imagens que se assemelham ao cenário onde nosso aplicativo será usado) ajudará a melhorar drasticamente sua precisão. Veremos depois como fazer um classificador mais robusto.

Leitura adicional

Para ajudar a entender melhor redes neurais e CNNs, nosso site oferece um guia de aprendizado que inclui recursos recomendados, como palestras em vídeo, blogs e, mais interessantemente, ferramentas visuais interativas que permitem experimentar diferentes cenários no navegador sem a necessidade de instalar pacotes. Se você é um aprendiz de aprendizado profundo pela primeira vez, recomendamos fortemente este guia para fortalecer seu conhecimento fundamental. Ele cobre a teoria necessária para construir a intuição para resolver problemas futuros. Usamos o TensorFlow Playground do Google para redes neurais e o ConvNetJS de Andrej Karpathy para CNNs.

![[Pasted image 20240904221524.png]] Montando uma rede neural com o TensorFlow Playground

![[Pasted image 20240904221536.png]] Definindo uma CNN e vizualizando a saida para cada camada durante o trinamento na ConvNetJS

FIM

Neste Tutorial, introduzimos o conceito de aprendizado por transferência. Reutilizamos um modelo pré-treinado para construir nosso próprio classificador de gatos versus cães em menos de 30 linhas de código e com apenas 500 imagens, atingindo uma precisão de ponta em poucos minutos. Ao escrever este código, também desmistificamos a ideia de que precisamos de milhões de imagens e GPUs poderosas para treinar nosso classificador (embora elas ajudem). Esperamos que, com essas habilidades, você finalmente possa responder à antiga questão de quem deixou os cães saírem.

Nos próximos capítulos, usaremos esse aprendizado para entender as CNNs mais profundamente e elevar a precisão do modelo para outro nível.

← Previous postProjeto ImageNet
Next post →Monitoramento c++