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 .

Join us at AWS re:Invent 2024! Learn how to use MongoDB for AI use cases.
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Produtoschevron-right
Atlaschevron-right

Crie um mecanismo de pesquisa de imagens com Python e MongoDB

Mark Smith8 min read • Published Jan 31, 2024 • Updated Sep 18, 2024
JupyterAtlasPesquisa vetorialPython
APLICATIVO COMPLETO
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse 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.
Neste tutorial, mostrarei como usar o modeloclic-ViT-L-14, que codifica texto e imagens no mesmo espaço vetorial. Como estamos usando Python, instalarei o modelo diretamente em meu ambiente Python para executar localmente. Na produção, você provavelmente não gostaria de ter seu modelo de incorporação executado diretamente dentro de seu aplicativo web, pois ele acopla muito fortemente seu modelo, que requer uma CPU poderosa, ao restante do aplicativo, que geralmente será vinculado a IO. Nesse caso, você pode hospedar um modelo apropriado no Abraços Face ou em uma plataforma semelhante.

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

Todo o código descrito neste tutorial está hospedado no GitHub.
A primeira coisa que você vai querer fazer é criar um ambiente virtual usando sua técnica favorita.Costumo usarvenv, que vem com o Python.
Depois de fazer isso, instale dependências com:
1pip install -r requirements.txt
Em seguida, você precisará definir uma variável de ambiente, MONGODB_URI, contendo a connection string para seu cluster MongoDB.
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"
Outra coisa que você precisará é de um diretório "images" que contenha algumas imagens para indexar! Fiz o download  do conjunto de dados ImageNet 1000 (mini) da Kaggle, que contém muitas imagens com cerca de 4GB, mas você pode usar um conjunto de dados diferente, se preferir. O notebook pesquisa o diretório "images" recursivamente, portanto, não é necessário ter tudo no nível superior.
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))
Nesta linha de código, model é o SentenceTransformer que criei acima e Image vem da bibliotecaPillow e é usado para carregar os dados da imagem.
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.
Observação: lembre-se de que queries vetoriais podem ser combinadas com qualquer outra técnica de query que você normalmente usaria no MongoDB! Essa é a grande vantagem que você obtém usando o Atlas Vector Search — ele faz parte do MongoDB Atlas, para que você possa executar query e transformar seus dados da maneira que quiser e até mesmo combiná-los com o poder do Atlas Search para consultas de texto livre.
O Notebook Jupyter carrega imagens em um loop — por padrão, ele carrega 10 imagens — mas isso não é suficiente para ver os benefícios de um mecanismo de pesquisa de imagens, então você provavelmente vai querer alterar NUMBER_OF_IMAGES_TO_LOAD para 1000 e execute o bloco de código de carregamento da imagem novamente.

Procurando imagens

Depois de indexar um bom número de imagens, é hora de testar se elas funcionam bem. Eu defini duas funções que podem ser usadas para isso. A primeira função, display_images, pega uma lista de documentos e exibe as imagens associadas em uma grade. Não estou incluindo o código aqui porque é uma função utilitária.
A segunda função, image_search, pega uma frase de texto, codifica-a como uma incorporação de vetor e, em seguida, usa o estágio de agregação$vectorSearchdo MongoDB para procurar imagens mais próximas desse local do vetor, limitando o resultado aos nove documentos mais próximos:
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)
O  estágio$projectadiciona um campo "score" que mostra a semelhança de cada documento com o vetor de consulta original. 1.0 significa "exatamente o mesmo," enquanto 0.0 significaria que a imagem retornada era totalmente diferente.
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!
Uma captura de tela, mostrando uma grade contendo fotos 9 de tubarões
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"))
Uma grade de fotos. A maioria das fotos contém um cachorro ou nuvens, ou ambos. Um dos cães é definitivamente um 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"))
Uma grade de fotos de crianças, brinquedo ou coisas como borrachas coloridas.
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"))
Fotografias de animais com aparência entediada e levemente triste, exceto por uma foto que contém um jovem com aparência entediada e levemente triste.

Próximos passos

Esperemos que você tenha achado este tutorial tão legal de ler quanto eu deixei de escrever!
Se você quisesse executar este modelo em produção, provavelmente desejaria usar um serviço de hospedagem como o Hugging Face, mas eu realmente gosto da capacidade de instalar e testar um modelo em meu laptop com uma única linha de código. Depois que a geração de incorporação, que exige muito do processador e, portanto, é uma tarefa de bloqueio, é delegada a uma chamada de API, seria mais fácil construir um wrapper FastAPI em torno da funcionalidade desse código. Então, você poderia construir uma interface web poderosa em torno dele e implantar seu próprio mecanismo de pesquisa de imagens personalizado.
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.
Iniciar a conversa

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Podcast

Episódio 1 da série de podcasts de aniversário de 5 anos do Atlas – Do Onramp ao Atlas


Aug 17, 2023 | 22 min
Tutorial

Tutorial do MongoDB Atlas Data Federation: consultas federadas e $out para AWS S3


Jan 23, 2024 | 7 min read
Artigo

Atlas Search Playground: experimentação fácil


Jun 03, 2024 | 7 min read
Tutorial

Como automatizar a cópia contínua de dados do MongoDB para o S3


Jan 23, 2024 | 8 min read
Sumário
  • Construindo um mecanismo de pesquisa de imagens com Python e MongoDB