Trovare i vicini più prossimi approssimativi, creare indici vettoriali ed eseguire query sugli incorporamenti vettoriali

Questa pagina descrive come trovare i vicini più prossimi approssimativi (ANN), creare indici vettoriali ed eseguire query sugli incorporamenti vettoriali utilizzando le seguenti funzioni di distanza ANN in Spanner:

  • APPROX_COSINE_DISTANCE
  • APPROX_EUCLIDEAN_DISTANCE
  • APPROX_DOT_PRODUCT

Quando un set di dati è piccolo, puoi utilizzare K-Nearest Neighbors (KNN) per trovare i vettori k-nearest esatti. Tuttavia, man mano che il set di dati cresce, aumentano anche la latenza e il costo di una ricerca KNN. Puoi utilizzare ANN per trovare i k-nearest neighbors approssimativi con latenza e costi notevolmente ridotti.

K-nearest neighbors approssimati

In una ricerca ANN, i k vettori restituiti non sono i k vicini più prossimi perché la ricerca ANN calcola distanze approssimative e potrebbe non esaminare tutti i vettori nel set di dati. Occasionalmente, vengono restituiti alcuni vettori che non rientrano tra i primi k-nearest neighbor. Questo fenomeno è noto come perdita di richiamo. La quantità di perdita di richiamo accettabile dipende dal caso d'uso, ma nella maggior parte dei casi, perdere un po' di richiamo in cambio di prestazioni del database migliorate è un compromesso accettabile.

Per ulteriori dettagli sulle funzioni di distanza approssimativa di Spanner, vedi:

Indice vettoriale

Spanner accelera le ricerche vettoriali ANN utilizzando un indice vettoriale specializzato. Questo indice sfrutta Scalable Nearest Neighbor (ScaNN) di Google Research, un algoritmo per la ricerca dei vicini più prossimi altamente efficiente.

L'indice vettoriale utilizza una struttura ad albero per partizionare i dati e facilitare ricerche più rapide. Spanner offre configurazioni ad albero a due e tre livelli:

  • Configurazione ad albero a due livelli: i nodi foglia (num_leaves) contengono gruppi di vettori strettamente correlati insieme al loro centroide corrispondente. Il livello radice è costituito dai centroidi di tutti i nodi foglia.
  • Configurazione ad albero a tre livelli: simile nel concetto a un albero a due livelli, ma introduce un ulteriore livello di ramificazione (num_branches), da cui i centroidi dei nodi foglia vengono ulteriormente partizionati per formare il livello radice (num_leaves).

Spanner sceglie un indice per te. Tuttavia, se sai che un indice specifico funziona meglio, puoi utilizzare il suggerimento FORCE_INDEX per scegliere di utilizzare l'indice vettoriale più appropriato per il tuo caso d'uso.

Per ulteriori informazioni, consulta la sezione Estratti conto VECTOR INDEX.

Limitazioni

Crea indice vettoriale

Per ottimizzare il recupero e il rendimento di un indice vettoriale, ti consigliamo di:

  • Crea l'indice vettoriale dopo che la maggior parte delle righe con gli incorporamenti è stata scritta nel database. Potresti anche dover ricreare periodicamente l'indice vettoriale dopo aver inserito nuovi dati. Per ulteriori informazioni, vedi Ricostruire l'indice vettoriale.

  • Utilizza la clausola STORING per archiviare una copia di una colonna nell'indice vettoriale. Se un valore della colonna è memorizzato nell'indice vettoriale, Spanner esegue il filtraggio a livello di foglia dell'indice per migliorare le prestazioni della query. Ti consigliamo di memorizzare una colonna se viene utilizzata in una condizione di filtro. Per saperne di più sull'utilizzo di STORING in un indice, vedi Creare un indice per le scansioni solo indice.

Quando crei la tabella, la colonna di incorporamento deve essere un array di tipo di dati FLOAT32 (consigliato) o FLOAT64 e deve avere un'annotazione vector_length, che indica la dimensione dei vettori.

La seguente istruzione DDL crea una tabella Documents con una colonna di incorporamento DocEmbedding con una lunghezza del vettore:

CREATE TABLE Documents (
  DocId INT64 NOT NULL,
  ...
  DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);

Dopo aver compilato la tabella Documents, puoi creare un indice vettoriale con un albero a due livelli e 1000 nodi foglia in una tabella Documents con una colonna di incorporamento DocEmbedding utilizzando la distanza del coseno:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(DocEmbedding)
  STORING (WordCount)
  OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);

Per creare un indice vettoriale con un albero a tre livelli e 1.000.000 di nodi foglia:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(NullableDocEmbedding)
  STORING (WordCount)
  WHERE NullableDocEmbedding IS NOT NULL
  OPTIONS (distance_type = 'COSINE', tree_depth = 3, num_branches=1000, num_leaves = 1000000);

Se la colonna di incorporamento non è contrassegnata come NOT NULL nella definizione della tabella, devi dichiararla con una clausola WHERE COLUMN_NAME IS NOT NULL:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(NullableDocEmbedding)
  STORING (WordCount)
  WHERE NullableDocEmbedding IS NOT NULL
  OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);

Filtrare un indice vettoriale

Puoi anche creare un indice vettoriale filtrato per trovare gli elementi più simili nel tuo database che corrispondono alla condizione di filtro. Un indice vettoriale filtrato indicizza selettivamente le righe che soddisfano le condizioni di filtro specificate, migliorando le prestazioni di ricerca.

Nel seguente esempio, la tabella Documents ha una colonna chiamata Category. Nella nostra ricerca vettoriale, vogliamo indicizzare la categoria "Tecnologia", quindi creiamo una colonna generata che restituisce NULL se la condizione della categoria non è soddisfatta.

CREATE TABLE Documents (
  DocId INT64 NOT NULL,
  Category STRING(MAX),
  NullIfFiltered BOOL AS (IF(Category = 'Tech', TRUE, NULL)) HIDDEN,
  DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);

Poi creiamo un indice vettoriale con un filtro. L'indice vettoriale TechDocEmbeddingIndex indicizza solo i documenti della categoria "Tecnologia".

CREATE VECTOR INDEX TechDocEmbeddingIndex
  ON Documents(DocEmbedding)
  STORING(NullIfFiltered)
  WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
  OPTIONS (...);

Quando Spanner esegue la seguente query, che ha filtri che corrispondono a TechDocEmbeddingIndex, sceglie automaticamente ed è accelerata da TechDocEmbeddingIndex. La query cerca solo documenti nella categoria "Tecnologia". Puoi anche utilizzare {@FORCE_INDEX=TechDocEmbeddingIndex} per forzare Spanner a utilizzare TechDocEmbeddingIndex in modo esplicito.

SELECT *
FROM Documents
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
ORDER BY APPROX_(....)
LIMIT 10;

Eseguire query sugli incorporamenti vettoriali

Per eseguire una query su un indice vettoriale, utilizza una delle tre funzioni di distanza approssimata:

  • APPROX_COSINE_DISTANCE
  • APPROX_EUCLIDEAN_DISTANCE
  • APPROX_DOT_PRODUCT

Le limitazioni quando si utilizzano le funzioni di distanza approssimativa includono quanto segue:

  • La funzione di distanza approssimativa deve calcolare la distanza tra una colonna di incorporamento e un'espressione costante (ad esempio, un parametro o un valore letterale).
  • L'output della funzione di distanza approssimativa deve essere utilizzato in una clausola ORDER BY come unica chiave di ordinamento e dopo ORDER BY deve essere specificato un LIMIT.
  • La query deve filtrare esplicitamente le righe non indicizzate. Nella maggior parte dei casi, ciò significa che la query deve includere una clausola WHERE <column_name> IS NOT NULL che corrisponda alla definizione dell'indice vettoriale, a meno che la colonna non sia già contrassegnata come NOT NULL nella definizione della tabella.

Per un elenco dettagliato delle limitazioni, consulta la pagina di riferimento della funzione di distanza approssimativa.

Esempi

Per cercare i 100 vettori più vicini a [1.0, 2.0, 3.0]:

SELECT DocId
FROM Documents
WHERE WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
  ARRAY<FLOAT32>[1.0, 2.0, 3.0], DocEmbedding,
  options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100

Se la colonna di embedding ammette valori nulli:

SELECT DocId
FROM Documents
WHERE NullableDocEmbedding IS NOT NULL AND WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
  ARRAY<FLOAT32>[1.0, 2.0, 3.0], NullableDocEmbedding,
  options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100

Best practice

Segui queste best practice per ottimizzare gli indici vettoriali e migliorare i risultati delle query.

Ottimizza le opzioni di ricerca vettoriale

Il valore di ricerca vettoriale più ottimale dipende dal caso d'uso, dal set di dati vettoriali e dai vettori di query. Potresti dover eseguire un'ottimizzazione iterativa per trovare i valori migliori per il tuo carico di lavoro specifico.

Ecco alcune linee guida utili da seguire quando scegli i valori appropriati:

  • tree_depth (livello ad albero): se la tabella da indicizzare ha meno di 10 milioni di righe, utilizza un tree_depth di 2. In caso contrario, un tree_depth di 3 supporta tabelle con un massimo di circa 10 miliardi di righe.

  • num_leaves: utilizza la radice quadrata del numero di righe nel set di dati. Un valore maggiore può aumentare il tempo di creazione dell'indice vettoriale. Evita di impostare num_leaves maggiore di table_row_count/1000, in quanto ciò comporta foglie troppo piccole e un rendimento scarso.

  • num_leaves_to_search: questa opzione specifica il numero di nodi foglia dell'indice che vengono cercati. L'aumento di num_leaves_to_search migliora il richiamo, ma aumenta anche la latenza e i costi. Ti consigliamo di utilizzare un numero pari all'1% del numero totale di foglie definito nell'istruzione CREATE VECTOR INDEX come valore per num_leaves_to_search. Se utilizzi una clausola di filtro, aumenta questo valore per ampliare la ricerca.

Se si ottiene un richiamo accettabile, ma il costo delle query è troppo elevato, con conseguente basso QPS massimo, prova ad aumentare num_leaves seguendo questi passaggi:

  1. Imposta num_leaves su un multiplo k del suo valore originale (ad esempio, 2 * sqrt(table_row_count)).
  2. Imposta num_leaves_to_search in modo che sia lo stesso multiplo k del suo valore originale.
  3. Prova a ridurre num_leaves_to_search per migliorare il costo e le QPS mantenendo il richiamo.

Migliorare il ricordo

Esistono diverse possibilità di peggioramento del richiamo, tra cui:

  • num_leaves_to_search è troppo piccolo: potresti avere più difficoltà a trovare i vicini più prossimi per alcuni vettori di query, quindi aumentare num_leaves_to_search per cercare più foglie può contribuire a migliorare il richiamo. Le query recenti potrebbero essere cambiate per includere un numero maggiore di questi vettori difficili.

  • L'indice vettoriale deve essere ricreato: la struttura ad albero dell'indice vettoriale è ottimizzata per il set di dati al momento della creazione ed è statica in seguito. Pertanto, se vengono aggiunti vettori significativamente diversi dopo la creazione dell'indice vettoriale iniziale, la struttura ad albero potrebbe non essere ottimale, con conseguente richiamo inferiore.

Ricrea l'indice vettoriale

Per ricompilare l'indice vettoriale senza tempi di inattività:

  1. Crea un nuovo indice vettoriale sulla stessa colonna di incorporamento dell'indice vettoriale attuale, aggiornando i parametri (ad esempio OPTIONS) in modo appropriato.
  2. Una volta completata la creazione dell'indice, utilizza il suggerimento FORCE_INDEX per indicare il nuovo indice per aggiornare la query di ricerca vettoriale. In questo modo la query utilizza il nuovo indice vettoriale. Potresti anche dover eseguire di nuovo l'ottimizzazione num_leaves_to_search nella nuova query.
  3. Elimina l'indice vettoriale obsoleto.

Passaggi successivi