Informazioni sulla ricerca ibrida

La ricerca vettoriale supporta la ricerca ibrida, un pattern di architettura molto utilizzato nel recupero delle informazioni (IR) che combina la ricerca semantica e la ricerca di parole chiave (chiamata anche ricerca basata su token). Con la ricerca ibrida, gli sviluppatori possono sfruttare il meglio dei due approcci, offrendo una qualità di ricerca superiore.

Questa pagina illustra i concetti di ricerca ibrida, ricerca semantica e ricerca basata su token e include esempi di come configurare la ricerca basata su token e la ricerca ibrida:

Perché la ricerca ibrida è importante?

Come descritto nella Panoramica di Ricerca vettoriale, la ricerca semantica con Ricerca vettoriale può trovare elementi con una somiglianza semantica utilizzando le query.

I modelli di embedding come gli embedding di Vertex AI creano uno spazio vettoriale come mappa dei significati dei contenuti. Ogni embedding di testo o multimodale è una posizione nella mappa che rappresenta il significato di alcuni contenuti. Come esempio semplificato, quando un modello di embedding prende un testo che parla di film per il 10%, di musica per il 2% e di attori per il 30%, potrebbe rappresentare questo testo con un embedding [0.1, 0.02, 0.3]. Con la ricerca vettoriale, puoi trovare rapidamente altri elementi simili. Questa ricerca in base al significato dei contenuti è chiamata ricerca semantica.

Illustrazione dei termini di ricerca semantica rispetto a un'altra in una mappa di significato.

La ricerca semantica con gli incorporamenti e la ricerca vettoriale può contribuire a rendere i sistemi IT intelligenti come bibliotecari o personale di negozi esperti. Gli incorporamenti possono essere utilizzati per collegare diversi dati aziendali ai relativi significati, ad esempio query e risultati di ricerca, testi e immagini, attività utente e prodotti consigliati, testi in inglese e testi in giapponese oppure dati dei sensori e condizioni di avviso. Con questa funzionalità, esistono molti casi d'uso per gli embedding.

La ricerca semantica non copre tutti i possibili requisiti per le applicazioni di recupero di informazioni, come la Retrieval-Augmented Generation (RAG). La ricerca semantica può trovare solo i dati che il modello di embedding può interpretare. Ad esempio, query o set di dati con numeri o SKU di prodotti arbitrari, nuovi nomi di prodotti aggiunti di recente e nomi in codice proprietari dell'azienda non funzionano con la ricerca semantica perché non sono inclusi nel set di dati di addestramento del modello di embedding. Questi sono i dati "out of domain".

In questi casi, devi combinare la ricerca semantica con la ricerca basata su parole chiave (chiamata anche basata su token) per creare una ricerca ibrida. Con la ricerca ibrida, puoi sfruttare sia la ricerca semantica sia quella basata su token per ottenere una qualità di ricerca superiore.

Uno dei sistemi di ricerca ibrida più utilizzati è la Ricerca Google. Nel 2015 il servizio ha incorporato la ricerca semantica con il modello RankBrain, oltre all'algoritmo di ricerca delle parole chiave basato su token. Con l'introduzione della ricerca ibrida, la Ricerca Google è stata in grado di migliorare notevolmente la qualità della ricerca soddisfacendo i due requisiti: ricerca per significato e ricerca per parola chiave.

In passato, creare un motore di ricerca ibrido era un'attività complessa. Come per la Ricerca Google, devi creare e gestire due diversi tipi di motori di ricerca (ricerca semantica e ricerca basata su token) e unire e classificare i risultati. Con il supporto della ricerca ibrida in Ricerca vettoriale, puoi creare il tuo sistema di ricerca ibrida con un unico indice di Ricerca vettoriale personalizzato in base alle esigenze della tua attività.

Come funziona la ricerca basata su token nella ricerca vettoriale? Dopo aver suddiviso il testo in token (ad esempio parole o sottoparole), puoi utilizzare algoritmi di embedding sparsi comuni come TF-IDF, BM25 o SPLADE per generare l'embedding sparso per il testo.

Una spiegazione semplificata degli embedding sparsi è che si tratta di vettori che rappresentano quante volte ogni parola o sottoparola compare nel testo. Gli embedding sparsi tipici non tengono conto della semantica del testo.

Incorporamenti sparsi

Nei testi potrebbero essere utilizzate migliaia di parole diverse. Pertanto, questo embedding solitamente ha decine di migliaia di dimensioni, con solo alcune di queste con valori diversi da zero. Ecco perché sono chiamati embedding "sparsi". La maggior parte dei valori è pari a zero. Questo spazio di embedding sparse funziona come una mappa di parole chiave, simile a un indice di libri.

In questo spazio di incorporamento sparso, puoi trovare incorporamenti simili esaminando il vicinato di un incorporamento della query. Questi embedding sono simili in termini di distribuzione delle parole chiave utilizzate nei testi.

Illustrazione di parole chiave simili posizionate una accanto all'altra in una mappa di significato.

Questo è il meccanismo di base della ricerca basata su token con embedding sparsi. Con la ricerca ibrida in Vector Search, puoi combinare embedding densi e sparsi in un unico indice vettoriale ed eseguire query con embedding densi, embedding sparsi o entrambi. Il risultato è una combinazione di ricerca semantica e risultati di ricerca basati su token.

La ricerca ibrida offre anche una latenza delle query inferiore rispetto a un motore di ricerca basato su token con un design di indice invertito. Come per la ricerca vettoriale per la ricerca semantica, ogni query con embedding densi o sparsi viene completata in millisecondi, anche con milioni o miliardi di elementi.

Per spiegare come utilizzare la ricerca basata su token, le sezioni seguenti includono esempi di codice che generano embedding sparsi e creano un indice con questi su Ricerca vettoriale.

Per provare questo codice campione, utilizza il notebook: Combinazione di ricerca semantica e per parole chiave: un tutorial sulla ricerca ibrida con Vertex AI Vector Search

Il primo passaggio consiste nel preparare un file di dati per creare un indice per gli embedding sparsi, in base al formato dei dati descritto in Formato e struttura dei dati di input.

In JSON, il file di dati ha il seguente aspetto:

{"id": "3", "sparse_embedding": {"values": [0.1, 0.2], "dimensions": [1, 4]}}
{"id": "4", "sparse_embedding": {"values": [-0.4, 0.2, -1.3], "dimensions": [10, 20, 30]}}

Ogni elemento deve avere una proprietà sparse_embedding con proprietà values e dimensions. Gli embedding sparsi hanno migliaia di dimensioni con alcuni valori diversi da zero. Questo formato di dati è efficiente perché contiene solo i valori diversi da zero con le relative posizioni nello spazio.

Preparare un set di dati di esempio

Come set di dati di esempio, utilizzeremo il set di dati Google Merchandise Store, che contiene circa 200 righe di prodotti con il brand Google.

0                          Google Sticker
1                    Google Cloud Sticker
2                       Android Black Pen
3                   Google Ombre Lime Pen
4                    For Everyone Eco Pen
                      ...
197        Google Recycled Black Backpack
198    Google Cascades Unisex Zip Sweater
199    Google Cascades Womens Zip Sweater
200         Google Cloud Skyline Backpack
201       Google City Black Tote Backpack

Prepara un vettore TF-IDF

Con questo set di dati, addestreremo un vettore, un modello che genera embedding sparsi da un testo. Questo esempio utilizza TfidfVectorizer in scikit-learn, un vettore di base che utilizza l'algoritmo TF-IDF.

from sklearn.feature_extraction.text import TfidfVectorizer

# Make a list of the item titles
corpus = df.title.tolist()

# Initialize TfidfVectorizer
vectorizer = TfidfVectorizer()

# Fit and Transform
vectorizer.fit_transform(corpus)

La variabile corpus contiene un elenco dei 200 nomi degli elementi, ad esempio "Adesivo Google" o "Fermaglio Dino di Chrome". Il codice li passa al vettore chiamando la funzione fit_transform(). In questo modo, il vettore si prepara a generare incorporamenti sparsi.

Il vettore TF-IDF cerca di dare un peso maggiore alle parole distintive nel set di dati (ad es. "Camicie" o "Dino") rispetto alle parole banali (ad es. "Il", "un" o "di") e conteggia quante volte queste parole distintive vengono utilizzate nel documento specificato. Ogni valore di un embedding sparso rappresenta la frequenza di ogni parola basata sui conteggi. Per ulteriori informazioni su TF-IDF, consulta Come funzionano TF-IDF e TfidfVectorizer?

Per semplificare, in questo esempio utilizziamo la tokenizzazione di base a livello di parola e la vettorizzazione TF-IDF. Nello sviluppo in produzione, puoi scegliere qualsiasi altra opzione per le tokenizzazioni e le vettoriizzazioni per generare embedding sparsi in base ai tuoi requisiti. Per quanto riguarda i tokenizzatori, in molti casi i tokenizzatori di sottoparole hanno un buon rendimento rispetto alla tokenizzazione a livello di parola e sono scelte molto diffuse. Per i vettorizzanti, BM25 è molto apprezzato come versione migliorata di TF-IDF. SPLADE è un altro algoritmo di vettorizzazione molto utilizzato che prende in considerazione alcune semantiche per l'embedding sparso.

Ottenere un embedding sparso

Per semplificare l'utilizzo del vettore con la ricerca vettoriale, definiamo una funzione wrapper, get_sparse_embedding():

def get_sparse_embedding(text):

  # Transform Text into TF-IDF Sparse Vector
  tfidf_vector = vectorizer.transform([text])

  # Create Sparse Embedding for the New Text
  values = []
  dims = []
  for i, tfidf_value in enumerate(tfidf_vector.data):
    values.append(float(tfidf_value))
    dims.append(int(tfidf_vector.indices[i]))
  return {"values": values, "dimensions": dims}

Questa funzione passa il parametro "text" al vettore per generare un embedding sparso. Poi convertilo nel formato {"values": ...., "dimensions": ...} citato in precedenza per creare un indice sparse di Vector Search.

Puoi testare questa funzione:

text_text = "Chrome Dino Pin"
get_sparse_embedding(text_text)

Dovresti visualizzare il seguente embedding sparso:

{'values': [0.6756557405747007, 0.5212913389979028, 0.5212913389979028],
 'dimensions': [157, 48, 33]}

Crea un file di dati di input

Per questo esempio, genereremo incorporamenti sparsi per tutti i 200 articoli.

items = []
for i in range(len(df)):
  id = i
  title = df.title[i]
  sparse_embedding = get_sparse_embedding(title)
  items.append({"id": id, "title": title, "sparse_embedding": sparse_embedding})

Questo codice genera la seguente riga per ogni elemento:

{
  'id': 0,
  'title': 'Google Sticker',
  'sparse_embedding': {
    'values': [0.933008728540452, 0.359853737603667],
    'dimensions': [191, 78]
  }
}

Poi, salvali come file JSONL "items.json" e caricali in un bucket Cloud Storage.

# output as a JSONL file and save to bucket
with open("items.json", "w") as f:
  for item in items:
    f.write(f"{item}\n")
! gsutil cp items.json $BUCKET_URI

Creare un indice di embedding sparso in Vector Search

Successivamente, creeremo e implementeremo un indice di embedding sparso in Vector Search. Si tratta della stessa procedura descritta nella guida introduttiva a Vector Search.

# create Index
my_index = aiplatform.MatchingEngineIndex.create_tree_ah_index(
  display_name = f"vs-hybridsearch-index-{UID}",
  contents_delta_uri = BUCKET_URI,
  dimensions = 768,
  approximate_neighbors_count = 10,
)

Per utilizzare l'indice, devi creare un endpoint indice. Funziona come un'istanza di server che accetta richieste di query per l'indice.

# create IndexEndpoint
my_index_endpoint = aiplatform.MatchingEngineIndexEndpoint.create(
  display_name = f"vs-quickstart-index-endpoint-{UID}",
  public_endpoint_enabled = True
)

Con l'endpoint indice, esegui il deployment dell'indice specificando un ID indice di cui è stato eseguito il deployment univoco.

DEPLOYED_INDEX_ID = f"vs_quickstart_deployed_{UID}"
# deploy the Index to the Index Endpoint
my_index_endpoint.deploy_index(
    index = my_index, deployed_index_id = DEPLOYED_INDEX_ID
)

Dopo aver atteso il deployment, possiamo eseguire una query di test.

Eseguire una query con un indice di embedding sparso

Per eseguire una query con un indice di embedding sparso, devi creare un oggetto HybridQuery per incapsulare l'embedding sparso del testo della query, come nell'esempio seguente:

from google.cloud.aiplatform.matching_engine.matching_engine_index_endpoint import HybridQuery

# create HybridQuery
query_text = "Kids"
query_emb = get_sparse_embedding(query_text)
query = HybridQuery(
  sparse_embedding_dimensions=query_emb['dimensions'],
  sparse_embedding_values=query_emb['values'],
)

Questo codice di esempio utilizza il testo "Bambini" per la query. Ora esegui una query con l'oggetto HybridQuery.

# build a query request
response = my_index_endpoint.find_neighbors(
  deployed_index_id=DEPLOYED_INDEX_ID,
  queries=[query],
  num_neighbors=5,
)

# print results
for idx, neighbor in enumerate(response[0]):
  title = df.title[int(neighbor.id)]
  print(f"{title:<40}")

L'output dovrebbe essere simile al seguente:

Google Blue Kids Sunglasses
Google Red Kids Sunglasses
YouTube Kids Coloring Pencils
YouTube Kids Character Sticker Sheet

Tra i 200 elementi, il risultato contiene i nomi degli elementi che contengono la parola chiave "Bambini".

Questo esempio combina la ricerca basata su token con la ricerca semantica per creare una ricerca ibrida nella ricerca vettoriale.

Come creare un indice ibrido

Per creare un indice ibrido, ogni elemento deve avere sia "embedding" (per l'embedding denso) sia "sparse_embedding":

items = []
for i in range(len(df)):
  id = i
  title = df.title[i]
  dense_embedding = get_dense_embedding(title)
  sparse_embedding = get_sparse_embedding(title)
  items.append(
    {"id": id, "title": title,
      "embedding": dense_embedding,
      "sparse_embedding": sparse_embedding,}
  )
items[0]

La funzione get_dense_embedding() utilizza l'API Vertex AI Embedding per generare embedding di testo con 768 dimensioni. Vengono generati embedding sia densi che sparsi nel seguente formato:

{
  "id": 0,
  "title": "Google Sticker",
  "embedding":
    [0.022880317643284798,
    -0.03315234184265137,
    ...
    -0.03309667482972145,
    0.04621824622154236],
  "sparse_embedding": {
    "values": [0.933008728540452, 0.359853737603667],
    "dimensions": [191, 78]
  }
}

Il resto della procedura è uguale a quella descritta in Esempio: come utilizzare la ricerca basata su token: carica il file JSONL nel bucket Cloud Storage, crea un indice di ricerca vettoriale con il file e esegui il deployment dell'indice nell'endpoint dell'indice.

Eseguire una query ibrida

Dopo aver eseguito il deployment dell'indice ibrido, puoi eseguire una query ibrida:

# create HybridQuery
query_text = "Kids"
query_dense_emb = get_dense_embedding(query_text)
query_sparse_emb = get_sparse_embedding(query_text)
query = HybridQuery(
  dense_embedding=query_dense_emb,
  sparse_embedding_dimensions=query_sparse_emb['dimensions'],
  sparse_embedding_values=query_sparse_emb['values'],
  rrf_ranking_alpha=0.5,
)

Per il testo della query "Bambini", genera embedding sia densi che sparsi per la parola e incapsulali nell'oggetto HybridQuery. La differenza rispetto al valore HybridQuery precedente è costituita da due parametri aggiuntivi: dense_embedding e rrf_ranking_alpha.

Questa volta stamperemo le distanze per ogni elemento:

# print results
for idx, neighbor in enumerate(response[0]):
  title = df.title[int(neighbor.id)]
  dense_dist = neighbor.distance if neighbor.distance else 0.0
  sparse_dist = neighbor.sparse_distance if neighbor.sparse_distance else 0.0
  print(f"{title:<40}: dense_dist: {dense_dist:.3f}, sparse_dist: {sparse_dist:.3f}")

In ogni oggetto neighbor è presente una proprietà distance che indica la distanza tra la query e l'elemento con l'embedding denso e una proprietà sparse_distance che indica la distanza con l'embedding sparso. Questi valori sono distanze invertite, pertanto un valore più alto indica una distanza più breve.

Se esegui una query con HybridQuery, ottieni il seguente risultato:

Google Blue Kids Sunglasses             : dense_dist: 0.677, sparse_dist: 0.606
Google Red Kids Sunglasses              : dense_dist: 0.665, sparse_dist: 0.572
YouTube Kids Coloring Pencils           : dense_dist: 0.655, sparse_dist: 0.478
YouTube Kids Character Sticker Sheet    : dense_dist: 0.644, sparse_dist: 0.468
Google White Classic Youth Tee          : dense_dist: 0.645, sparse_dist: 0.000
Google Doogler Youth Tee                : dense_dist: 0.639, sparse_dist: 0.000
Google Indigo Youth Tee                 : dense_dist: 0.637, sparse_dist: 0.000
Google Black Classic Youth Tee          : dense_dist: 0.632, sparse_dist: 0.000
Chrome Dino Glow-in-the-Dark Youth Tee  : dense_dist: 0.632, sparse_dist: 0.000
Google Bike Youth Tee                   : dense_dist: 0.629, sparse_dist: 0.000

Oltre ai risultati di ricerca basati su token che contengono la parola chiave "Bambini", sono inclusi anche i risultati di ricerca semantica. Ad esempio, "Google White Classic Youth Tee" è incluso perché il modello di embedding sa che "Youth" e "Kids" sono semanticamente simili.

Per unire i risultati di ricerca basati su token e semantici, la ricerca ibrida utilizza la fusione di ranking reciproco (RRF). Per ulteriori informazioni su RRF e su come specificare il parametro rrf_ranking_alpha, consulta Che cos'è la fusione del ranking reciproco?.

Re-ranking

RRF fornisce un modo per unire il ranking dei risultati di ricerca semantici e basati su token. In molti sistemi di recupero delle informazioni di produzione o di consigli, i risultati verranno sottoposti ad altri algoritmi di ranking di precisione, il cosiddetto ranking. Con la combinazione del recupero rapido a livello di millisecondi con la ricerca vettoriale e il ricoinvolgimento preciso dei risultati, puoi creare sistemi su più livelli che offrono una qualità di ricerca o un rendimento dei consigli superiore.

Pipeline di valutazione di milioni di articoli, quindi ricalcolo del ranking di centinaia di articoli per produrre un numero inferiore di consigli.

L'API Ranking di Vertex AI fornisce un modo per implementare il ranking in base alla pertinenza generica tra il testo della query e i testi dei risultati di ricerca con il modello preaddestrato. TensorFlow Ranking fornisce anche un'introduzione su come progettare e addestrare modelli di ranking basato sull'apprendimento (LTR) per il ricoordinamento avanzato che possono essere personalizzati in base a vari requisiti aziendali.

Iniziare a utilizzare la ricerca ibrida

Le seguenti risorse possono aiutarti a iniziare a utilizzare la ricerca ibrida in Ricerca vettoriale.

Risorse per la ricerca ibrida

Risorse di Vector Search

Concetti aggiuntivi

Le sezioni seguenti descrivono in maggiore dettaglio TF-IDF e TfidVectorizer, la fusione di ranking reciproco e il parametro alpha.

Come funzionano TF-IDF e TfidfVectorizer?

La funzione fit_transform() esegue due importanti processi dell'algoritmo TF-IDF:

  • Adattamento: il vettore calcola la frequenza inversa dei documenti (IDF) per ogni termine del vocabolario. L'IDF riflette l'importanza di un termine in tutto il corpus. I termini rari ricevono punteggi IDF più elevati:

    IDF(t) = log_e(Total number of documents / Number of documents containing term t)

  • Trasforma:

    • Tokenizzazione: suddivide i documenti in singoli termini (parole o frasi).
    • Calcolo della frequenza dei termini (TF): conteggia la frequenza con cui ogni termine compare in ogni documento con:

      TF(t, d) = (Number of times term t appears in document d) / (Total number of terms in document d)

    • Calcolo TF-IDF: combina il TF per ogni termine con l'IDF precalcolato per creare un punteggio TF-IDF. Questo punteggio rappresenta l'importanza di un termine in un determinato documento rispetto all'intero corpus.

      TF-IDF(t, d) = TF(t, d) * IDF(t)

      Il vettore TF-IDF cerca di dare un peso maggiore alle parole distintive nel set di dati, come "Camicie" o "Dino", rispetto alle parole banali, come "Il", "un" o "di", e conteggia quante volte queste parole distintive vengono utilizzate nel documento specificato. Ogni valore di un embedding sparso rappresenta la frequenza di ogni parola in base ai conteggi.

Che cos'è Reciprocal Rank Fusion?

Per unire i risultati di ricerca basati su token e semantici, la ricerca ibrida utilizza la fusione di ranking reciproco (RRF). RRF è un algoritmo per combinare più elenchi di elementi classificati in un unico ranking unificato. Si tratta di una tecnica molto utilizzata per unire i risultati di ricerca provenienti da fonti o metodi di recupero diversi, in particolare nei sistemi di ricerca ibrida e nei modelli linguistici di grandi dimensioni.

Nel caso della ricerca ibrida di Ricerca vettoriale, la distanza fitta e la distanza sparsa vengono misurate in spazi diversi e non possono essere confrontate direttamente tra loro. Pertanto, l'RRF è efficace per l'unione e il ranking dei risultati provenienti dai due diversi spazi.

Ecco come funziona il RRF:

  1. Rango reciproco: per ogni elemento di un elenco classificato, calcola il suo rango reciproco. Ciò significa prendere l'inverso della posizione (del ranking) dell'elemento nell'elenco. Ad esempio, l'elemento classificato al primo posto riceve un ranking reciproco di 1/1 = 1 e l'elemento classificato al secondo posto riceve 1/2 = 0, 5.
  2. Somma i ranghi reciproci: somma i ranghi reciproci di ogni elemento in tutti gli elenchi classificati. In questo modo viene assegnato un punteggio finale a ogni articolo.
  3. Ordina per punteggio finale: ordina gli elementi in base al punteggio finale in ordine decrescente. Gli elementi con i punteggi più alti sono considerati i più pertinenti o importanti.

In breve, gli elementi con ranking più elevati sia nei risultati densi che in quelli sparsi verranno messi in cima all'elenco. Pertanto, l'articolo "Occhiali da sole per bambini blu Google" si trova in alto perché ha un ranking più elevato sia nei risultati di ricerca densi che in quelli sparsi. Elementi come "Google White Classic Youth Tee" hanno un ranking basso perché hanno un ranking solo nei risultati di ricerca con molti elementi.

Comportamento del parametro alpha

L'esempio di utilizzo della ricerca ibrida imposta il parametro rrf_ranking_alpha su 0,5 durante la creazione dell'oggetto HybridQuery. Puoi specificare una ponderazione per il ranking dei risultati di ricerca densi e sparsi utilizzando i seguenti valori per rrf_ranking_alpha:

  • 1 o non specificato: la ricerca ibrida utilizza solo risultati di ricerca concentrati e ignora quelli sparsi.
  • 0: la ricerca ibrida utilizza solo risultati di ricerca sparsi e ignora quelli concentrati.
  • 0 a 1: la ricerca ibrida unisce entrambi i risultati da ricerche dense e sparse con il peso specificato dal valore. 0,5 significa che verranno unite con lo stesso peso.