A Jornada para Otimizar a Pesquisa de Imagens em Escala de Bilhões (2/2)
Este artigo é a segunda parte de A Jornada para Otimizar a Pesquisa de Imagens em Escala de Bilhões pela UPYUN. Se você perdeu a primeira, clique aqui.
O sistema de pesquisa por imagem de segunda geração
O sistema de pesquisa por imagem de segunda geração escolhe tecnicamente a solução CNN + Milvus. O sistema é baseado em vetores de características e oferece melhor suporte técnico.
Extração de características
No campo da visão computacional, o uso de inteligência artificial tornou-se predominante. Da mesma forma, a extração de características do sistema de pesquisa por imagem de segunda geração usa rede neural convolucional (CNN) como tecnologia subjacente
O termo CNN é difícil de entender. Aqui nos concentramos em responder a duas perguntas:
- O que a CNN pode fazer?
- Por que posso usar a CNN para uma pesquisa de imagens?
Foto por memegenerator.net
Há muitas competições no campo da IA e a classificação de imagens é uma das mais importantes. O trabalho da classificação de imagens é determinar se o conteúdo da imagem é sobre um gato, um cachorro, uma maçã, uma pera ou outros tipos de objetos.
O que a CNN pode fazer? Ela pode extrair características e reconhecer objetos. Ela extrai características de múltiplas dimensões e mede o quão próximas as características de uma imagem estão das características de gatos ou cães. Podemos escolher as mais próximas como nosso resultado de identificação, que indica se o conteúdo de uma imagem específica é sobre um gato, um cachorro ou outra coisa.
Qual é a conexão entre a função de identificação de objetos da CNN e a pesquisa por imagem? O que queremos não é o resultado final da identificação, mas o vetor de características extraído de múltiplas dimensões. Os vetores de características de duas imagens com conteúdo semelhante devem estar próximos.
Qual modelo de CNN devo usar?
A resposta é VGG16. Por que escolhê-lo? Primeiro, o VGG16 tem boa capacidade de generalização, ou seja, é muito versátil. Segundo, os vetores de características extraídos pelo VGG16 têm 512 dimensões. Se houver pouquíssimas dimensões, a precisão pode ser afetada. Se houver dimensões demais, o custo de armazenar e calcular esses vetores de características é relativamente alto.
Usar CNN para extrair características de imagens é uma solução predominante. Podemos usar VGG16 como modelo e Keras + TensorFlow para a implementação técnica. Aqui está o exemplo oficial do Keras:
from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np
model = VGG16(weights=’imagenet’, include_top=False)
img_path = ‘elephant.jpg’
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
features = model.predict(x)
As características extraídas aqui são vetores de características.
1. Normalização
Para facilitar operações subsequentes, frequentemente normalizamos a característica:
O que é usado subsequentemente também é o norm_feat normalizado.
2. Descrição da imagem
A imagem é carregada usando o método image.load_img de keras.preprocessing:
from keras.preprocessing import image
img_path = 'elephant.jpg'
img = image.load_img(img_path, target_size=(224, 224))
Na verdade, é o método do TensorFlow chamado pelo Keras. Para detalhes, consulte a documentação do TensorFlow. O objeto de imagem final é, na verdade, uma instância de PIL Image (o PIL usado pelo TensorFlow).
3. Conversão de bytes
Em termos práticos, o conteúdo da imagem é frequentemente transmitido pela rede. Portanto, em vez de carregar imagens a partir de um caminho, preferimos converter dados em bytes diretamente em objetos de imagem, ou seja, PIL Images:
import io
from PIL import Image
# img_bytes: 图片内容 bytes
img = Image.open(io.BytesIO(img_bytes))
img = img.convert('RGB')
img = img.resize((224, 224), Image.NEAREST)
A img acima é a mesma que o resultado obtido pelo método image.load_img. Há duas coisas às quais prestar atenção:
- Você deve fazer a conversão RGB.
- Você deve redimensionar (resize é o segundo parâmetro do
método load_img).
4. Processamento de bordas pretas
Imagens, como capturas de tela, podem ocasionalmente ter muitas bordas pretas. Essas bordas pretas não têm valor prático e causam muita interferência. Por esse motivo, remover bordas pretas também é uma prática comum.
Uma borda preta é essencialmente uma linha ou coluna de pixels em que todos os pixels são (0, 0, 0) (imagem RGB). Remover a borda preta é encontrar essas linhas ou colunas e excluí-las. Isso é, na verdade, uma multiplicação de matriz 3-D no NumPy.
Um exemplo de remoção de bordas pretas horizontais:
# -*- coding: utf-8 -*-
import numpy as np
from keras.preprocessing import image
def RemoveBlackEdge(img):
Args:
img: PIL image instance
Returns:
PIL image instance
"""
width = img.width
img = image.img_to_array(img)
img_without_black = img[~np.all(img == np.zeros((1, width, 3), np.uint8), axis=(1, 2))]
img = image.array_to_img(img_without_black)
return img
Isso é praticamente o que eu queria falar sobre usar CNN para extrair características de imagens e implementar outros processamentos de imagem. Agora vamos dar uma olhada nos mecanismos de busca vetorial.
Mecanismo de busca vetorial
O problema de extrair vetores de características de imagens foi resolvido. Então os problemas restantes são:
- Como armazenar vetores de características?
- Como calcular a similaridade dos vetores de características, ou seja, como pesquisar? O mecanismo de busca vetorial de código aberto Milvus pode resolver esses dois problemas. Até agora, ele tem funcionado bem em nosso ambiente de produção.
Logo do Milvus.
Milvus, o mecanismo de busca vetorial
Extrair vetores de características de uma imagem está longe de ser suficiente. Também precisamos gerenciar dinamicamente esses vetores de características (adição, exclusão e atualização), calcular a similaridade dos vetores e retornar os dados vetoriais no intervalo de vizinhos mais próximos. O mecanismo de busca vetorial de código aberto Milvus executa essas tarefas muito bem.
O restante deste artigo descreverá práticas específicas e pontos a serem observados.
1. Requisitos para CPU
Para usar o Milvus, sua CPU deve oferecer suporte ao conjunto de instruções avx2. Para sistemas Linux, use o seguinte comando para verificar quais conjuntos de instruções sua CPU oferece suporte:
cat /proc/cpuinfo | grep flags</code?
Então você obtém algo como:
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm abm cpuid_fault epb invpcid_single pti intel_ppin tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm xsaveopt cqm_llc cqm_occup_llc dtherm ida arat pln pts
O que vem depois de flags são os conjuntos de instruções que sua CPU oferece suporte. Claro, estes são muito mais do que eu preciso. Eu só quero ver se um conjunto de instruções específico, como avx2, é suportado. Basta adicionar um grep para filtrá-lo:
cat /proc/cpuinfo | grep flags | grep avx2
Se nenhum resultado for retornado, isso significa que esse conjunto de instruções específico não é suportado. Então você precisa trocar sua máquina.
2. Planejamento de capacidade
O planejamento de capacidade é nossa primeira consideração quando projetamos um sistema. Quantos dados precisamos armazenar? Quanta memória e espaço em disco os dados exigem?
Vamos fazer algumas contas rápidas. Cada dimensão de um vetor é float32. Um tipo float32 ocupa 4 Bytes. Então um vetor de 512 dimensões requer 2 KB de armazenamento. Da mesma forma:
- Mil vetores de 512 dimensões exigem 2 MB de armazenamento.
- Um milhão de vetores de 512 dimensões exigem 2 GB de armazenamento.
- 10 milhões de vetores de 512 dimensões exigem 20 GB de armazenamento.
- 100 milhões de vetores de 512 dimensões exigem 200 GB de armazenamento.
- Um bilhão de vetores de 512 dimensões exigem 2 TB de armazenamento.
Se quisermos armazenar todos os dados na memória, então o sistema precisa ter pelo menos a capacidade de memória correspondente.
Recomenda-se que você use a ferramenta oficial de cálculo de tamanho: Milvus sizing tool.
Na verdade, nossa memória pode não ser tão grande. (Não importa muito se você não tiver memória suficiente. O Milvus descarrega automaticamente os dados no disco.) Além dos dados vetoriais originais, também precisamos considerar o armazenamento de outros dados, como logs.
3. Configuração do sistema
Para obter mais informações sobre a configuração do sistema, consulte a documentação do Milvus:
- Configuração do servidor Milvus: https://milvus.io/docs/v0.10.1/milvus_config.md
4. Design do banco de dados
Collection & Partition
- Collection também é conhecida como tabela.
- Partition refere-se às partições dentro de uma collection.
A implementação subjacente de partition é, na verdade, a mesma que a de collection, exceto que uma partition fica dentro de uma collection. Mas, com partitions, a organização dos dados se torna mais flexível. Também podemos consultar uma partition específica em uma collection para obter melhores resultados de consulta.
Quantas collections e partitions podemos ter? As informações básicas sobre collection e partition estão em Metadata. O Milvus usa SQLite (integração interna do Milvus) ou MySQL (requer conexão externa) para gerenciamento interno de metadados. Se você usar SQLite por padrão para gerenciar Metadata, sofrerá uma grande perda de desempenho quando o número de collections e partitions for muito grande. Portanto, o número total de collections e partitions não deve exceder 50.000 (o Milvus 0.8.0 limitará esse número a 4.096). Se você precisar definir um número maior, recomenda-se usar MySQL por meio de uma conexão externa.
A estrutura de dados suportada por collection e partition do Milvus é muito simples, ou seja, ID + vector. Em outras palavras, há apenas duas colunas na tabela: ID e dados vetoriais.
Observação:
- ID deve ser composto por inteiros.
- Precisamos garantir que o ID seja único dentro de uma collection, em vez de dentro de uma partition.
Filtragem condicional
Quando usamos bancos de dados tradicionais, podemos especificar valores de campos como condições de filtragem. Embora o Milvus não filtre exatamente da mesma forma, podemos implementar uma filtragem condicional simples usando collections e partitions. Por exemplo, temos uma grande quantidade de dados de imagens e os dados pertencem a usuários específicos. Então podemos dividir os dados em partitions por usuário. Portanto, usar o usuário como condição de filtro é, na verdade, especificar a partition.
Dados estruturados e mapeamento vetorial
O Milvus oferece suporte apenas à estrutura de dados ID + vector. Mas, em cenários de negócios, o que precisamos são dados estruturados com significado de negócio. Em outras palavras, precisamos encontrar dados estruturados por meio de vetores. Assim, precisamos manter as relações de mapeamento entre dados estruturados e vetores por meio do ID.
ID de dados estruturados <--> tabela de mapeamento <--> ID do Milvus
Selecionando índice
Você pode consultar os seguintes artigos:
- Tipos de índice: https://www.milvus.io/docs/v0.10.1/index.md
- Como selecionar índice: https://medium.com/@milvusio/how-to-choose-an-index-in-milvus-4f3d15259212
5. Processando resultados de busca
Os resultados de busca do Milvus são uma coleção de ID + distância:
- ID: o ID em uma collection.
- Distância: um valor de distância de 0 ~ 1 indica o nível de similaridade; quanto menor o valor, mais semelhantes são os dois vetores.
Filtrando dados cujo ID é -1
Quando o número de collections é muito pequeno, os resultados da busca podem conter dados cujo ID é -1. Precisamos filtrá-los por conta própria.
Paginação
A busca por vetores é bastante diferente. Os resultados da consulta são classificados em ordem decrescente de similaridade, e os resultados mais similares (topK) são selecionados (topK é especificado pelo usuário no momento da consulta).
O Milvus não oferece suporte à paginação. Precisamos implementar a função de paginação por conta própria se precisarmos dela para o negócio. Por exemplo, se tivermos dez resultados em cada página e quisermos exibir apenas a terceira página, precisamos especificar que topK = 30 e retornar apenas os últimos dez resultados.
Limiar de similaridade para o negócio
A distância entre os vetores de duas imagens está entre 0 e 1. Se quisermos decidir se duas imagens são similares em um cenário de negócio específico, precisamos especificar um limiar dentro desse intervalo. As duas imagens são similares se a distância for menor que o limiar, ou são bastante diferentes uma da outra se a distância for maior que o limiar. Você precisa ajustar o limiar para atender às suas próprias necessidades de negócio.
Este artigo foi escrito por rifewang, usuário do Milvus e engenheiro de software da UPYUN. Se você gostou deste artigo, sinta-se à vontade para vir dizer oi em https://github.com/rifewang.
Continue lendo

What Is a Vector Lakebase?
A Vector Lakebase is a unified, lake-native data architecture for AI that combines vector-database-grade serving with open lake storage, reusable lake-level indexes, and a shared semantic layer.

What Exactly Are AI Agents? Why OpenAI and LangChain Are Fighting Over Their Definition?
AI agents are software programs powered by AI that can perceive their environment, make decisions, and take actions to achieve a goal—often autonomously.

DeepSeek Always Busy? Deploy It Locally with Milvus in Just 10 Minutes—No More Waiting!
Learn how to set up DeepSeek-R1 on your local machine using Ollama, AnythingLLM, and Milvus in just 10 minutes. Bypass busy servers and enhance AI responses with custom data.



