Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Saiba por que o MongoDB foi selecionado como um líder no 2024 Gartner_Magic Quadrupnt()

Crie um mecanismo de pesquisa de imagens com Python e MongoDB

Mark Smith8 min read • Published Jan 31, 2024 • Updated Sep 18, 2024
APLICATIVO COMPLETO
Facebook Icontwitter iconlinkedin icon
Avaliar este tutorial
star-empty
star-empty
star-empty
star-empty
star-empty

Construindo um mecanismo de pesquisa de imagens com Python e MongoDB

Ainda me recordo quando a pesquisa começou a funcionar no Google fotos — a plataforma onde armazeno todas as fotos que tirava no meu celular. Pareceu-me mágico que algum tipo de técnica de aprendizado de máquina pudesse me permitir descrever uma imagem em minha vasta coleção de fotos e fazer com que a plataforma retornasse essa imagem para mim, junto com quaisquer imagens semelhantes.
Uma das técnicas usadas para isso é a classificação de imagens, onde uma rede causal é usada para identificar objetos e até mesmo pessoas em uma imagem, e a imagem é marcada com esses dados. Outra técnica — que é, no mínimo, mais poderosa — é a capacidade de gerar uma incorporação vetorial para a imagem usando um modelo de incorporação que funciona com texto e imagens.
Usar um modelo de incorporação multimodal como esse permite gerar um vetor que pode ser armazenado e indexado com eficiência no MongoDB Atlas e, quando você desejar recuperar uma imagem, o mesmo modelo de incorporação pode ser usado para gerar um vetor que é então usado para procurar imagens semelhantes à descrição. É quase como mágica.

Modelos de incorporação multimodais

Um modelo de incorporação multimodal é um modelo de aprendizado de máquina que codifica informações de vários tipos de dados, como texto e imagens, em um espaço vetorial comum. Ele ajuda a vincular diferentes tipos de dados para tarefas como correspondência de texto para imagem ou tradução entre modalidades.
A vantagem disso é que o texto e as imagens podem ser indexados da mesma forma, permitindo que as imagens sejam pesquisadas fornecendo texto ou outra imagem. Você pode até pesquisar um item de texto com uma imagem, mas não consigo pensar em um motivo pelo qual você gostaria de fazer isso. A desvantagem dos modelos multimodais é que eles são muito complexos de produzir e, portanto, não são tão "inteligentes" quanto alguns dos modelos monomodo que estão sendo produzidos atualmente.
In this tutorial, I'll show you how to use the clip-ViT-L-14 model, which encodes both text and images into the same vector space. Because we're using Python, I'll install the model directly into my Python environment to run locally. In production, you probably wouldn't want to have your embedding model running directly inside your web application because it too tightly couples your model, which requires a powerful GPU, to the rest of your application, which will usually be mostly IO-bound. In that case, you can host an appropriate model on Hugging Face or a similar platform.

Descrição do mecanismo de pesquisa

Esse exemplo de mecanismo de busca será uma prova de conceito. Todo o código está disponível em um Notebook Jupyter, e estou armazenando todas as minhas imagens localmente no disco. Na produção, você gostaria de usar um serviço de armazenamento de objetos como o S3 da Amazon.
Da mesma forma, em produção, você gostaria de hospedar o modelo usando um serviço especializado ou alguma configuração dedicada no hardware apropriado, enquanto eu tentaria baixar e executar o modelo localmente.
Se você tiver uma máquina mais antiga, pode demorar um pouco para gerar os vetores, mas descobrir que, em um Intel Macbook Pro de quatro anos, poderia gerar cerca de 1,000 incorporações em 30 minutos, ou o meu Macbook Air M2 faz o mesmo em cerca de cinco minutos! De qualquer forma, talvez você Go e faça uma chávena de café para si mesmo quando o bloco de anotações chegar a essa etapa.
O mecanismo de busca usará o mesmo modelo vetorial para codificar consultas (que são texto) no mesmo espaço vetorial usado para codificar dados de imagem, o que significa que uma frase descrevendo uma imagem deve aparecer em um local semelhante à localização da imagem no espaço vetorial. Essa é a mágica dos modelos vetoriais multimodais!

Preparando-se para executar o notebook

All of the code described in this tutorial is hosted on GitHub.
The first thing you'll want to do is create a virtual environment using your favorite technique. I tend to use venv, which comes with Python.
Depois de fazer isso, instale dependências com:
1pip install -r requirements.txt
Next, you'll need to set an environment variable, MONGODB_URI, containing the connection string for your MongoDB cluster.
1# Set the value below to your cluster:
2export MONGODB_URI="mongodb+srv://image_search_demo:my_password_not_yours@sandbox.abcde.mongodb.net/image_search_demo?retryWrites=true&w=majority"
One more thing you'll need is an "images" directory, containing some images to index! I downloaded  Kaggle's ImageNet 1000 (mini) dataset, which contains lots of images at around 4GB, but you can use a different dataset if you prefer. The notebook searches the "images" directory recursively, so you don't need to have everything at the top level.
Então, você pode iniciar o notebook com:
1jupyter notebook "Image Search.ipynb"

Entendendo o código

Se você configurou o notebook conforme descrito acima, você deve ser capaz de executá-lo e seguir as explicações no caderno. Neste tutorial, vou destacar o código mais importante, mas não vou reproduzi-lo todo aqui, pois trabalhei duro para tornar o notebook compreensível sozinho.

Configurando a collection

Primeiro, vamos configurar uma coleção com um índice de pesquisa vetorial apropriado. No Atlas, se você se conectar a um cluster, poderá configurar índices de pesquisa vetorial na guia Atlas Search, mas eu preferia configurar índices em meu código para manter tudo autônomo.
O código a seguir pode ser executado várias vezes, mas só criará a coleção e o índice de pesquisa associado na primeira execução. Isto é útil se você quiser executar o notebook várias vezes!
1client = MongoClient(MONGODB_URI)
2db = client.get_database(DATABASE_NAME)
3
4# Ensure the collection exists, because otherwise you can't add a search index to it.
5try:
6    db.create_collection(IMAGE_COLLECTION_NAME)
7except CollectionInvalid:
8    # This is raised when the collection already exists.
9    print("Images collection already exists")
10
11# Add a search index (if it doesn't already exist):
12collection = db.get_collection(IMAGE_COLLECTION_NAME)
13if len(list(collection.list_search_indexes(name="default"))) == 0:
14    print("Creating search index...")
15    collection.create_search_index(
16        SearchIndexModel(
17            {
18                "mappings": {
19                    "dynamic": True,
20                    "fields": {
21                        "embedding": {
22                            "dimensions": 768,
23                            "similarity": "cosine",
24                            "type": "knnVector",
25                        }
26                    },
27                }
28            },
29            name="default",
30        )
31    )
32    print("Done.")
33else:
34    print("Vector search index already exists")
A parte mais importante do código acima é a configuração que está sendo passada para create_search_index:
1{
2    "mappings": {
3        "dynamic": True,
4        "fields": {
5            "embedding": {
6                "dimensions": 768,
7                "similarity": "cosine",
8                "type": "knnVector",
9            }
10        },
11    }
12}
Isso especifica que o índice indexará todos os campos no documento (porque "dynamic" está definido como "true") e que o campo "embedding" deve ser indexado como um vetor de incorporação, usando similaridade de cosseno. Atualmente, "knnVector" é o único tipo suportado pelo Atlas. A dimensão do vetor é definida como 768 porque esse é o número de dimensões vetoriais usadas pelo modelo CLIP.

Carregando o modelo CLIP

A linha de código a seguir pode não parecer muito, mas na primeira vez que você a executar, ela fará o download do modelo Clip-Vit-L-14, que tem cerca de 2GB:
1# Load CLIP model.
2# This may print out warnings, which can be ignored.
3model = SentenceTransformer("clip-ViT-L-14")

Geração e armazenamento de uma incorporação de vetor

Dado um caminho para um arquivo de imagem, uma incorporação para essa imagem pode ser gerada com o seguinte código:
1emb = model.encode(Image.open(path))
In this line of code, model is the SentenceTransformer I created above, and Image comes from the Pillow library and is used to load the image data.
Com o vetor de incorporação, um novo documento pode ser criado com o código abaixo:
1collection.insert_one(
2    {
3        "_id": re.sub("images/", "", path),
4        "embedding": emb.tolist(),
5    }
6)
Estou armazenando apenas o caminho para a imagem (como um identificador exclusivo) e o vetor de incorporação. Em um aplicativo do mundo real, eu armazenaria todos os metadados de imagem que meu aplicativo exigisse e provavelmente um URL para um objeto S3 contendo os próprios dados da imagem.
Note: Remember that vector queries can be combined with any other query technique you'd normally use in MongoDB! That's the huge advantage you get using Atlas Vector Search — it's part of MongoDB Atlas, so you can query and transform your data any way you want and even combine it with the power of Atlas Search for free text queries.
The Jupyter Notebook loads images in a loop — by default, it loads 10 images — but that's not nearly enough to see the benefits of an image search engine, so you'll probably want to change NUMBER_OF_IMAGES_TO_LOAD to 1000 and run the image load code block again.

Procurando imagens

Once you've indexed a good number of images, it's time to test how well it works. I've defined two functions that can be used for this. The first function, display_images, takes a list of documents and displays the associated images in a grid. I'm not including the code here because it's a utility function.
The second function, image_search, takes a text phrase, encodes it as a vector embedding, and then uses MongoDB's $vectorSearch aggregation stage to look up images that are closest to that vector location, limiting the result to the nine closest documents:
1def image_search(search_phrase):
2    """
3    Use MongoDB Vector Search to search for a matching image.
4
5    The search_phrase is first converted to a vector embedding using
6    the model loaded earlier in the Jupyter notebook. The vector is then used
7    to search MongoDB for matching images.
8    """
9    emb = model.encode(search_phrase)
10    cursor = collection.aggregate(
11        [
12            {
13                "$vectorSearch": {
14                    "index": "default",
15                    "path": "embedding",
16                    "queryVector": emb.tolist(),
17                    "numCandidates": 100,
18                    "limit": 9,
19                }
20            },
21            {"$project": {"_id": 1, "score": {"$meta": "vectorSearchScore"}}},
22        ]
23    )
24
25    return list(cursor)
The $project stage adds a "score" field that shows how similar each document was to the original query vector. 1.0 means "exactly the same," whereas 0.0 would mean that the returned image was totally dissimilar.
Com a função exibição_imagens e a função imagem_search, posso pesquisar imagens de "sharks in the pool":
1display_images(image_search("sharks in the water"))
No meu laptop, recebo a seguinte grade de nove imagens, o que é muito bom!
A screenshot, showing a grid containing 9 photos of sharks
Quando tentei pela primeira vez a pesquisa acima, não tinha imagens suficientes carregadas, então a consulta acima incluía a foto de um corgi em pé sobre ladrilhos cinza. Essa não foi uma partida muito disputada! Depois de carregar mais algumas imagens para corrigir os resultados da consulta sobre tubarões, ainda consegui encontrar a imagem do corgi pesquisando por " corgi on snow " — é a segunda imagem abaixo. Observe que nenhuma das imagens corresponde exatamente à consulta, mas algumas são definitivamente corgis e várias estão paradas na neve.
1display_images(image_search("corgi in the snow"))
A grid of photos. Most photos contain either a dog or snow, or both. One of the dogs is definitely a corgi.
Uma das coisas que realmenteadoro na pesquisa vetorial é que ela é "semântica", então posso pesquisar por algo bastante obscuro, como "infância".
1display_images(image_search("childhood"))
A grid of photographs of children or toys or things like colorful erasers.
Meu resultado favorito foi quando pesquisei "ennui" (um sentido de apatia e preocupação decorrente da falta de atividade ou euforia) que retornou fotos de bichos entediados (e de um menor)!
1display_images(image_search("ennui"))
Photographs of animals looking bored and slightly sad, except for one photo which contains a young man looking bored and slightly sad.

Próximos passos

Esperemos que você tenha achado este tutorial tão legal de ler quanto eu deixei de escrever!
If you wanted to run this model in production, you would probably want to use a hosting service like Hugging Face, but I really like the ability to install and try out a model on my laptop with a single line of code. Once the embedding generation, which is processor-intensive and thus a blocking task, is delegated to an API call, it would be easier to build a FastAPI wrapper around the functionality in this code. Then, you could build a powerful web interface around it and deploy your own customized image search engine.
Este exemplo também não demonstra muitos dos recursos de query do MongoDB. O poder do vetor Atlas Search com o MongoDB Atlas é a capacidade de combiná-lo com todo o poder da estrutura de agregação do MongoDB para consultar e agregar seus dados. Se eu tiver algum tempo, posso estender este exemplo para filtrar por critérios como a data de cada imagem e talvez permitir que as fotos sejam marcadas manualmente ou agrupadas automaticamente em coleções.

Leitura adicional

Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.

Facebook Icontwitter iconlinkedin icon
Avaliar este tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Como escolher a estratégia de chunking certa para seu aplicativo LLM


Jun 17, 2024 | 16 min read
Tutorial

Primeiros passos com o MongoDB Atlas e Ruby no Rails


Dec 11, 2023 | 6 min read
Artigo

Como criar um serviço de pesquisa em Java


Apr 23, 2024 | 11 min read
Tutorial

Improve Your App's Search Results with Auto-Tuning


Aug 14, 2024 | 5 min read