Recomendação de imagens por similaridade utilizando TensorFlow Similarity

Antonio Ramos
A3data
Published in
9 min readMar 29, 2023

Aprendizado por similaridade

Ao estudar sobre aprendizado de máquina, comumente nos deparamos com problemas de classificação, regressão e clusterização. Porém existe uma infinidade de outros problemas que o aprendizado de máquina pode solucionar. O aprendizado por similaridade (ou “similarity learning” em inglês) é uma técnica de aprendizado de máquina que se concentra em encontrar padrões de similaridade entre dados. O objetivo é criar modelos que possam reconhecer novos dados com base em sua semelhança com dados de treinamento previamente vistos.

Existem vários métodos de aprendizado por similaridade, mas um dos mais comuns é o uso de redes neurais. As redes neurais podem aprender a representar dados em um espaço vetorial de alta dimensão, onde padrões de similaridade entre os dados podem ser facilmente detectados. Os dados podem ser representados por vetores (embeddings) e, em seguida, comparados usando uma medida de distância, como a distância euclidiana ou a distância de coseno.

Casos de uso

A pesquisa por similaridade esta presente no nosso dia a dia mais do que imaginamos, existem uma diversidade de casos de usos onde temos a presença do aprendizado por similaridade, alguns deles são:

  1. E-commerce: sites de e-commerce podem usar recomendação por similaridade de imagens para sugerir produtos similares aos que os clientes estão visualizando ou comprando. Isso pode aumentar as chances de uma compra adicional e aumentar a satisfação do cliente.
  2. Serviços de streaming de vídeo: plataformas de streaming de vídeo, como Netflix e YouTube, podem usar recomendação por similaridade de imagens para recomendar filmes ou vídeos semelhantes aos que os usuários estão assistindo.
  3. Busca de imagens: os sistemas de busca de imagens podem usar recomendação por similaridade para sugerir imagens semelhantes às que os usuários estão procurando.
  4. Coleções de arte e museus: coleções de arte e museus podem usar recomendação por similaridade para ajudar os visitantes a descobrir obras de arte que possam ser de seu interesse com base em suas preferências de arte e estilo.
  5. Moda e beleza: plataformas de moda e beleza podem usar recomendação por similaridade para recomendar produtos de beleza e roupas com base no estilo de uma pessoa, cor de pele, tipo de corpo e preferências pessoais.

TensorFlow Similarity

TensorFlow Similarity é uma extensão da biblioteca TensorFlow que oferece suporte a cálculos de similaridade e busca por similaridade. Ela foi projetada para ser usada em sistemas de recomendação, pesquisa de imagem, classificação e outras tarefas que envolvam a análise de dados de alta dimensionalidade.

A biblioteca é baseada em cima da API do TensorFlow 2.x e oferece uma série de operações e camadas personalizadas que facilitam o cálculo de métricas de similaridade, como a distância euclidiana, a distância coseno e a distância de Manhattan.

Encontrar itens relacionados é uma habilidade que possui diversas aplicações práticas, como identificar roupas de aparência semelhante, descobrir qual música está tocando no momento ou ajudar a encontrar animais de estimação perdidos. Em geral, a capacidade de recuperar rapidamente itens relacionados é crucial em muitos sistemas de informação fundamentais, incluindo pesquisas multimídia, sistemas de recomendação e fluxos de agrupamento.

Na prática, o TensorFlow Similarity utiliza modelos de aprendizado profundo para produzir embeddings que são projetados em um espaço métrico onde através de uma medida de distância (Coseno, Manhattan, euclidiana) para encontrar o par mais próximo da imagem de envio.

Os modelos de similaridade aprendem a produzir embeddings que projetam itens em um espaço métrico onde os itens semelhantes estão próximos e distantes dos diferentes

Entre as principais funcionalidades da biblioteca estão:

  • Implementação de camadas personalizadas, como a camada de similaridade, que calcula a similaridade entre dois conjuntos de vetores.
  • Implementação de métricas de distância, como a distância euclidiana, a distância coseno e a distância de Manhattan.
  • Implementação de algoritmos de busca por similaridade, como o algoritmo KNN (k-nearest neighbors).
  • Suporte a vários tipos de entrada de dados, como tensores, dados esparsos e dados em formato de sequência.
  • Suporte a aceleração de hardware, como GPUs e TPUs.

Como treinar um modelo de similaridade utilizando TensorFlow Similarity

No exemplo a seguir, veremos como fazer um simples treinamento de um modelo de simiaridade utilizando um conjunto de dados de animais de estimação com o objetivo de encontrar animais similares a partir de uma pesquisa.

Importações

import os
import random
from time import time

import numpy as np
from IPython.display import Markdown, display
from matplotlib import pyplot as plt

os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
import tensorflow as tf

try:
import tensorflow_similarity as tfsim
except ModuleNotFoundError:
!pip install tensorflow_similarity
import tensorflow_similarity as tfsim

Preparação dos dados

Neste primeiro passo, vamos carregar o conjunto de dados de animais de estimação Oxford-IIIT diretamente do catálogo do conjunto de dados TensorFlow, porém você pode utilizar outro samples para carregar seus dados. Este conjunto de dados tem 37 classes representando diferentes raças de gatos e cães com cerca de 200 imagens para cada classe.

No entanto, as imagens do conjunto de dados não são do mesmo tamanho, pelo que teremos de as redimensionar como parte do carregamento de dados. A EfficientNetSim espera que as imagens sejam de 224x224 na configuração padrão. No entanto, como utilizamos uma camada aleatória de cultivo e redimensionamento como parte da nossa estratégia de aumento, é importante ter imagens que sejam ligeiramente maiores do que o tamanho de entrada da EfficientNet. Assim, redimensionamos as imagens para 300 na função abaixo, que funciona bem, mas pode experimentar tamanhos diferentes desde que sejam superiores a 224.

IMG_SIZE = 300

def resize(img, label):
with tf.device("/cpu:0"):
img = tf.cast(img, dtype="int32")
img = tf.image.resize_with_pad(img, IMG_SIZE, IMG_SIZE)
return img, label

As seguintes células carregam dados diretamente do catálogo TensorFlow utilizando a similaridade TFDatasetMultiShotMemorySampler.

É necessário utilizar um amostrador para garantir que cada lote contenha pelo menos N amostras de cada classe incluida em cada lote. Caso contrário, a perda contrastiva não funciona correctamente, uma vez que não pode calcular distâncias positivas.

Como os modelos de similaridade nos permitem combinar dados de classes não vistas, é possível experimentar a capacidade do modelo de generalizar apenas através de treino num subconjunto das classes. Sinta-se à vontade para experimentar a proporção de classes vistas e não vistas, alterando os parâmetros do amostrador abaixo. Quanto mais exemplos forem vistas durante o treino, melhor será o seu desempenho.

training_classes = 16
examples_per_class_per_batch = 4
train_cls = random.sample(range(37), k=training_classes)
classes_per_batch = max(16, training_classes)
target_img_size = 224

print(f"Class IDs seen during training {train_cls}")

def img_augmentation(img_batch, y, *args):
batch_size = tf.shape(img_batch)[0]
img_batch = tf.image.random_crop(img_batch, (batch_size,target_img_size,target_img_size,3))
img_batch = tf.image.random_flip_left_right(img_batch)
return img_batch, y

train_ds = tfsim.samplers.TFDatasetMultiShotMemorySampler(
"oxford_iiit_pet",
splits="train",
examples_per_class_per_batch=examples_per_class_per_batch,
classes_per_batch=classes_per_batch,
preprocess_fn=resize,
class_list=train_cls,
augmenter=img_augmentation,
)

test_ds = tfsim.samplers.TFDatasetMultiShotMemorySampler(
"oxford_iiit_pet",
splits="test",
total_examples_per_class=20,
classes_per_batch=classes_per_batch,
preprocess_fn=resize,
)

Preparação do modelo

Antes de treinar o modelo é importante configurar os callbacks, que são funções que são chamadas em determinados pontos durante o treinamento do modelo, geralmente ao final de cada época. O objetivo dos callbacks é monitorar e modificar o comportamento do modelo durante o treinamento.

A maioria das métricas utilizadas para avaliar modelos de similaridade não podem ser computadas sem indexação dos embeddings e classificação de correspondência de consulta. TensorFlow Similarity fornece callbacks que facilitam o cálculo destas métricas de desempenho durante a formação.

Estes callbacks funcionam através de dois conjuntos de exemplos desajustados:

  1. Um conjunto de exemplos-alvo e rótulos a serem indexados. Estes serão exemplos devolvidos pela pesquisa.
  2. Um conjunto de exemplos e rótulos de consulta que serão pesquisados.
num_targets = 200
num_queries = 300
k = 3

queries_x, queries_y = test_ds.get_slice(0, num_queries)
targets_x, targets_y = test_ds.get_slice(num_queries, num_targets)
tsc = tfsim.callbacks.EvalCallback(
queries_x,
queries_y,
targets_x,
targets_y,
metrics=["f1"],
k=k,
)

val_loss = tfsim.callbacks.EvalCallback(
queries_x,
queries_y,
targets_x,
targets_y,
metrics=["binary_accuracy"],
known_classes=tf.constant(train_cls),
k=k,
)

callbacks = [
val_loss,
tsc,
]

Treinamento do modelo

Após a configuração dos callbacks, podemos treinar o nosso modelo de similaridade. Vamos agora realizar o fine-tune em arquitetura EfficientNetSim usando a função de perda CircleLoss. Como vamos fazer o fine-tune do modelo, não precisamos de muitas épocas. Em particular, porque o conjunto de dados é muito pequeno. O pequeno tamanho do conjunto de dados também significa que o modelo não generalizará muito bem, e as métricas não parecerão impressionantes. No entanto, é por isso que a inspeção visual é importante, na prática a similaridade parece realmente boa e o modelo é suficientemente bom.

Além disso, você também pode experimentar outras arquiteturas da sua escolha, o Tensorflow Similarity possui suporte para mais algumas.

embedding_size = 128

model = tfsim.architectures.EfficientNetSim(
train_ds.example_shape,
embedding_size,
pooling="gem",
gem_p=3.0,
)
epochs = 5
LR = 0.0001
gamma = 256
steps_per_epoch = 100
val_steps = 50
distance = "cosine"

loss = tfsim.losses.CircleLoss(gamma=gamma)

brute_force_search = tfsim.search.NMSLibSearch(
distance=distance,
dim=embedding_size,
method='brute_force',
)

model.compile(
optimizer=tf.keras.optimizers.Adam(LR),
loss=loss,
distance=distance,
search=brute_force_search,
)
history = model.fit(
train_ds,
epochs=epochs,
steps_per_epoch=steps_per_epoch,
validation_data=test_ds,
validation_steps=val_steps,
callbacks=callbacks,
)
Epoch 1/5
100/100 [==============================] - ETA: 0s - loss: 55.0701Warmup complete
7/7 [==============================] - 1s 41ms/step
5/5 [==============================] - 0s 51ms/step
6/6 [==============================] - 0s 25ms/step
7/7 [==============================] - 0s 24ms/step
10/10 [==============================] - 0s 38ms/step
100/100 [==============================] - 18s 116ms/step - loss: 55.0701 - val_loss: 51.4159 - binary_accuracy_known_classes: 0.7803 - binary_accuracy_unknown_classes: 0.7857 - f1: 0.8785
Warmup complete
Epoch 2/5
7/7 [==============================] - 0s 25ms/step- loss: 3
5/5 [==============================] - 0s 25ms/step
6/6 [==============================] - 0s 24ms/step
7/7 [==============================] - 0s 25ms/step
10/10 [==============================] - 0s 27ms/step
100/100 [==============================] - 9s 90ms/step - loss: 37.0859 - val_loss: 46.6403 - binary_accuracy_known_classes: 0.8712 - binary_accuracy_unknown_classes: 0.7798 - f1: 0.9011
Epoch 3/5
7/7 [==============================] - 0s 24ms/step- loss: 2
5/5 [==============================] - 0s 23ms/step
6/6 [==============================] - 0s 23ms/step
7/7 [==============================] - 0s 24ms/step
10/10 [==============================] - 0s 24ms/step
100/100 [==============================] - 9s 87ms/step - loss: 27.8631 - val_loss: 46.3947 - binary_accuracy_known_classes: 0.8712 - binary_accuracy_unknown_classes: 0.7798 - f1: 0.9011
Epoch 4/5
7/7 [==============================] - 0s 24ms/step- loss: 2
5/5 [==============================] - 0s 24ms/step
6/6 [==============================] - 0s 24ms/step
7/7 [==============================] - 0s 24ms/step
10/10 [==============================] - 0s 24ms/step
100/100 [==============================] - 9s 92ms/step - loss: 21.8307 - val_loss: 46.4019 - binary_accuracy_known_classes: 0.8712 - binary_accuracy_unknown_classes: 0.7679 - f1: 0.8971
Epoch 5/5
7/7 [==============================] - 0s 24ms/step- loss: 1
5/5 [==============================] - 0s 25ms/step
6/6 [==============================] - 0s 25ms/step
7/7 [==============================] - 0s 25ms/step
10/10 [==============================] - 0s 24ms/step
100/100 [==============================] - 10s 97ms/step - loss: 19.1875 - val_loss: 44.7902 - binary_accuracy_known_classes: 0.8561 - binary_accuracy_unknown_classes: 0.7619 - f1: 0.8909

Métricas do modelo

As plotagens seguintes mostram que o modelo tem dificuldade em generalizar-se, uma vez que a perda para os dados de validação se mantém na sua maioria plana enquanto a perda para os dados de treinamento diminui.

plt.plot(history.history["loss"])
plt.plot(history.history["val_loss"])
plt.legend(["loss", "val_loss"])
plt.title(f"Loss: {loss.name} - LR: {LR}")
plt.show()

Indexação

Após o treinamento, precisamos avaliar os resultados, mas antes disso precisamos indexar as imagens ao modelo. Vamos indexar cerca de 1.000 exemplos de todas as 37 classes e usar o restante do conjunto de dados de teste como consultas não vistas.

index_size = 360
query_size = 360
index_x, index_y = test_ds.get_slice(0, index_size)
index_data = tf.cast(index_x, dtype="int32")

test_x, test_y = test_ds.get_slice(index_size, query_size)
test_y = [int(c) for c in test_y]
test_data = tf.cast(test_x, dtype="int32")

model.reset_index()
model.index(index_x, index_y, data=index_data)
[Indexing {tf.shape(x)} points]
|-Computing embeddings
12/12 [==============================] - 0s 24ms/step
|-Storing data points in key value store
|-Adding embeddings to index.
|-Building index.

Visualização dos resultados

Conforme mencionado anteriormente, pode ser difícil ter uma noção da qualidade do modelo apenas com base nas métricas. Uma abordagem complementar é inspecionar manualmente um conjunto de resultados de consulta para obter uma noção da qualidade da correspondência. Observar o resultado do modelo, embora imperfeito, ainda retorna resultados significativamente similares. O modelo é capaz de encontrar imagens de animais de aparência similar, independentemente de sua pose ou iluminação da imagem.

num_examples = 3
num_neigboors = 5
idxs = random.sample(range(len(test_y)), num_examples)
batch = tf.gather(test_x, idxs)
nns = model.lookup(batch, k=num_neigboors)
for bid, nn in zip(idxs, nns):
if test_y[bid] in train_cls:
display(Markdown("**Known Class**"))
else:
display(Markdown("**Unknown Class**"))
tfsim.visualization.viz_neigbors_imgs(test_data[bid], test_y[bid], nn, class_mapping=breeds, cmap="Greys")

O aprendizado por similaridade é uma técnica poderosa que permite comparar e encontrar padrões em dados complexos. Ao utilizar o TensorFlow, os desenvolvedores podem criar modelos de similaridade precisos e escaláveis para lidar com grandes volumes de dados em tempo real, com diversas aplicações práticas, como reconhecimento de imagens e recomendação de produtos. Esse modelo é relevante e necessário em um mundo onde a análise de dados é fundamental para as estratégias de negócios.

Se você está interessado em aprender mais sobre o uso de dados para impulsionar o crescimento dos negócios, a A3Data é uma empresa de consultoria especializada em análise de dados, que oferece soluções personalizadas para empresas de todos os tamanhos e setores. Através de uma equipe altamente capacitada e experiente em diversas áreas, a A3Data pode ajudar a sua empresa a desenvolver e implementar estratégias baseadas em dados para otimizar o desempenho e alcançar resultados de alto impacto. Visite o site da A3Data para saber mais sobre como podemos ajudar a sua empresa a aproveitar ao máximo o potencial dos dados.

--

--