Avvio rapido con Milvus Lite
I vettori, il formato dei dati di output dei modelli di reti neurali, possono codificare efficacemente le informazioni e svolgere un ruolo fondamentale nelle applicazioni di IA come le basi di conoscenza, la ricerca semantica, la Retrieval Augmented Generation (RAG) e altro ancora.
Milvus è un database vettoriale open-source che si adatta ad applicazioni di IA di ogni dimensione, dall'esecuzione di un chatbot dimostrativo in un notebook Jupyter alla costruzione di ricerche su scala web che servono miliardi di utenti. In questa guida vi spiegheremo come configurare Milvus localmente in pochi minuti e come utilizzare la libreria client Python per generare, memorizzare e cercare vettori.
Installare Milvus
In questa guida utilizzeremo Milvus Lite, una libreria python inclusa in pymilvus
che può essere incorporata nell'applicazione client. Milvus supporta anche la distribuzione su Docker e Kubernetes per i casi di produzione.
Prima di iniziare, assicurarsi di avere Python 3.8+ disponibile nell'ambiente locale. Installare pymilvus
che contiene sia la libreria client python che Milvus Lite:
$ pip install -U pymilvus
Se si utilizza Google Colab, per abilitare le dipendenze appena installate, potrebbe essere necessario riavviare il runtime. (Fare clic sul menu "Runtime" nella parte superiore dello schermo e selezionare "Riavvia sessione" dal menu a discesa).
Impostazione del database vettoriale
Per creare un database vettoriale locale di Milvus, è sufficiente istanziare un MilvusClient
specificando un nome di file in cui memorizzare tutti i dati, ad esempio "milvus_demo.db".
from pymilvus import MilvusClient
client = MilvusClient("milvus_demo.db")
Creare una collezione
In Milvus abbiamo bisogno di una collezione per memorizzare i vettori e i loro metadati associati. Si può pensare a questa collezione come a una tabella nei database SQL tradizionali. Quando si crea una collezione, è possibile definire i parametri dello schema e dell'indice per configurare le specifiche dei vettori, come la dimensionalità, i tipi di indice e le metriche distanti. Esistono anche concetti complessi per ottimizzare l'indice per le prestazioni della ricerca vettoriale. Per ora, concentriamoci sulle basi e usiamo i valori predefiniti per tutto ciò che è possibile. Come minimo, è sufficiente impostare il nome della collezione e la dimensione del campo vettoriale della collezione.
if client.has_collection(collection_name="demo_collection"):
client.drop_collection(collection_name="demo_collection")
client.create_collection(
collection_name="demo_collection",
dimension=768, # The vectors we will use in this demo has 768 dimensions
)
Nella configurazione precedente,
- La chiave primaria e i campi vettoriali utilizzano i loro nomi predefiniti ("id" e "vector").
- Il tipo di metrica (definizione della distanza vettoriale) è impostato sul valore predefinito(COSINE).
- Il campo della chiave primaria accetta numeri interi e non si incrementa automaticamente (cioè non utilizza la funzione auto-id).
Preparare i dati
In questa guida, utilizziamo i vettori per eseguire una ricerca semantica sul testo. È necessario generare vettori per il testo scaricando i modelli di incorporamento. Questo può essere fatto facilmente utilizzando le funzioni di utilità della libreria pymilvus[model]
.
Rappresentare il testo con i vettori
Per prima cosa, installare la libreria dei modelli. Questo pacchetto include strumenti di ML essenziali come PyTorch. Il download del pacchetto può richiedere un po' di tempo se il vostro ambiente locale non ha mai installato PyTorch.
$ pip install "pymilvus[model]"
Generare le incorporazioni vettoriali con il modello predefinito. Milvus si aspetta che i dati siano inseriti organizzati come un elenco di dizionari, dove ogni dizionario rappresenta un record di dati, definito entità.
from pymilvus import model
# If connection to https://huggingface.co/ failed, uncomment the following path
# import os
# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
# This will download a small embedding model "paraphrase-albert-small-v2" (~50MB).
embedding_fn = model.DefaultEmbeddingFunction()
# Text strings to search from.
docs = [
"Artificial intelligence was founded as an academic discipline in 1956.",
"Alan Turing was the first person to conduct substantial research in AI.",
"Born in Maida Vale, London, Turing was raised in southern England.",
]
vectors = embedding_fn.encode_documents(docs)
# The output vector has 768 dimensions, matching the collection that we just created.
print("Dim:", embedding_fn.dim, vectors[0].shape) # Dim: 768 (768,)
# Each entity has id, vector representation, raw text, and a subject label that we use
# to demo metadata filtering later.
data = [
{"id": i, "vector": vectors[i], "text": docs[i], "subject": "history"}
for i in range(len(vectors))
]
print("Data has", len(data), "entities, each with fields: ", data[0].keys())
print("Vector dim:", len(data[0]["vector"]))
Dim: 768 (768,)
Data has 3 entities, each with fields: dict_keys(['id', 'vector', 'text', 'subject'])
Vector dim: 768
[Utilizza una rappresentazione fittizia con vettori casuali.
Se non è stato possibile scaricare il modello a causa di problemi di rete, come soluzione alternativa è possibile utilizzare vettori casuali per rappresentare il testo e completare l'esempio. Si noti solo che il risultato della ricerca non rifletterà la somiglianza semantica, poiché i vettori sono falsi.
import random
# Text strings to search from.
docs = [
"Artificial intelligence was founded as an academic discipline in 1956.",
"Alan Turing was the first person to conduct substantial research in AI.",
"Born in Maida Vale, London, Turing was raised in southern England.",
]
# Use fake representation with random vectors (768 dimension).
vectors = [[random.uniform(-1, 1) for _ in range(768)] for _ in docs]
data = [
{"id": i, "vector": vectors[i], "text": docs[i], "subject": "history"}
for i in range(len(vectors))
]
print("Data has", len(data), "entities, each with fields: ", data[0].keys())
print("Vector dim:", len(data[0]["vector"]))
Data has 3 entities, each with fields: dict_keys(['id', 'vector', 'text', 'subject'])
Vector dim: 768
Inserire i dati
Inseriamo i dati nella raccolta:
res = client.insert(collection_name="demo_collection", data=data)
print(res)
{'insert_count': 3, 'ids': [0, 1, 2], 'cost': 0}
Ricerca semantica
Ora possiamo effettuare ricerche semantiche rappresentando il testo della query di ricerca come un vettore e condurre una ricerca di similarità vettoriale su Milvus.
Ricerca vettoriale
Milvus accetta una o più richieste di ricerca vettoriale contemporaneamente. Il valore della variabile query_vectors è un elenco di vettori, dove ogni vettore è un array di numeri float.
query_vectors = embedding_fn.encode_queries(["Who is Alan Turing?"])
# If you don't have the embedding function you can use a fake vector to finish the demo:
# query_vectors = [ [ random.uniform(-1, 1) for _ in range(768) ] ]
res = client.search(
collection_name="demo_collection", # target collection
data=query_vectors, # query vectors
limit=2, # number of returned entities
output_fields=["text", "subject"], # specifies fields to be returned
)
print(res)
data: ["[{'id': 2, 'distance': 0.5859944820404053, 'entity': {'text': 'Born in Maida Vale, London, Turing was raised in southern England.', 'subject': 'history'}}, {'id': 1, 'distance': 0.5118255615234375, 'entity': {'text': 'Alan Turing was the first person to conduct substantial research in AI.', 'subject': 'history'}}]"] , extra_info: {'cost': 0}
L'output è un elenco di risultati, ciascuno dei quali corrisponde a una query di ricerca vettoriale. Ogni query contiene un elenco di risultati, dove ogni risultato contiene la chiave primaria dell'entità, la distanza dal vettore della query e i dettagli dell'entità con i dati specificati output_fields
.
Ricerca vettoriale con filtraggio dei metadati
È possibile effettuare una ricerca vettoriale anche considerando i valori dei metadati (chiamati campi "scalari" in Milvus, poiché gli scalari si riferiscono a dati non vettoriali). Ciò avviene con un'espressione di filtro che specifica alcuni criteri. Vediamo come cercare e filtrare con il campo subject
nell'esempio seguente.
# Insert more docs in another subject.
docs = [
"Machine learning has been used for drug design.",
"Computational synthesis with AI algorithms predicts molecular properties.",
"DDR1 is involved in cancers and fibrosis.",
]
vectors = embedding_fn.encode_documents(docs)
data = [
{"id": 3 + i, "vector": vectors[i], "text": docs[i], "subject": "biology"}
for i in range(len(vectors))
]
client.insert(collection_name="demo_collection", data=data)
# This will exclude any text in "history" subject despite close to the query vector.
res = client.search(
collection_name="demo_collection",
data=embedding_fn.encode_queries(["tell me AI related information"]),
filter="subject == 'biology'",
limit=2,
output_fields=["text", "subject"],
)
print(res)
data: ["[{'id': 4, 'distance': 0.27030569314956665, 'entity': {'text': 'Computational synthesis with AI algorithms predicts molecular properties.', 'subject': 'biology'}}, {'id': 3, 'distance': 0.16425910592079163, 'entity': {'text': 'Machine learning has been used for drug design.', 'subject': 'biology'}}]"] , extra_info: {'cost': 0}
Per impostazione predefinita, i campi scalari non sono indicizzati. Se è necessario eseguire una ricerca filtrata sui metadati in un set di dati di grandi dimensioni, si può prendere in considerazione l'utilizzo di uno schema fisso e attivare l'indice per migliorare le prestazioni della ricerca.
Oltre alla ricerca vettoriale, è possibile eseguire altri tipi di ricerca:
Query
Una query() è un'operazione che recupera tutte le entità che corrispondono a un criterio, come un'espressione di filtro o la corrispondenza con alcuni id.
Ad esempio, recupera tutte le entità il cui campo scalare ha un particolare valore:
res = client.query(
collection_name="demo_collection",
filter="subject == 'history'",
output_fields=["text", "subject"],
)
Recuperare direttamente le entità in base alla chiave primaria:
res = client.query(
collection_name="demo_collection",
ids=[0, 2],
output_fields=["vector", "text", "subject"],
)
Eliminare le entità
Se si desidera eliminare i dati, è possibile cancellare le entità specificando la chiave primaria o eliminando tutte le entità che corrispondono a una particolare espressione di filtro.
# Delete entities by primary key
res = client.delete(collection_name="demo_collection", ids=[0, 2])
print(res)
# Delete entities by a filter expression
res = client.delete(
collection_name="demo_collection",
filter="subject == 'biology'",
)
print(res)
[0, 2]
[3, 4, 5]
Caricare i dati esistenti
Poiché tutti i dati di Milvus Lite sono memorizzati in un file locale, è possibile caricare tutti i dati in memoria anche dopo la chiusura del programma, creando un MilvusClient
con il file esistente. Ad esempio, in questo modo si recuperano le raccolte dal file "milvus_demo.db" e si continua a scrivere i dati in esso.
from pymilvus import MilvusClient
client = MilvusClient("milvus_demo.db")
Eliminare la raccolta
Se si desidera eliminare tutti i dati di una raccolta, è possibile eliminare la raccolta con
# Drop collection
client.drop_collection(collection_name="demo_collection")
Per saperne di più
Milvus Lite è ottimo per iniziare con un programma python locale. Se si dispone di dati su larga scala o si desidera utilizzare Milvus in produzione, si può imparare a distribuire Milvus su Docker e Kubernetes. Tutte le modalità di distribuzione di Milvus condividono la stessa API, quindi il codice lato client non deve cambiare molto se si passa a un'altra modalità di distribuzione. È sufficiente specificare l'URI e il Token di un server Milvus distribuito ovunque:
client = MilvusClient(uri="http://localhost:19530", token="root:Milvus")
Milvus fornisce API REST e gRPC, con librerie client in linguaggi come Python, Java, Go, C# e Node.js.