Progettazione di schemi Hands-On
I sistemi di Information Retrieval (IR), noti anche come ricerca, sono essenziali per varie applicazioni di intelligenza artificiale, come la Retrieval-augmented generation (RAG), la ricerca di immagini e la raccomandazione di prodotti. Il primo passo nello sviluppo di un sistema IR è la progettazione del modello di dati, che comporta l'analisi dei requisiti aziendali, la determinazione di come organizzare le informazioni e l'indicizzazione dei dati per renderli semanticamente ricercabili.
Milvus supporta la definizione del modello di dati attraverso uno schema di raccolta. Una raccolta organizza i dati non strutturati come testo e immagini, insieme alle loro rappresentazioni vettoriali, compresi i vettori densi e radi in varie precisioni utilizzate per la ricerca semantica. Inoltre, Milvus supporta la memorizzazione e il filtraggio di tipi di dati non vettoriali chiamati "Scalar". I tipi di scalare includono BOOL, INT8/16/32/64, FLOAT/DOUBLE, VARCHAR, JSON e Array.
Esempio di schema di dati progettato per la ricerca di articoli di giornale
La progettazione del modello di dati di un sistema di ricerca comporta l'analisi delle esigenze aziendali e l'astrazione delle informazioni in un modello di dati espresso in forma di schema. Ad esempio, per cercare un testo, è necessario "indicizzarlo" convertendo la stringa letterale in un vettore attraverso l'"embedding", che consente la ricerca vettoriale. Oltre a questo requisito di base, può essere necessario memorizzare altre proprietà, come la data di pubblicazione e l'autore. Questi metadati consentono di affinare le ricerche semantiche attraverso il filtraggio, restituendo solo i testi pubblicati dopo una data specifica o da un particolare autore. Può anche essere necessario recuperarli insieme al testo principale, per rendere il risultato della ricerca nell'applicazione. Per organizzare questi pezzi di testo, a ciascuno di essi deve essere assegnato un identificatore univoco, espresso come un numero intero o una stringa. Questi elementi sono essenziali per ottenere una logica di ricerca sofisticata.
Uno schema ben progettato è importante perché astrae il modello dei dati e decide se gli obiettivi aziendali possono essere raggiunti attraverso la ricerca. Inoltre, dal momento che ogni riga di dati inserita nella raccolta deve seguire lo schema, esso aiuta notevolmente a mantenere la coerenza dei dati e la qualità a lungo termine. Da un punto di vista tecnico, uno schema ben definito porta a una memorizzazione ben organizzata dei dati delle colonne e a una struttura dell'indice più pulita, che può aumentare le prestazioni di ricerca.
Un esempio: Ricerca di notizie
Supponiamo di voler creare una ricerca per un sito web di notizie e di avere un corpus di notizie con testo, immagini in miniatura e altri metadati. Per prima cosa, dobbiamo analizzare come vogliamo utilizzare i dati per supportare i requisiti aziendali della ricerca. Immaginiamo che il requisito sia recuperare le notizie in base all'immagine in miniatura e al sommario del contenuto, e che i metadati come le informazioni sull'autore e l'ora di pubblicazione siano criteri per filtrare i risultati della ricerca. Questi requisiti possono essere ulteriormente suddivisi in.
Per cercare le immagini tramite il testo, possiamo incorporare le immagini nei vettori tramite un modello di incorporazione multimodale che può mappare i dati di testo e immagine nello stesso spazio latente.
Il testo riassuntivo di un articolo viene incorporato nei vettori tramite un modello di incorporazione del testo.
Per filtrare in base all'ora di pubblicazione, le date sono memorizzate come campo scalare e per il campo scalare è necessario un indice per un filtraggio efficiente. Altre strutture di dati più complesse, come JSON, possono essere memorizzate in uno scalare e la ricerca filtrata può essere eseguita sul loro contenuto (l'indicizzazione di JSON è una funzione in arrivo).
Per recuperare i byte delle miniature delle immagini e renderle nella pagina dei risultati della ricerca, viene memorizzato anche l'url dell'immagine. Allo stesso modo, per il testo di riepilogo e il titolo. (In alternativa, si possono memorizzare i dati del testo grezzo e del file immagine come campi scalari, se necessario).
Per migliorare i risultati della ricerca sul testo riassuntivo, progettiamo un approccio di ricerca ibrido. Per un percorso di recupero, utilizziamo un modello di incorporazione regolare per generare un vettore denso dal testo, come
text-embedding-3-large
di OpenAI o il modello open-sourcebge-large-en-v1.5
. Questi modelli sono in grado di rappresentare la semantica complessiva del testo. L'altra strada è quella di utilizzare modelli di incorporazione sparsi, come BM25 o SPLADE, per generare un vettore rado, simile alla ricerca full-text, che è in grado di cogliere i dettagli e i singoli concetti del testo. Milvus supporta l'uso di entrambi nella stessa raccolta di dati grazie alla sua funzione multivettore. La ricerca su più vettori può essere effettuata con un'unica operazionehybrid_search()
.Infine, abbiamo bisogno di un campo ID per identificare ogni singola pagina di notizie, formalmente chiamata "entità" nella terminologia di Milvus. Questo campo è usato come chiave primaria (o "pk" in breve).
Nome del campo | article_id (chiave primaria) | titolo | autore_info | pubblicare_t | url_immagine | vettore_immagine | sommario | vettore_riassunto_denso | vettore_riassuntivo_sparso |
---|---|---|---|---|---|---|---|---|---|
Tipo | INT64 | VARCHAR | JSON | INT32 | VARCHAR | VETTORE_FIORITO | VARCHAR | VETTORE_FIORITO | SPARSE_FLOAT_VECTOR |
Necessità di indice | N | N | N (supporto in arrivo) | Y | N | Y | N | Y | Y |
Come implementare lo schema di esempio
Creare lo schema
Per prima cosa, creiamo un'istanza client di Milvus, che può essere usata per connettersi al server Milvus e gestire collezioni e dati.
Per impostare uno schema, si usa create_schema()
per creare un oggetto schema e add_field()
per aggiungere campi allo schema.
from pymilvus import MilvusClient, DataType
collection_name = "my_collection"
# client = MilvusClient(uri="http://localhost:19530")
client = MilvusClient(uri="./milvus_demo.db")
schema = MilvusClient.create_schema(
auto_id=False,
)
schema.add_field(field_name="article_id", datatype=DataType.INT64, is_primary=True, description="article id")
schema.add_field(field_name="title", datatype=DataType.VARCHAR, max_length=200, description="article title")
schema.add_field(field_name="author_info", datatype=DataType.JSON, description="author information")
schema.add_field(field_name="publish_ts", datatype=DataType.INT32, description="publish timestamp")
schema.add_field(field_name="image_url", datatype=DataType.VARCHAR, max_length=500, description="image URL")
schema.add_field(field_name="image_vector", datatype=DataType.FLOAT_VECTOR, dim=768, description="image vector")
schema.add_field(field_name="summary", datatype=DataType.VARCHAR, max_length=1000, description="article summary")
schema.add_field(field_name="summary_dense_vector", datatype=DataType.FLOAT_VECTOR, dim=768, description="summary dense vector")
schema.add_field(field_name="summary_sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR, description="summary sparse vector")
Si può notare l'argomento uri
in MilvusClient
, che viene usato per connettersi al server Milvus. È possibile impostare gli argomenti come segue.
Se si ha bisogno di un database vettoriale locale solo per dati su piccola scala o per la creazione di prototipi, l'impostazione dell'uri come file locale, ad esempio
./milvus.db
, è il metodo più conveniente, poiché utilizza automaticamente Milvus Lite per memorizzare tutti i dati in questo file.Se si dispone di una grande quantità di dati, ad esempio più di un milione di vettori, è possibile configurare un server Milvus più performante su Docker o Kubernetes. In questa configurazione, utilizzare l'indirizzo e la porta del server come uri, ad esempio
http://localhost:19530
. Se si attiva la funzione di autenticazione su Milvus, utilizzare "<nome_utente>:<password>" come token, altrimenti non impostare il token.Se si utilizza Zilliz Cloud, il servizio cloud completamente gestito per Milvus, regolare
uri
etoken
, che corrispondono all'endpoint pubblico e alla chiave API di Zilliz Cloud.
Come per auto_id
in MilvusClient.create_schema
, AutoID è un attributo del campo primario che determina se abilitare l'incremento automatico per il campo primario. Poiché abbiamo impostato il campoarticle_id
come chiave primaria e vogliamo aggiungere manualmente l'id dell'articolo, impostiamo auto_id
False per disabilitare questa funzione.
Dopo aver aggiunto tutti i campi all'oggetto schema, il nostro oggetto schema corrisponde alle voci della tabella precedente.
Definire l'indice
Dopo aver definito lo schema con i vari campi, compresi i metadati e i campi vettoriali per le immagini e i dati di riepilogo, il passo successivo consiste nel preparare i parametri dell'indice. L'indicizzazione è fondamentale per ottimizzare la ricerca e il recupero dei vettori, garantendo prestazioni efficienti delle query. Nella sezione seguente verranno definiti i parametri dell'indice per i campi vettoriali e scalari specificati nella collezione.
index_params = client.prepare_index_params()
index_params.add_index(
field_name="image_vector",
index_type="AUTOINDEX",
metric_type="IP",
)
index_params.add_index(
field_name="summary_dense_vector",
index_type="AUTOINDEX",
metric_type="IP",
)
index_params.add_index(
field_name="summary_sparse_vector",
index_type="SPARSE_INVERTED_INDEX",
metric_type="IP",
)
index_params.add_index(
field_name="publish_ts",
index_type="INVERTED",
)
Una volta impostati e applicati i parametri dell'indice, Milvus è ottimizzato per gestire query complesse su dati vettoriali e scalari. Questa indicizzazione migliora le prestazioni e l'accuratezza delle ricerche di similarità all'interno della collezione, consentendo di recuperare in modo efficiente gli articoli basati su vettori di immagini e vettori di sintesi. Sfruttando l'opzione AUTOINDEX
per i vettori densi, il SPARSE_INVERTED_INDEX
per i vettori sparsi e l'opzione INVERTED_INDEX
per gli scalari, Milvus è in grado di identificare e restituire rapidamente i risultati più rilevanti, migliorando significativamente l'esperienza complessiva dell'utente e l'efficacia del processo di recupero dei dati.
Esistono molti tipi di indici e metriche. Per ulteriori informazioni su di essi, si può fare riferimento a Milvus index type e Milvus metric type.
Creare la raccolta
Una volta definiti lo schema e gli indici, si crea una "collezione" con questi parametri. La collezione per Milvus è come una tabella in un DB relazionale.
client.create_collection(
collection_name=collection_name,
schema=schema,
index_params=index_params,
)
Possiamo verificare che la collezione sia stata creata con successo descrivendo la collezione.
collection_desc = client.describe_collection(
collection_name=collection_name
)
print(collection_desc)
Altre considerazioni
Caricamento dell'indice
Quando si crea una collezione in Milvus, si può scegliere di caricare l'indice immediatamente o di rimandarlo a dopo l'ingestione di alcuni dati. In genere non è necessario fare una scelta esplicita, poiché gli esempi precedenti mostrano che l'indice viene creato automaticamente per tutti i dati ingeriti subito dopo la creazione della raccolta. Ciò consente di ricercare immediatamente i dati ingeriti. Tuttavia, se si ha un grande inserimento di massa dopo la creazione della raccolta e non si ha bisogno di cercare alcun dato fino a un certo punto, si può rimandare la costruzione dell'indice omettendo index_params nella creazione della raccolta e costruire l'indice chiamando esplicitamente load dopo aver ingerito tutti i dati. Questo metodo è più efficiente per costruire l'indice su un insieme di grandi dimensioni, ma non è possibile effettuare ricerche fino a quando non si chiama load().
Come definire il modello dei dati per la multi-tendenza
Il concetto di più tenancy è comunemente usato in scenari in cui una singola applicazione o servizio software deve servire più utenti o organizzazioni indipendenti, ciascuno con il proprio ambiente isolato. Ciò si verifica spesso nel cloud computing, nelle applicazioni SaaS (Software as a Service) e nei sistemi di database. Ad esempio, un servizio di cloud storage può utilizzare la multi-tenancy per consentire a diverse aziende di archiviare e gestire i propri dati separatamente, pur condividendo la stessa infrastruttura sottostante. Questo approccio massimizza l'utilizzo delle risorse e l'efficienza, garantendo al tempo stesso la sicurezza e la privacy dei dati per ciascun tenant.
Il modo più semplice per differenziare i tenant è isolare i loro dati e le loro risorse gli uni dagli altri. Ogni tenant ha accesso esclusivo a risorse specifiche o condivide risorse con altri per gestire entità Milvus come database, collezioni e partizioni. Esistono metodi specifici allineati a queste entità per implementare la multi-tenancy di Milvus. Per ulteriori informazioni, consultare la pagina Milvus multi-tenancy.