Nella prima parte, abbiamo discusso di come interagire con un modello per ottenere un dialogo basato su informazioni sulle quali non è stato addestrato. Per riassumere, si aggiungono al contesto le informazioni desiderate. Ma allora, supponiamo che si voglia utilizzare un'intera base di conoscenza? Ciò comporterebbe troppo molte informazioni da aggiungere al contesto. Pertanto, dobbiamo inserire nel database l'insieme delle informazioni che desideriamo fornire agli utenti. Divideremo i nostri contenuti in paragrafi e applicheremo un indice vettoriale. Questo indice converte il testo in vettori numerici che rappresentano il significato semantico delle parole e delle frasi. Ciò che è fantastico, è che ciò consente di effettuare ricerche basate sul significato piuttosto che sulle parole esatte.
Tra i database in grado di eseguire questo tipo di ricerca, troviamo:
-
Elasticsearch: È un motore di ricerca e di analisi distribuito, ampiamente utilizzato per la ricerca testuale. Elasticsearch può essere utilizzato per creare indici vettoriali grazie ai suoi plugin e integrazioni, in particolare per il trattamento del linguaggio naturale.
-
Faiss (Facebook AI Similarity Search): Sviluppato da Facebook AI, Faiss è una libreria che permette l'indicizzazione vettoriale efficiente e la ricerca di similarità. È particolarmente adatta per gestire grandi set di vettori ed è spesso utilizzata nei sistemi di raccomandazione e di ricerca semantica.
-
Milvus: Milvus è un sistema di gestione di basi di dati vettoriali open source. È progettato per gestire indici vettoriali su larga scala ed è compatibile con diversi modelli di machine learning, compresi quelli usati per il trattamento del linguaggio naturale.
-
Pinecone: Pinecone è un database vettoriale progettato per le applicazioni di machine learning. Offre gestione e ricerca di vettori su larga scala, utile per applicazioni di ricerca semantica e NLP.
-
Weaviate: Weaviate è una base di dati di conoscenze vettoriali, che consente lo stoccaggio di dati in forma vettoriale. Supporta le query semantiche ed è ottimizzata per i casi di utilizzo che coinvolgono il trattamento del linguaggio naturale.
-
Annoy (Approximate Nearest Neighbors Oh Yeah): Annoy è una libreria C++ con binding Python progettata per cercare i vicini più vicini in spazi ad alta dimensione. È usata per creare indici vettoriali ed è efficiente per le query di ricerca rapida.
-
HNSW (Hierarchical Navigable Small World): HNSW è un algoritmo popolare per la ricerca dei vicini più vicini negli spazi ad alta dimensione. Diversi sistemi di gestione di basi di dati integrano HNSW per la creazione di indici vettoriali.
-
Postgresql (pgvector): Postgresql utilizza HNSW per la creazione di indici vettoriali nell'ambito dell'estensione pgvector.Potete trovare un elenco più completo a questo indirizzo: https://js.langchain.com/docs/integrations/vectorstores Questo offre già molte possibilità.
Per quanto riguarda la parte di memorizzazione, come si creano questi vettori? Si usano ciò che si chiama gli 'embeddings'. Gli embeddings trasformano dati complessi, come le parole, in vettori in uno spazio multidimensionale. Ad esempio, ogni parola di un testo può essere rappresentata da un vettore di 50, 100, 300 dimensioni o più. Questi vettori sono progettati in modo tale che le parole con significati simili siano vicine in questo spazio vettoriale. Ad esempio, 're' e 'regina' sarebbero vicini, riflettendo la loro relazione semantica. Ci sono modelli di embeddings pre-addestrati come Word2Vec, GloVe o BERT, che sono stati addestrati su vasti corpora di testo e possono essere utilizzati per ottenere embeddings di parole di alta qualità.
Potrete trovare un elenco dei diversi sistemi di embeddings disponibili a questo indirizzo: https://js.langchain.com/docs/integrations/text_embedding
È importante capire che quando si utilizza un modello per creare i propri embeddings, si dovrà usare lo stesso modello anche per creare le proprie query in seguito. È fondamentale mantenere la coerenza per massimizzare le possibilità che il motore del database confronti correttamente la distanza tra i vettori della vostra query e quelli memorizzati nel database.
In sintesi, avete in database una serie di vettori, e farete una query con un'altra serie di vettori. Il database confronterà le distanze; più sono basse, più le significazioni saranno simili.
Per approfondire, ho trovato gli esempi di questo articolo piuttosto esemplificativi: https://medium.com/@aravilliatchutaram/intent-classification-using-vector-database-txtai-sqlite-and-large-language-models-821f939b87ba
Se desiderate utilizzare un sistema diverso da quello proposto in standard da txtai, ecco una risorsa definitiva: https://neuml.hashnode.dev/customize-your-own-embeddings-database Qui, troverete esempi di generazione di embeddings con NumPy, PyTorch, Faiss, HNSW e persino l'utilizzo di un'API esterna come Huggingface.
Da notare che txtai offre di default la creazione di vettori con il modello https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2 utilizzando Faiss come backend dell'indice Ann.
Per riassumere:
- Creare un embedding
from txtai.embeddings import Embeddings
embeddings = Embeddings()
- Creare un embedding con una registrazione in un database: SQLITE / all-MiniLM-L6-v2 / Faiss
from txtai.embeddings import Embeddings
embeddings = Embeddings(content=True)
- Per creare un embedding con Postgresql / gte-large / Faiss
pipenv shell
pipenv install psycopg2-binary
from datasets import load_dataset
import txtai
# Load dataset
ds = load_dataset("ag_news", split="train")
embeddings = txtai.Embeddings(
content="postgresql+psycopg2://testuser:testpwd@localhost:5432/vectordb",
objects=True,
backend="faiss",
path="thenlper/gte-large"
)
# indexer
embeddings.index(ds["text"])
# sauvegarder l'index
embeddings.save("./index")
# charger l'index sauvegardé
embeddings.load("./index")
## ou dans le cloud,compressé
embeddings.save("/path/to/save/index.tar.gz", cloud={...})
In questo esempio, registriamo i contenuti in un database e creiamo il nostro indice con FAISS. Questo vi richiederà di salvare i vostri indici per poterli ricaricare in seguito. Avete anche la possibilità di salvarli nel cloud o in Elasticsearch. Da notare che se modificate i vostri contenuti nel database, dovrete ricreare il vostro indice.
import txtai
embeddings = txtai.Embeddings(
content="postgresql+psycopg2://testuser:testpwd@localhost:5432/vectordb",
objects=True,
backend="faiss",
path="thenlper/gte-large"
)
embeddings.reindex(path="sentence-transformers/all-MiniLM-L6-v2", backend="hnsw")
embeddings.save("./index")
Strategia di frammentazione o segmentazione dei vostri documenti
Ci sono diverse strategie quando si tratta di creare i vostri segmenti di dati (chunks).1. Segmentazione di Dimensione Fissa
È il metodo più comune e semplice. Si basa sulla determinazione di un numero fisso di token per ciascun segmento, potenzialmente con un certo sovrapposizione tra di loro per preservare il contesto semantico.
- Segmentazione "Consa della Contenuto" (Content-aware)
Questo approccio si serve della natura del contenuto per una segmentazione più sofisticata. Comprende:
-
Suddivisione delle Frasi: Utilizzo di strumenti come NLTK o spaCy per dividere il testo in frasi, garantendo una migliore preservazione del contesto.
-
Segmentazione Ricorsiva: Divide il testo in segmenti più piccoli in maniera gerarchica e iterativa, utilizzando diversi separatori o criteri fino a ottenere la dimensione o la struttura di segmento desiderata.
Queste tecniche variano in funzione del contenuto e dell'applicazione specifica, e la scelta del metodo adeguato dipende da diversi fattori, inclusa la natura del contenuto, il modello di codifica usato, e l'obiettivo dell'applicazione.
Configurazione semplice del database PostgreSQL:
Qui non c'è nessun mistero, facciamo tutto nel modo più semplice. Useremo l'immagine Docker fornita dallo sviluppatore del modulo pgvector.
## docker-compose.yaml
services:
db:
hostname: db
image: ankane/pgvector
ports:
- 5432:5432
restart: always
environment:
- POSTGRES_DB=vectordb
- POSTGRES_USER=testuser
- POSTGRES_PASSWORD=testpwd
- POSTGRES_HOST_AUTH_METHOD=trust
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
## init.sql
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE IF NOT EXISTS embeddings (
id SERIAL PRIMARY KEY,
embedding vector,
text text,
created_at timestamptz DEFAULT now()
);
docker compose up -d
Quindi, è tutto per questa seconda parte, in qualche modo teorica. Ho solo effettuato una panoramica sugli argomenti e come potrete immaginare, ciascuno dei componenti coinvolti è ricco di parametri per la regolazione fine. La bellezza di TxtAi risiede nel fatto che tutto è semplice e già preconfigurato. Questi esempi non sono veramente utili se si desidera implementare un sistema semplice. Tuttavia, nella realtà di un progetto, si utilizzano raramente le cose così come sono. Ecco perché ho voluto mostrare per ogni elemento le possibilità offerte da questo framework. Tutto è configurabile, nulla è lasciato al caso. Ciò che è fantastico di questo framework, specialmente se si è nuovi nel campo, è che si possono scoprire attraverso la documentazione i moduli necessari per un tale sistema. Le cose possono diventare molto complesse quando si desidera uscire dagli schemi stabiliti.
Nel terzo capitolo della nostra serie sul sistema RAG, ci sporcheremo un po' più le mani. Avendo già affrontato il lato più arido della teoria, potremo concentrarci sul codice.