Quando lavoriamo con il linguaggio naturale, spesso desideriamo che le nostre applicazioni non si limitino a confrontare stringhe di testo in maniera superficiale. Ad esempio, vogliamo che “gatto domestico” e “felino da compagnia” siano riconosciuti come concetti simili. Per raggiungere questo obiettivo, utilizziamo gli embeddings, ovvero rappresentazioni numeriche del testo. Un embedding trasforma una frase, una parola o un documento in un vettore di numeri reali, che conserva le relazioni semantiche tra testi diversi.
Come funzionano gli embeddings
Gli embeddings possono essere pensati come coordinate in uno spazio multidimensionale. Frasi semanticamente simili avranno coordinate vicine, mentre frasi molto diverse saranno lontane. Ad esempio, immaginiamo di avere queste due frasi: “Il gatto dorme sul divano.” e il “Il felino riposa sul sofà.” Quando le convertiamo in embeddings, i vettori numerici risultanti saranno molto vicini tra loro, nonostante le parole non siano identiche. Questo ci consente di costruire applicazioni capaci di comprendere meglio il significato del testo, anziché fermarsi alla mera forma.
Applicazioni pratiche
Gli embeddings possono essere utilizzati in diversi contesti applicativi. I principali casi d’uso sono:
- Ricerca semantica: possiamo cercare documenti simili a una query, anche se non condividono le stesse parole. Esempio: un utente cerca “come curare una pianta d’appartamento”. Con gli embeddings, il sistema può restituire anche articoli che parlano di “manutenzione delle piante da interni” o “cura dei vasi da salotto”.
- Raccomandazioni: confrontando embeddings possiamo suggerire contenuti simili. Esempio: se un utente legge un articolo sugli “algoritmi di machine learning”, possiamo raccomandare testi sugli “algoritmi supervisionati” o “reti neurali”, perché vicini nello spazio vettoriale.
- Rilevamento anomalie: analizzando la distanza tra embeddings, possiamo individuare testi che si discostano nettamente dalla norma. Esempio: in un sistema di recensioni, un embedding che risulta troppo distante dagli altri può segnalare un commento sospetto o spam.
Embeddings con Claude
Supponiamo di voler realizzare un sistema basato su Claude capace di rispondere a domande utilizzando testi esterni, come documenti aziendali, articoli o manuali. Poiché Claude non genera embeddings in modo nativo, come evidenziato nella documentazione ufficiale, è necessario affidarci a un fornitore esterno, ad esempio Voyage AI, per ottenere le rappresentazioni vettoriali del testo. Quindi, per realizzare quanto descritto sopra dobbiamo:
- scegliere un fornitore di embeddings (ad esempio, Voyage AI) e selezionare il modello più adatto alle nostre necessità (ad esempio voyage-3-large o voyage-3.5);
- convertire i documenti di riferimento in embeddings;
- Infine, passarli a Claude per generare una risposta contestualizzata.
Quanto detto si concretizza nel seguente codice:
import voyageai
vo = voyageai.Client()
# This will automatically use the environment variable VOYAGE_API_KEY.
# Alternatively, you can use vo = voyageai.Client(api_key="")
texts = ["Sample text 1", "Sample text 2"]
result = vo.embed(texts, model="voyage-3.5", input_type="document")
print(result.embeddings[0])
print(result.embeddings[1])
Questo codice mostra un semplice esempio di utilizzo della libreria voyageai per generare embeddings a partire da testi. Dopo aver inizializzato il client, che recupera automaticamente la chiave API dalla variabile d’ambiente VOYAGE_API_KEY, definiamo una lista di stringhe (texts) contenente due brevi esempi. Successivamente invochiamo il metodo embed, specificando il modello voyage-3.5 e il tipo di input come document, ottenendo così una rappresentazione numerica (embedding) per ciascun testo. Infine, stampiamo a schermo i vettori corrispondenti ai due testi forniti, che potranno essere usati in applicazioni di ricerca semantica, clustering o raccomandazione. Quindi, otteniamo in result.embeddings una lista composta da due vettori di embedding, ciascuno formato da 1024 valori numerici in virgola mobile.
In altre parole, a ogni testo della nostra lista corrisponde un embedding distinto: ad esempio, il primo vettore rappresenta semanticamente la stringa “Sample text 1”, mentre il secondo rappresenta la stringa “Sample text 2”. Tali vettori, che appaiono come sequenze di numeri reali, costituiscono la base su cui possiamo costruire operazioni avanzate di confronto semantico, ricerca o classificazione.
Un secondo esempio di embeddings
Un altro esempio pratico è il seguente:
import numpy as np
# Embed the query
query_embd = vo.embed(
[query], model="voyage-3.5", input_type="query"
).embeddings[0]
# Compute the similarity
# Voyage embeddings are normalized to length 1, therefore dot-product
# and cosine similarity are the same.
similarities = np.dot(doc_embds, query_embd)
retrieved_id = np.argmax(similarities)
print(documents[retrieved_id])
In questo frammento di codice mostriamo per prima cosa come viene calcolato l’embedding della query (query_embd), specificando come tipo di input "query". Successivamente, si confronta questo embedding con quelli dei documenti precedentemente calcolati (doc_embds) attraverso il prodotto scalare (np.dot).
Dato che gli embeddings di Voyage sono già normalizzati a lunghezza unitaria, il prodotto scalare equivale alla similarità coseno. Viene quindi individuato l’indice del documento con la similarità massima (np.argmax(similarities)) e stampato a schermo il contenuto del documento più rilevante rispetto alla query. Il codice implementa un meccanismo semplice ma efficace di recupero semantico basato su embeddings.
In questo ultimo codice abbiamo utilizzato come come input_type il tipo query. La differenza di query con il document negli embeddings riguarda principalmente il ruolo che ciascuno ha nel processo di retrieval semantico. Il tipo document rappresenta un testo di riferimento, come un articolo, un manuale o una voce di knowledge base, e viene calcolato in anticipo per tutti i documenti, mentre il query embedding rappresenta la domanda dell’utente, cioè il testo della ricerca, e viene generato al momento della richiesta.
Confrontando l’embedding della query con quelli dei documenti possiamo individuare i testi più rilevanti semanticamente. Nel codice di esempio viene utilizzato il prodotto scalare, che per vettori normalizzati equivale alla similarità coseno, ma con NumPy possiamo anche calcolare altre misure di similarità o distanza.
Una spiegazione dettagliata sulla similarità tra embeddings è disponibile nella documentazione. Gli embeddings di Voyage AI sono normalizzati a lunghezza 1. Ciò significa che la similarità coseno è equivalente al prodotto scalare, anche se quest’ultimo può essere calcolato più rapidamente, e che la similarità coseno e la distanza euclidea producono classifiche identiche dei documenti.
Conclusione
In quest'ultima lezione della guida abbiamo visto come gli embeddings ci permettano di comprendere il significato del testo e di costruire applicazioni con Claude in grado di rispondere a domande su testi esterni, confrontando documenti e query tramite prodotto scalare o similarità coseno per ottenere risultati semanticamente rilevanti. Con queste conoscenze, siamo ora in grado di progettare sistemi di ricerca, raccomandazione e analisi dei dati testuali più intelligenti ed efficaci.
Se vuoi aggiornamenti su Embeddings con Claude AI inserisci la tua email nel box qui sotto: