Il percorso verso l'ottimizzazione della ricerca di immagini su scala miliardaria (2/2)
Questo articolo è la seconda parte di The Journey to Optimizing Billion-scale Image Search by UPYUN. Se ti sei perso la prima, clicca qui.
Il sistema search-by-image di seconda generazione
Il sistema search-by-image di seconda generazione sceglie tecnicamente la soluzione CNN + Milvus. Il sistema si basa su vettori di caratteristiche e fornisce un supporto tecnico migliore.
Estrazione delle caratteristiche
Nel campo della computer vision, l'uso dell'intelligenza artificiale è diventato lo standard. Analogamente, l'estrazione delle caratteristiche del sistema search-by-image di seconda generazione utilizza la rete neurale convoluzionale (CNN) come tecnologia sottostante
Il termine CNN è difficile da comprendere. Qui ci concentriamo sul rispondere a due domande:
- Che cosa può fare la CNN?
- Perché posso usare la CNN per una ricerca di immagini?
Foto di memegenerator.net
Ci sono molte competizioni nel campo dell'IA e la classificazione delle immagini è una delle più importanti. Il compito della classificazione delle immagini è determinare se il contenuto dell'immagine riguarda un gatto, un cane, una mela, una pera o altri tipi di oggetti.
Che cosa può fare la CNN? Può estrarre caratteristiche e riconoscere oggetti. Estrae caratteristiche da più dimensioni e misura quanto le caratteristiche di un'immagine siano vicine alle caratteristiche di gatti o cani. Possiamo scegliere quelle più vicine come risultato della nostra identificazione, che indica se il contenuto di una specifica immagine riguarda un gatto, un cane o qualcos'altro.
Qual è il collegamento tra la funzione di identificazione degli oggetti della CNN e la ricerca per immagine? Ciò che vogliamo non è il risultato finale dell'identificazione, ma il vettore di caratteristiche estratto da più dimensioni. I vettori di caratteristiche di due immagini con contenuto simile devono essere vicini.
Quale modello CNN dovrei usare?
La risposta è VGG16. Perché sceglierlo? Innanzitutto, VGG16 ha una buona capacità di generalizzazione, cioè è molto versatile. In secondo luogo, i vettori di caratteristiche estratti da VGG16 hanno 512 dimensioni. Se le dimensioni sono troppo poche, l'accuratezza può essere influenzata. Se le dimensioni sono troppe, il costo di archiviazione e calcolo di questi vettori di caratteristiche è relativamente elevato.
Usare la CNN per estrarre le caratteristiche delle immagini è una soluzione mainstream. Possiamo usare VGG16 come modello e Keras + TensorFlow per l'implementazione tecnica. Ecco l'esempio ufficiale di 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)
Le features estratte qui sono vettori di caratteristiche.
1. Normalizzazione
Per facilitare le operazioni successive, spesso normalizziamo le caratteristiche:
Ciò che viene usato successivamente è anche il norm_feat normalizzato.
2. Descrizione dell'immagine
L'immagine viene caricata usando il metodo image.load_img di keras.preprocessing:
from keras.preprocessing import image
img_path = 'elephant.jpg'
img = image.load_img(img_path, target_size=(224, 224))
In realtà, è il metodo TensorFlow chiamato da Keras. Per i dettagli, consulta la documentazione di TensorFlow. L'oggetto immagine finale è in realtà un'istanza PIL Image (il PIL usato da TensorFlow).
3. Conversione dei byte
In termini pratici, il contenuto delle immagini viene spesso trasmesso attraverso la rete. Pertanto, invece di caricare le immagini da un percorso, preferiamo convertire direttamente i dati in byte in oggetti immagine, cioè 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)
L'immagine sopra è la stessa del risultato ottenuto con il metodo image.load_img. Ci sono due cose a cui prestare attenzione:
- Devi eseguire la conversione RGB.
- Devi ridimensionare (resize è il secondo parametro del
load_img method).
4. Elaborazione dei bordi neri
Le immagini, come gli screenshot, possono occasionalmente avere parecchi bordi neri. Questi bordi neri non hanno alcun valore pratico e causano molte interferenze. Per questo motivo, rimuovere i bordi neri è anche una pratica comune.
Un bordo nero è essenzialmente una riga o una colonna di pixel in cui tutti i pixel sono (0, 0, 0) (immagine RGB). Rimuovere il bordo nero significa trovare queste righe o colonne ed eliminarle. In NumPy si tratta in realtà di una moltiplicazione di matrici 3D.
Un esempio di rimozione dei bordi neri orizzontali:
# -*- 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
Questo è più o meno ciò di cui volevo parlare riguardo all'uso delle CNN per estrarre caratteristiche dalle immagini e implementare altre elaborazioni delle immagini. Ora diamo un'occhiata ai motori di ricerca vettoriale.
Motore di ricerca vettoriale
Il problema dell'estrazione dei vettori di caratteristiche dalle immagini è stato risolto. Quindi i problemi rimanenti sono:
- Come archiviare i vettori di caratteristiche?
- Come calcolare la similarità dei vettori di caratteristiche, cioè come effettuare la ricerca? Il motore di ricerca vettoriale open-source Milvus può risolvere questi due problemi. Finora, ha funzionato bene nel nostro ambiente di produzione.
Logo Milvus.
Milvus, il motore di ricerca vettoriale
Estrarre vettori di caratteristiche da un'immagine è tutt'altro che sufficiente. Dobbiamo anche gestire dinamicamente questi vettori di caratteristiche (aggiunta, eliminazione e aggiornamento), calcolare la similarità dei vettori e restituire i dati vettoriali nell'intervallo dei vicini più prossimi. Il motore di ricerca vettoriale open-source Milvus svolge questi compiti piuttosto bene.
Il resto di questo articolo descriverà pratiche specifiche e punti da tenere presenti.
1. Requisiti per la CPU
Per utilizzare Milvus, la tua CPU deve supportare il set di istruzioni avx2. Per i sistemi Linux, usa il seguente comando per verificare quali set di istruzioni supporta la tua CPU:
cat /proc/cpuinfo | grep flags</code?
Quindi ottieni qualcosa del genere:
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
Ciò che segue flags sono i set di istruzioni supportati dalla tua CPU. Naturalmente, questi sono molti più di quelli di cui ho bisogno. Voglio solo vedere se uno specifico set di istruzioni, come avx2, è supportato. Basta aggiungere un grep per filtrarlo:
cat /proc/cpuinfo | grep flags | grep avx2
Se non viene restituito alcun risultato, significa che questo specifico set di istruzioni non è supportato. In tal caso devi cambiare macchina.
2. Pianificazione della capacità
La pianificazione della capacità è la nostra prima considerazione quando progettiamo un sistema. Quanti dati dobbiamo archiviare? Quanta memoria e quanto spazio su disco richiedono i dati?
Facciamo qualche calcolo rapido. Ogni dimensione di un vettore è float32. Un tipo float32 occupa 4 Byte. Quindi un vettore di 512 dimensioni richiede 2 KB di archiviazione. Per lo stesso motivo:
- Mille vettori a 512 dimensioni richiedono 2 MB di archiviazione.
- Un milione di vettori a 512 dimensioni richiedono 2 GB di archiviazione.
- 10 milioni di vettori a 512 dimensioni richiedono 20 GB di archiviazione.
- 100 milioni di vettori a 512 dimensioni richiedono 200 GB di archiviazione.
- Un miliardo di vettori a 512 dimensioni richiedono 2 TB di archiviazione.
Se vogliamo archiviare tutti i dati in memoria, il sistema necessita almeno della capacità di memoria corrispondente.
Si consiglia di utilizzare lo strumento ufficiale per il calcolo delle dimensioni: Milvus sizing tool.
In realtà la nostra memoria potrebbe non essere così grande. (Non importa molto se non hai abbastanza memoria. Milvus scarica automaticamente i dati su disco.) Oltre ai dati vettoriali originali, dobbiamo anche considerare l'archiviazione di altri dati, come i log.
3. Configurazione di sistema
Per ulteriori informazioni sulla configurazione di sistema, consulta la documentazione di Milvus:
- Configurazione del server Milvus: https://milvus.io/docs/v0.10.1/milvus_config.md
4. Progettazione del database
Collection & Partition
- Collection è anche nota come tabella.
- Partition si riferisce alle partizioni all'interno di una collection.
L'implementazione sottostante di partition è in realtà la stessa di quella di collection, tranne per il fatto che una partition si trova sotto una collection. Ma con le partition, l'organizzazione dei dati diventa più flessibile. Possiamo anche interrogare una partition specifica in una collection per ottenere risultati di query migliori.
Quante collection e partition possiamo avere? Le informazioni di base su collection e partition si trovano nei Metadata. Milvus utilizza SQLite (integrazione interna di Milvus) o MySQL (richiede connessione esterna) per la gestione interna dei metadati. Se utilizzi SQLite per impostazione predefinita per gestire i Metadata, subirai una grave perdita di prestazioni quando il numero di collection e partition è troppo elevato. Pertanto, il numero totale di collection e partition non dovrebbe superare 50.000 (Milvus 0.8.0 limiterà questo numero a 4.096). Se devi impostare un numero maggiore, si consiglia di utilizzare MySQL tramite una connessione esterna.
La struttura dati supportata da collection e partition di Milvus è molto semplice, ovvero ID + vector. In altre parole, nella tabella ci sono solo due colonne: ID e dati vettoriali.
Nota:
- ID should be integers.
- Dobbiamo assicurarci che l'ID sia univoco all'interno di una collection invece che all'interno di una partition.
Filtraggio condizionale
Quando utilizziamo database tradizionali, possiamo specificare i valori dei campi come condizioni di filtro. Sebbene Milvus non filtri esattamente nello stesso modo, possiamo implementare un semplice filtraggio condizionale utilizzando collection e partition. Ad esempio, abbiamo una grande quantità di dati di immagini e i dati appartengono a utenti specifici. Quindi possiamo suddividere i dati in partition per utente. Pertanto, utilizzare l'utente come condizione di filtro significa in realtà specificare la partition.
Dati strutturati e mapping vettoriale
Milvus supporta solo la struttura dati ID + vector. Ma negli scenari aziendali, ciò di cui abbiamo bisogno sono dati strutturati con significato di business. In altre parole, dobbiamo trovare dati strutturati tramite vettori. Di conseguenza, dobbiamo mantenere le relazioni di mapping tra dati strutturati e vettori tramite ID.
ID dei dati strutturati <--> tabella di mapping <--> ID Milvus
Selezione dell'indice
Puoi consultare i seguenti articoli:
- Tipi di indice: https://www.milvus.io/docs/v0.10.1/index.md
- Come selezionare l'indice: https://medium.com/@milvusio/how-to-choose-an-index-in-milvus-4f3d15259212
5. Elaborazione dei risultati della ricerca
I risultati della ricerca di Milvus sono una raccolta di ID + distance:
- ID: l'ID in una collection.
- Distance: un valore di distanza da 0 a 1 indica il livello di somiglianza; più piccolo è il valore, più simili sono i due vettori.
Filtraggio dei dati il cui ID è -1
Quando il numero di collection è troppo piccolo, i risultati della ricerca possono contenere dati il cui ID è -1. Dobbiamo filtrarli autonomamente.
Paginazione
La ricerca dei vettori è piuttosto diversa. I risultati della query sono ordinati in ordine decrescente di similarità e vengono selezionati i risultati più simili (topK) (topK è specificato dall'utente al momento della query).
Milvus non supporta la paginazione. Dobbiamo implementare noi stessi la funzione di paginazione se ne abbiamo bisogno per il business. Ad esempio, se abbiamo dieci risultati su ogni pagina e vogliamo visualizzare solo la terza pagina, dobbiamo specificare che topK = 30 e restituire solo gli ultimi dieci risultati.
Soglia di similarità per il business
La distanza tra i vettori di due immagini è compresa tra 0 e 1. Se vogliamo decidere se due immagini sono simili in uno scenario di business specifico, dobbiamo specificare una soglia all'interno di questo intervallo. Le due immagini sono simili se la distanza è inferiore alla soglia, oppure sono piuttosto diverse l'una dall'altra se la distanza è superiore alla soglia. È necessario regolare la soglia per soddisfare le proprie esigenze di business.
Questo articolo è scritto da rifewang, utente Milvus e software engineer di UPYUN. Se ti piace questo articolo, vieni pure a salutarci @ https://github.com/rifewang.
Continua a leggere

DeepSeek-OCR Explained: Optical Compression for Scalable Long-Context and RAG Systems
Discover how DeepSeek-OCR uses visual tokens and Contexts Optical Compression to boost long-context LLM efficiency and reshape RAG performance.

Why AI Databases Don't Need SQL
Whether you like it or not, here's the truth: SQL is destined for decline in the era of AI.

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.



