Como implementar a busca de similaridade de imagem no Elasticsearch

blog-thumb-website-search.png

Aprenda como implementar a busca de imagem por similaridade no Elastic em apenas alguns passos. Comece configurando o ambiente da aplicação, depois importe o modelo de PLN e, por fim, conclua a geração de incorporações para seu conjunto de imagens.

Tenha uma visão geral da busca de similaridade de imagem com o Elastic >> 

Como configurar o seu ambiente

O primeiro passo é configurar o ambiente para sua aplicação. Os requisitos gerais são:

  • Git
  • Python 3.9
  • Docker
  • Centenas de imagens

É importante usar centenas de imagens para garantir os melhores resultados.

Vá até a pasta de trabalho e verifique o código do repositório criado. Em seguida, navegue até a pasta do repositório.

$ git clone https://github.com/radoondas/flask-elastic-image-search.git
$ cd flask-elastic-image-search

Como você usará o Phyton para executar o código, precisará verificar se todos os requisitos estão cumpridos e se o ambiente está pronto. Agora, crie o ambiente virtual e instale todas as dependências.

$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install -r requirements.txt

O cluster do Elasticsearch e o modelo de incorporação

Faça login em sua conta para iniciar um cluster do Elasticsearch. Configure um pequeno cluster com o seguinte: 

  • Um nó HOT com memória de 2 GB
  • Um nó ML (Machine Learning) com memória de 4 GB (o tamanho do nó é importante porque o modelo PLN a ser importado para o Elasticsearch consome ~1,5 GB de memória)

Depois que a implantação estiver pronta, vá ao Kibana e verifique a capacidade dos seus nós de machine learning. Você verá um nó de machine learning na visualização. Não há modelos carregados no momento.

Carregue o modelo de incorporação CLIP da OpenAI usando a biblioteca do Eland. O Eland é um cliente Phyton do Elasticsearch para explorar e analisar dados no Elasticsearch e pode trabalhar tanto com texto quanto com imagens. Você usará esse modelo para gerar incorporações com base na entrada de texto e da consulta de imagens correspondentes. Encontre mais detalhes na documentação da biblioteca do Eland.

Para o próximo passo, você precisará do endpoint do Elasticsearch. Ele pode ser obtido no console da nuvem do Elasticsearch na seção de detalhes de implantação.

Usando o URL do endpoint, execute o seguinte comando no diretório raiz do repositório. O cliente Eland será conectado ao cluster do Elasticsearch e carregará o modelo no nó de machine learning. Você pode fazer referência ao seu verdadeiro URL do cluster com o parâmetro –url. Por exemplo, veja abaixo a referência a  ‘image-search.es.europe-west1.gcp.cloud.es.io’ como URL do cluster.

--url https://elastic:<password>@image-search.es.europe-west1.gcp.cloud.es.io:443

Digite o comando de importação do Eland.

$ eland_import_hub_model --url https://elastic:<password>@<URL>:443 \
  --hub-model-id sentence-transformers/clip-ViT-B-32-multilingual-v1 \
  --task-type text_embedding --ca-certs app/conf/ess-cloud.cer \
  --start

A saída será semelhante ao seguinte:

2022-12-12 13:40:52,308 INFO : Establishing connection to Elasticsearch
2022-12-12 13:40:52,327 INFO : Connected to cluster named 'image-search-8.6.1' (version: 8.5.3)
2022-12-12 13:40:52,328 INFO : Loading HuggingFace transformer tokenizer and model 'sentence-transformers/clip-ViT-B-32-multilingual-v1'
2022-12-12 13:41:03,032 INFO : Creating model with id 'sentence-transformers__clip-vit-b-32-multilingual-v1'
2022-12-12 13:41:03,050 INFO : Uploading model definition
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 129/129 [00:42<00:00,  3.01 parts/s]
2022-12-12 13:41:45,902 INFO : Uploading model vocabulary
2022-12-12 13:41:46,120 INFO : Starting model deployment
2022-12-12 13:41:52,825 INFO : Model successfully imported with id 'sentence-transformers__clip-vit-b-32-multilingual-v1'

É provável que o upload leve alguns minutos para ser concluído, dependendo da sua conexão. Quando terminar, verifique a lista de modelos treinados na página do Kibana de machine learning: Menu -> Analytics -> Machine Learning -> Model management ->Trained models. Verifique se o modelo CLIP de PLN está no estado ‘started’.

Caso você receba uma mensagem na tela — ML job and trained model synchronization required — clique no link para sincronizar os modelos.

Como criar incorporações de imagem

Depois de configurar o cluster do Elasticsearch e importar o modelo de incorporação, você precisa vetorizar seus dados de imagem e criar incorporações de imagem para cada imagem individual do conjunto de dados.

Para criar essas incorporações, use um script simples do Python. Você pode encontrar o script aqui: create-image-embeddings.py. O script percorrerá o diretório das suas imagens e gerará incorporações de imagem individuais. Ele criará o documento com o nome e o caminho relativo, que será salvo em um índice do Elasticsearch ‘my-image-embeddings’ usando o mapping fornecido. 

Coloque todas as suas imagens (fotos) na pasta ‘app/static/images’. Use uma estrutura de diretório com subpastas para manter as imagens organizadas. Uma vez que todas as imagens estejam prontas, execute o script com alguns parâmetros.

É crucial ter pelo menos algumas centenas de imagens para conseguir resultados razoáveis. Quando você tem poucas imagens, não consegue os resultados esperados, já que o espaço no qual a busca acontece é muito limitado, e as distâncias para buscar vetores são muito similares.

Na pasta image_embeddings, execute o script e use seus valores para as variáveis.

$ cd image_embeddings
$ python3 create-image-embeddings.py \
  --es_host='https://image-search.es.europe-west1.gcp.cloud.es.io:443' \
  --es_user='elastic' --es_password=<password> \
  --ca_certs='../app/conf/ess-cloud.cer'

Dependendo do número de imagens, de seus tamanhos, da CPU e da conexão de rede, essa tarefa pode levar algum tempo. Experimente usar um número menor de imagens antes de tentar processar o conjunto de dados inteiro.
Depois que o script for concluído, você poderá verificar se o índice my-image-embeddings existe e tem documentos correspondentes usando as ferramentas de desenvolvimento do Kibana.

GET _cat/indices/my-image-embeddings?v

health status index               uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   my-image-embeddings vfA3wOheT1C79R-PceDyXg   1   1       1222            0     24.4mb         12.2mb

Ao analisar os documentos, você perceberá objetos JSON muito similares (como o exemplo). Você verá o nome da imagem, bem como seu ID, e também o caminho relativo dentro da pasta images. Esse caminho é usado na aplicação front-end para exibir de forma apropriada a imagem durante a busca.
A parte mais importante do documento JSON é a ‘image_embedding’ que contém o vetor denso produzido pelo modelo CLIP. O vetor é usado quando a aplicação está buscando alguma imagem ou uma imagem similar.

{
   "_index": "my-image-embeddings",
   "_id": "_g9ACIUBMEjlQge4tztV",
   "_score": 6.703597,
   "_source": {
     "image_id": "IMG_4032",
     "image_name": "IMG_4032.jpeg",
     "image_embedding": [
       -0.3415695130825043,
       0.1906963288784027,
       .....
       -0.10289803147315979,
       -0.15871885418891907
       ],
     "relative_path": "phone/IMG_4032.jpeg"
   }
}

Como usar a aplicação Flask para buscar imagens

Agora que seu ambiente está totalmente configurado, você pode avançar para os próximos passos, realmente buscando imagens com linguagem natural e encontrando imagens semelhantes com a aplicação Flask que fornecemos como prova de conceito. A aplicação da web tem uma UI simples que facilita a busca de imagem. Você pode acessar o protótipo da aplicação Flask neste repo do GitHub. 

A aplicação em segundo plano executa duas tarefas. Depois que você insere a string da busca na caixa de busca, o texto é vetorizado pelo endpoint _infer de machine learning. Em seguida, a consulta com o seu vetor denso é executada em comparação com o índice my-image-embeddings que contém os vetores.

Você pode ver essas duas consultas no exemplo. A primeira chamada da API usa o endpoint _infer, e o resultado é um vetor denso.

POST _ml/trained_models/sentence-transformers__clip-vit-b-32-multilingual-v1/deployment/_infer
{
  "docs" : [
    {"text_field": "endless route to the top"}
    ]
}

Na segunda tarefa, a consulta da busca, utilizaremos o vetor denso e obteremos imagens classificadas por pontuação.

GET my-image-embeddings/_search
{
  "knn": {
    "field": "image_embedding",
    "k": 5,
    "num_candidates": 10,
    "query_vector": [
    -0.19898493587970734,
    0.1074572503566742,
    -0.05087625980377197,
    ...
    0.08200495690107346,
    -0.07852292060852051
  ]
  },
  "fields": [
    "image_id", "image_name", "relative_path"

  ],
  "_source": false
}

Para que a aplicação Flask funcione perfeitamente, navegue até a pasta raiz do repositório e configure o arquivo .env. Os valores no arquivo de configuração são usados para a conexão com o cluster do Elasticsearch. Você precisa inserir os valores para as seguintes variáveis. Esses valores são os mesmos usados na geração da incorporação de imagem.

  • ES_HOST='URL:PORT'
  • ES_USER='elastic'
  • ES_PWD='password'

Quando estiver tudo pronto, execute a aplicação Flask na pasta principal e espere até que ela se inicie.

# In the main directory 
$ flask run --port=5001

Se a aplicação se iniciar, você verá uma saída semelhante à que está abaixo, indicando no final qual URL você precisa visitar para acessar a aplicação.

flask run --port=5001
 * Serving Flask app 'flask-elastic-image-search.py' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5001
Press CTRL+C to quit

Parabéns! Agora, sua aplicação deve estar funcionando perfeitamente, acessível via navegadores da internet, em http://127.0.0.1:5001.

Navegue até a aba de busca de imagem e insira o texto que descreve melhor sua imagem. Tente usar um texto descritivo ou sem palavras-chave.

No exemplo abaixo, o texto inserido foi “endless route to the top”. Os resultados mostrados são do nosso conjunto de dados. Se um usuário gostar de uma imagem específica no conjunto de resultados, basta clicar no botão ao lado dela para que imagens semelhantes apareçam. Os usuários podem fazer isso inúmeras vezes e construir seu próprio caminho através do conjunto de dados de imagem.

A busca também trabalha com o simples upload de uma imagem. A aplicação converterá a imagem em um vetor e buscará uma imagem semelhante no conjunto de dados. Para fazer isso, navegue até a terceira aba, Similar Image (Imagem similar), carregue uma imagem do disco e clique em Search (Buscar).

Como o modelo de PLN (sentence-transformers/clip-ViT-B-32-multilingual-v1) que estamos usando no Elasticsearch é multilíngue e aceita inferência em muitos idiomas, tente buscar as imagens em seu próprio idioma. Em seguida, verifique os resultados usando o idioma inglês também.

É importante observar que os modelos usados são genéricos, o que oferece uma boa precisão na busca, mas os resultados obtidos vão variar dependendo do caso de uso ou de outros fatores. Se for necessário ter mais precisão, você terá que adaptar um modelo genérico ou desenvolver o seu próprio, já que o modelo CLIP serve apenas como um ponto de partida.

Resumo do código

Você encontra o código completo no repositório do GitHub. É possível inspecionar o código em routes.py, que implementa a lógica principal da aplicação. Além da imprescindível definição da rota, você deve se concentrar em métodos que definam os endpoints _infer e _search (infer_trained_model e knn_search_images). O código que gera as incorporações de imagem está localizado no arquivo create-image-embeddings.py.

Resumo

Agora que você tem o app Flask todo configurado, pode buscar seu próprio conjunto de imagens com facilidade! O Elastic oferece integração nativa da busca vetorial dentro da plataforma, o que evita a comunicação com processos externos. Você tem a flexibilidade para desenvolver modelos de incorporação personalizados e empregar os que tenha desenvolvido com o PyTorch.

A busca de imagem semântica proporciona os seguintes benefícios, comparados com outras abordagens tradicionais de busca de imagem:

  • Maior precisão: a similaridade do vetor captura o contexto e as associações sem contar com as meta-descrições textuais das imagens.
  • Uma experiência aprimorada para o usuário: eles descrevem o que estão procurando, ou fornecem uma imagem de exemplo, em vez de adivinhar quais palavras-chave são mais relevantes para sua busca.
  • Categorização de bancos de dados de imagem: não há preocupação com a catalogação das imagens, pois a similaridade encontra imagens relevantes em uma pilha delas sem a necessidade de organizar tudo.

Se o seu caso de uso precisa usar mais os dados de texto, você pode aprender mais sobre como implementar a busca semântica e aplicar o processamento de linguagem natural ao texto nos blogs anteriores. Para os dados de texto, uma combinação de similaridades de vetor com a pontuação tradicional de palavras-chave reúne o que há de melhor nas duas realidades.
Tudo pronto para começar? Inscreva-se em um workshop prático de busca vetorial no nosso hub de eventos virtuais e participe da comunidade em nosso fórum de discussão online.