https://a.storyblok.com/f/270183/674027/98b9749478/building-a-dog-breed-detector-using-machine-learning.png

Criação de um detector de raças de cães usando aprendizado de máquina

Publicado em May 10, 2021

Tempo de leitura: 10 minutos

Aqui na Nexmo, usamos o Facebook Workplace como um dos nossos diversos canais de comunicação. Se você ainda não o usou ou nunca ouviu falar dele, é igual ao Facebook, mas voltado para empresas. Todos nós aqui na Nexmo temos um Account e podemos ver e participar de diferentes grupos em toda a organização.

Há alguns meses, um dos nossos colegas de trabalho criou um grupo para mostrarmos nossos animais de estimação, e foi uma ótima ideia; muitos membros da equipe postam fotos dos seus bichinhos. Eu dou uma olhada no grupo quase todos os dias, e é uma boa maneira de aproveitar as pequenas alegrias da vida (cachorrinhos!).

wp-groupwp-group

Então, depois de ver as fotos que todo mundo postou de seus cães, gatos e até coelhos, algumas pessoas perguntaram: “Que raça é essa?”. Quando vi isso, tive uma ideia: criar um algoritmo de aprendizado de máquina para identificar a raça do cão que aparecia na foto.

Nesta postagem, vamos aprender a criar um detector de raças de cães usando Keras, que é uma estrutura muito popular para a criação de modelos de aprendizado de máquina.

Pré-requisitos

Este post pressupõe que você tenha algum conhecimento de Python, bem como uma compreensão muito básica de aprendizado de máquina. Você deve saber o que é o Keras e como treinar um modelo básico de aprendizado de máquina.

Por onde eu começo?

Para resolver muitos problemas de aprendizado de máquina, são necessários dados — e muitos deles. Especificamente, precisamos de fotos de muitos cães e de saber quais raças existem. Para este projeto, vamos usar o conjunto de dados do Desafio de Identificação de Raças de Cães no Kaggle. Esse conjunto de dados contém mais de 10.000 imagens de cães, categorizadas por raça.

Construindo o modelo

Primeiro, vamos começar construindo o modelo. Vou usar o Google Colab para criar meu Jupyter Notebook, em Python. Um Jupyter Notebook é um aplicativo web de código aberto que permite escrever código, além de texto e imagens. É uma ótima maneira de começar. O Google Colab é um serviço gratuito que hospeda seus Jupyter Notebooks.

Observação: Se você quiser ver como o modelo é construído, pode acessar meu notebook aqui.

Antes de construir o modelo, precisamos obter os dados, que estão hospedados no Kaggle. Para carregar os dados, precisamos usar um pacote para baixá-los para o nosso notebook, utilizando a API do Kaggle. Isso nos permitirá baixar o conjunto de dados da Competição de Raças de Cães. Antes de baixar o conjunto de dados, precisamos criar um Account no Kaggle e obter sua chave e seu segredo da API do Kaggle.

kaggle-create-api-tokenkaggle-create-api-token

Acesse “Criar novo token de API” e salve o arquivo no seu computador. Para baixar os dados, vamos executar este célula.

# Run this cell and select the kaggle.json file downloaded
# from the Kaggle account settings page.
from google.colab import files
files.upload()
# Let's make sure the kaggle.json file is present.
!ls -lha kaggle.json
# Next, install the Kaggle API client.
!pip install -q kaggle

# The Kaggle API client expects this file to be in ~/.kaggle,
# so move it there.
!mkdir -p ~/.kaggle
!cp kaggle.json ~/.kaggle/

# This permissions change avoids a warning on Kaggle tool startup.
!chmod 600 ~/.kaggle/kaggle.json

#download the dataset for the dog-breed identification challenge https://www.kaggle.com/c/dog-breed-identification
!kaggle competitions download -c dog-breed-identification

#unzip the downloaded files
!unzip labels.csv.zip
!unzip test.zip
!unzip train.zip

Se você não entender todas as linhas deste código ou de qualquer outra seção de código, não se preocupe. Você poderá copiar e colar o código-fonte para executar tudo por conta própria, sem precisar se preocupar com os detalhes.

Ao executar esta célula, será solicitado que você selecione um arquivo. Encontre o arquivo JSON baixado do Kaggle e faça o upload para a célula. Em seguida, você poderá executar a API do Kaggle e baixar o conjunto de dados para o notebook. Assim que os arquivos forem baixados, vamos descompactá-los usando !unzip. O ! antes do comando permite que você execute uma ação de linha de comando dentro do Google Colab. O !unzip simplesmente descompacta cada arquivo.

Os arquivos baixados do Kaggle contêm o seguinte:

  • Imagens de treinamento, localizadas na \train pasta

  • Imagens de teste, localizadas na \test pasta

  • Um arquivo CSV chamado labels.csv, contendo o nome da raça e o nome do arquivo, que aponta para a imagem na pasta de treinamento.

Agora, podemos carregar nossos dados em um DataFrame, usando o Pandas. Um DataFrameé uma estrutura de dados simples que contém linhas e colunas, semelhante a um arquivo CSV. O Pandas é um pacote do Python que oferece estruturas de dados de alto desempenho e fáceis de usar, além de ferramentas de análise de dados. Ele é usado em muitas aplicações de aprendizado de máquina. Se você trabalha com aplicações de aprendizado de máquina, um dos primeiros pacotes que vai usar é o Pandas. Se quiser saber mais sobre o Pandas, confira o tutorial oficial, Tutorial de 10 minutos sobre o Pandas.

Usando o Pandas, podemos importar o arquivo CSV do conjunto de dados do Kaggle para um DataFrame do Pandas.

Fonte.

#import the necessary packages
import pandas as pd
import numpy as np

#constants
num_classes = 12 # the number of breeds we want to classify
seed = 42 # makes the random numbers in numpy predictable
im_size = 299 # This size of the images
batch_size = 32

#read the csv into a dataframe, group the breeds by name and append the path the to image in the `filename` column
df = pd.read_csv('labels.csv')
selected_breed_list = list(df.groupby('breed').count().sort_values(by='id', ascending=False).head(num_classes).index)
df = df[df['breed'].isin(selected_breed_list)]
df['filename'] = df.apply(lambda x: ('train/' + x['id'] + '.jpg'), axis=1)

breeds = pd.Series(df['breed'])
print("total number of breeds to classify",len(breeds.unique()))

df.head()

Isso pega o arquivo CSV, carrega-o em um DataFrame pd.read_csv('labels.csv')e, em seguida, ordena o DataFrame por raça, em ordem alfabética. Depois, exibimos as primeiras 10 linhas usando df.head()

df_headdf_head

Em seguida, precisamos escrever uma função que redimensione todas as imagens para o tamanho que precisamos, que é 299x299px. Ficará claro por que precisamos redimensionar a imagem mais tarde.

Fonte

from keras.preprocessing import image

def read_img(img_id, train_or_test, size):
    """Read and resize image.
    # Arguments
    img_id: string
    train_or_test: string 'train' or 'test'.
    size: resize the original image.
    # Returns
    Image as numpy array.
    """
    path = train_or_test + "/" + img_id + ".jpg"
    img = image.load_img(path, target_size=size)
return image.img_to_array(img)

A read_img() função carregará a imagem no tamanho que precisamos (299x299 px) e a converterá em uma matriz numpy multidimensional. O Numpy é outro pacote do Python muito utilizado em aprendizado de máquina. Ele facilita o trabalho com esses tipos de matrizes no Python.

Em seguida, precisamos converter os nomes das raças (basenji, scottish_deerhound) em vetores (matriz unidimensional de números), já que nosso modelo de aprendizado de máquina só consegue lidar com números. Para isso, usaremos o LabelEncoder. Um LabelEncoder pega todos os nomes das raças e converte cada um deles em um número inteiro. Cada número será diferente para cada raça (0 para basenji, 1 para scottish_deerhound etc.). O Scikit-Learn é outro pacote de código aberto que facilita a introdução ao aprendizado de máquina.

Em seguida, dividiremos o conjunto de dados em dois: um para treinamento e outro para teste. Ao treinar nosso modelo, usaremos os dados do conjunto de treinamento para treiná-lo; depois, quando precisarmos avaliar seu desempenho, testaremos o modelo no conjunto de teste.

Fonte

from sklearn.preprocessing import LabelEncoder
label_enc = LabelEncoder()
np.random.seed(seed=seed)
rnd = np.random.random(len(df))
train_idx = rnd < 0.9 valid_idx = rnd >= 0.9
y_train = label_enc.fit_transform(df["breed"].values)
ytr = y_train[train_idx]
yv = y_train[valid_idx]

Por fim, pegaremos todas as imagens do conjunto de treinamento e redimensionaremos usando a read_img função que criamos anteriormente. Em seguida, precisamos processar cada imagem para colocá-la no formato correto que nosso modelo espera.

Fonte

from tqdm import tqdm
from keras.applications import xception

x_train = np.zeros((train_idx.sum(), im_size, im_size, 3), dtype='float32')
x_valid = np.zeros((valid_idx.sum(), im_size, im_size, 3), dtype='float32')
train_i = 0
valid_i = 0
for i, img_id in tqdm(enumerate(df['id'])):
    img = read_img(img_id, 'train', (im_size, im_size))
    x = xception.preprocess_input(np.expand_dims(img.copy(), axis=0))
    if train_idx[i]:
        x_train[train_i] = x
        train_i += 1
    elif valid_idx[i]:
        x_valid[valid_i] = x
        valid_i += 1
print('Train Images shape: {} size: {:,}'.format(x_train.shape, x_train.size))

[00:06, 201.73it/s]Train Images shape: (1218, 299, 299, 3) size: 326,671,254

Nesta função, percorremos todos os itens do nosso DataFrame (for i, img_id in tqdm(enumerate(df['id'])): e chamamos a read_image função, que recebe o img_id, que é o ID da imagem, que corresponde ao nome do arquivo da imagem na \train pasta, e redimensionamos para 299x299px. Em seguida, chamamos a xception.preprocess_input função.

Antes de falarmos sobre o que essa função faz, precisamos entender o que xception é.

Treinar modelos do zero exige muito mais imagens do que a quantidade que temos (10 mil), além de muito tempo de computação e recursos. Para acelerar esse processo, podemos usar uma técnica chamada Aprendizado por Transferência. Isso significa que podemos usar um modelo que já foi pré-treinado em outro conjunto de dados, como o conjunto de dados ImageNet. O Xception é um desses modelos pré-treinados. Podemos contar com o modelo pré-treinado para extrair características da imagem. Em seguida, basta treinar o modelo para nosso caso de uso específico: determinar a raça.

Experimentei alguns modelos e descobri que o Xception oferece os melhores resultados para o caso de uso de detecção de raças, com o conjunto de dados de imagens que estamos usando.

Para outros conjuntos de dados, pode haver outros modelos mais adequados às suas necessidades, que proporcionem melhores resultados. Portanto, certifique-se de fazer alguns testes antes de decidir qual modelo usar. Para saber mais sobre este e outros modelos pré-treinados, confira este link (https://www.pyimagesearch.com/2017/03/20/imagenet-vggnet-resnet-inception-xception-keras/). Esta publicação também explica o que é o ImageNet e como ele se relaciona com esses modelos pré-treinados.

OK, agora vamos voltar ao que a xception.preprocess_input() função faz. Ela pega a imagem, que agora é um array numpy, e a converte para um formato que o modelo Xception espera, no qual todos os valores do array estão entre -1 e 1, o que é conhecido como normalização.

Agora, podemos construir nosso modelo.

Como estamos usando o Xception como nosso modelo base, nosso modelo personalizado é muito simples. Para o nosso próprio modelo, carregaremos a saída do Xception — que consiste em todas as camadas que já foram treinadas com imagens do ImageNet — e, em seguida, construiremos um modelo sequencial.

Do blog do Keras: “O modelo Sequential é uma pilha linear de camadas.” - Introdução ao modelo Sequential do Keras

Isso significa apenas que podemos sobrepor outras camadas ao modelo Xception. Isso permitirá que nosso modelo seja treinado com nossas imagens de cães.

Aqui está o nosso modelo. Fonte

from keras.layers import GlobalAveragePooling2D, Dense, BatchNormalization, Dropout
from keras.optimizers import Adam, SGD, RMSprop
from keras.models import Model, Input

# create the base pre-trained model
base_model = xception.Xception(weights='imagenet', include_top=False)
# first: train only the top layers (which were randomly initialized)
# i.e. freeze all convolutional Xception layers
for layer in base_model.layers:
    layer.trainable = False

# add a global spatial average pooling layer
x = base_model.output
x = BatchNormalization()(x)
x = GlobalAveragePooling2D()(x)
# let's add a fully-connected layer
x = Dropout(0.5)(x)
x = Dense(1024, activation='relu')(x)
x = Dropout(0.5)(x)
# and a logistic layer and set it to the number of breeds we want to classify,
predictions = Dense(num_classes, activation='softmax')(x)

# this is the model we will train
model = Model(inputs=base_model.input, outputs=predictions)

Primeiro, pegamos nossa base_model, que é o Xception, e, em seguida, Freeze as camadas. Isso significa que não faremos nenhum treinamento nessas camadas, já que elas já foram treinadas. Em seguida, pegaremos a saída da base_model e adicionaremos as seguintes camadas:

A seleção dessas camadas é feita por tentativa e erro. Não existe uma maneira conhecida de determinar uma boa estrutura de rede ao construir seu modelo. Ao construir seus próprios modelos, o Stack Overflow é seu melhor amigo.

Treinamento do modelo

Agora, vamos começar a desenvolver — ou seja, a treinar — o modelo. A maneira como estou treinando o modelo é bem básica. Você verá esse tipo de código ao explorar outros modelos no Keras. Fonte

import datetime
from keras.callbacks import EarlyStopping, ModelCheckpoint

epochs = 1
learning_rate = 0.001

# checkpoints
early_stopping = EarlyStopping(monitor='val_acc', patience=5)
STAMP = "{}_dog_breed_model".format(datetime.date.today().strftime("%Y-%m-%d"))

bst_model_path = "{}.h5".format(STAMP)
model_checkpoint = ModelCheckpoint(bst_model_path,
save_best_only=True,
save_weights_only=False,
verbose=1)
# compile the model
optimizer = RMSprop(lr=learning_rate, rho=0.9)
model.compile(optimizer=optimizer,
loss='sparse_categorical_crossentropy',
metrics=["accuracy"])

hist = model.fit_generator(train_generator,
steps_per_epoch=train_idx.sum() // batch_size,
epochs=epochs, callbacks=[early_stopping, model_checkpoint],
validation_data=valid_generator,
validation_steps=valid_idx.sum() // batch_size)

model.save(bst_model_path)

Primeiro, adicionamos algumas funções de retorno de chamada, que são funções que executamos após cada rodada de treinamento, também conhecidas como epoch. Temos duas callbacks:

  • early_stopping com o patience parâmetro 5: Isso interromperá o treinamento se o modelo não apresentar melhorias após 5 épocas.

  • model_checkpoint: Isso salva o modelo em um arquivo para uso posterior.

Em seguida, definimos o otimizador como RMSprop. Um otimizador é a forma como o modelo “aprende”. A cada época, o modelo calcula a função de perda, que representa o desempenho do modelo em relação ao conjunto de teste. O objetivo é minimizar essa perda, o que é chamado de Descida de Gradiente. O Keras oferece suporte a diversos otimizadores e, em meus experimentos, o RMSProp, que utiliza o Descenso de Gradiente, pareceu funcionar melhor.

A seguir, construiremos o modelo usando model.compile função, que aceita o otimizador e a função de perda que queremos calcular (sparse_categorical_crossentropy) e definindo o metrics parâmetro para accuracy, o que nos indicará a precisão do modelo após cada época.

Depois disso, faremos nosso treinamento chamando model.fit_generator(). Os parâmetros dessa função são: ImageDataGeneratoros conjuntos de dados de treinamento e de teste, quantos passos serão executados, o número de épocas, o que será validado e o número de passos a serem validados. Vamos treinar esse modelo por 10 épocas por enquanto, só para ver como nos saímos.

Epoch 1/10
38/38 [==============================] - 40s 1s/step - loss: 0.5477 - acc: 0.8281 - val_loss: 0.0555 - val_acc: 0.9766

*skipping output for readability*

Epoch 10/10
38/38 [==============================] - 33s 857ms/step - loss: 0.2426 - acc: 0.9358 - val_loss: 0.0457 - val_acc: 0.9905

Portanto, temos um modelo com 99% de precisão na previsão de 12 raças!

Agora, podemos testar nosso modelo em algumas imagens de cães e verificar se conseguimos identificar a raça correta a partir dessas imagens. Vamos escrever uma função que receba uma imagem da internet, a formate de acordo com o que o modelo espera (imagem de 299x299 px) e faça a previsão usando model.predict(). Essa função recebe uma imagem, na forma de uma matriz numpy, e retorna o resultado como uma lista de probabilidades para cada raça. Usamos np.argmax() para encontrar o índice da maior probabilidade na saída de model.predict(). Para retornar o nome da raça, usamos a labels.csv que carregamos do conjunto de dados do Kaggle, que contém os nomes das 12 raças. Em seguida, ordenaremos a lista em ordem alfabética e retornaremos o nome da raça.

from keras.models import load_model
from keras.preprocessing import image
import matplotlib.pyplot as plt
import numpy as np
import os

def predict_from_image(img_path):
    img = image.load_img(img_path, target_size=(299, 299))
    img_tensor = image.img_to_array(img) # (height, width, channels)
    img_tensor = np.expand_dims(img_tensor, axis=0) # (1, height, width, channels), add a dimension because the model expects this shape: (batch_size, height, width, channels)
    img_tensor /= 255.

    pred = model.predict(img_tensor)
    predicted_class = sorted_breeds_list[np.argmax(pred)]

return predicted_class

Agora vamos testar essa função, só para ter certeza de que está funcionando. Vamos baixar uma foto de um cão-de-caça escocês, usando wget, que é um utilitário de linha de comando para baixar arquivos, e vamos ver como o modelo se sai.

predictpredict

Que legal! O modelo prevê que o cachorro da foto é um Deerhound escocês, e é mesmo!

Conclusão

Nesta postagem, aprendemos a construir nosso próprio modelo de aprendizado de máquina usando o Keras, a treinar nosso modelo por meio do aprendizado por transferência e a fazer previsões com nosso modelo.

Em um post futuro, vamos explicar como implantar esse modelo em um servidor como uma API simples para que outras pessoas possam usá-la. Em seguida, vamos criar um bot no Workplace que permita que qualquer pessoa do nosso grupo no Workplace pergunte qual é a raça do cão que aparece em uma foto publicada no Workplace.

Compartilhar:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Tony HungEx-funcionários da Vonage

Desenvolvedor iOS que se tornou entusiasta de Ciência de Dados e Aprendizado de Máquina. Quero que as pessoas entendam o que é o aprendizado de máquina e como podemos usá-lo em nossas Applications.