Ricerca con incorporamenti vettoriali

La pagina mostra come utilizzare Firestore per eseguire il comando K-più vicino le ricerche del vettore di vicini (KNN) utilizzando le seguenti tecniche:

  • Archivia i valori dei vettori
  • Crea e gestisci indici vettoriali KNN
  • Esegui una query K-Neighbor (KNN) utilizzando uno dei vettori supportati misure di distanza

Archivia gli incorporamenti vettoriali

Puoi creare valori vettoriali come gli incorporamenti di testo dal tuo Firestore e archiviali in documenti Firestore.

Operazione di scrittura con un incorporamento vettoriale

L'esempio riportato di seguito mostra come archiviare un embedding vettoriale in un documento Firestore:

Python
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector

firestore_client = firestore.Client()
collection = firestore_client.collection("coffee-beans")
doc = {
    "name": "Kahawa coffee beans",
    "description": "Information about the Kahawa coffee beans.",
    "embedding_field": Vector([1.0, 2.0, 3.0]),
}

collection.add(doc)
Node.js
import {
  Firestore,
  FieldValue,
} from "@google-cloud/firestore";

const db = new Firestore();
const coll = db.collection('coffee-beans');
await coll.add({
  name: "Kahawa coffee beans",
  description: "Information about the Kahawa coffee beans.",
  embedding_field: FieldValue.vector([1.0 , 2.0, 3.0])
});

Calcola gli incorporamenti vettoriali con una Cloud Function

Per calcolare e archiviare gli incorporamenti vettoriali ogni volta che un documento viene aggiornato puoi configurare una funzione Cloud Run:

Python
@functions_framework.cloud_event
def store_embedding(cloud_event) -> None:
  """Triggers by a change to a Firestore document.
  """
  firestore_payload = firestore.DocumentEventData()
  payload = firestore_payload._pb.ParseFromString(cloud_event.data)

  collection_id, doc_id = from_payload(payload)
  # Call a function to calculate the embedding
  embedding = calculate_embedding(payload)
  # Update the document
  doc = firestore_client.collection(collection_id).document(doc_id)
  doc.set({"embedding_field": embedding}, merge=True)
Node.js
/**
 * A vector embedding will be computed from the
 * value of the `content` field. The vector value
 * will be stored in the `embedding` field. The
 * field names `content` and `embedding` are arbitrary
 * field names chosen for this example.
 */
async function storeEmbedding(event: FirestoreEvent<any>): Promise<void> {
  // Get the previous value of the document's `content` field.
  const previousDocumentSnapshot = event.data.before as QueryDocumentSnapshot;
  const previousContent = previousDocumentSnapshot.get("content");

  // Get the current value of the document's `content` field.
  const currentDocumentSnapshot = event.data.after as QueryDocumentSnapshot;
  const currentContent = currentDocumentSnapshot.get("content");

  // Don't update the embedding if the content field did not change
  if (previousContent === currentContent) {
    return;
  }

  // Call a function to calculate the embedding for the value
  // of the `content` field.
  const embeddingVector = calculateEmbedding(currentContent);

  // Update the `embedding` field on the document.
  await currentDocumentSnapshot.ref.update({
    embedding: embeddingVector,
  });
}

Crea e gestisci indici vettoriali

Prima di poter eseguire una ricerca per il vicino più prossimo con gli incorporamenti vettoriali, devi creare un indice corrispondente. Gli esempi riportati di seguito mostrano come creare e gestire gli indici di vettori.

Creare un indice di vettori

Prima di creare un indice vettoriale, esegui l'upgrade alla versione più recente di Google Cloud CLI:

gcloud components update

Per creare un indice vettoriale, utilizza gcloud firestore indexes composite create:

gcloud
gcloud firestore indexes composite create \
--collection-group=collection-group \
--query-scope=COLLECTION \
--field-config field-path=vector-field,vector-config='vector-configuration' \
--database=database-id

dove:

  • collection-group è l'ID del gruppo di raccolte.
  • vector-field è il nome del campo che contiene l'incorporamento vettoriale.
  • database-id è l'ID del database.
  • vector-configuration include il vettore dimension e il tipo di indice. dimension è un numero intero fino a 2048. Il tipo di indice deve essere flat. Formatta la configurazione dell'indice come segue: {"dimension":"DIMENSION", "flat": "{}"}.

L'esempio seguente crea un indice composto, incluso un indice vettoriale per il campo vector-field e un indice crescente per il campo color. Puoi utilizzare questo tipo di indice per pre-filtrare dati prima della ricerca del vicino più prossimo.

gcloud
gcloud firestore indexes composite create \
--collection-group=collection-group \
--query-scope=COLLECTION \
--field-config=order=ASCENDING,field-path="color" \
--field-config field-path=vector-field,vector-config='{"dimension":"1024", "flat": "{}"}' \
--database=database-id

Elenca tutti gli indici vettoriali

gcloud
gcloud firestore indexes composite list --database=database-id

Sostituisci database-id con l'ID del database.

Eliminare un indice di vettori

gcloud
gcloud firestore indexes composite delete index-id --database=database-id

dove:

  • index-id è l'ID dell'indice da eliminare. Utilizza indexes composite list per recuperare l'ID indice.
  • database-id è l'ID del database.

Descrivere un indice vettoriale

gcloud
gcloud firestore indexes composite describe index-id --database=database-id

dove:

  • index-id è l'ID dell'indice da descrivere. Utilizza o indexes composite list per recuperare l'ID indice.
  • database-id è l'ID del database.

Eseguire una query per il vicino più prossimo

Puoi eseguire una ricerca di somiglianza per trovare i vicini più prossimi di una all'incorporamento vettoriale. Le ricerche di analogie richiedono indici vettoriali. Se non esiste un indice, Firestore suggerisce un indice da creare utilizzando gcloud CLI.

Il seguente esempio trova i 10 vicini più prossimi del vettore di query.

Python
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

collection = db.collection("coffee-beans")

# Requires a single-field vector index
vector_query = collection.find_nearest(
    vector_field="embedding_field",
    query_vector=Vector([3.0, 1.0, 2.0]),
    distance_measure=DistanceMeasure.EUCLIDEAN,
    limit=5,
)
Node.js
import {
  Firestore,
  FieldValue,
  VectorQuery,
  VectorQuerySnapshot,
} from "@google-cloud/firestore";

// Requires a single-field vector index
const vectorQuery: VectorQuery = coll.findNearest({
  vectorField: 'embedding_field',
  queryVector: [3.0, 1.0, 2.0],
  limit: 10,
  distanceMeasure: 'EUCLIDEAN'
});

const vectorQuerySnapshot: VectorQuerySnapshot = await vectorQuery.get();

Distanze vettoriali

Le query per il vicino più vicino supportano le seguenti opzioni per la distanza vettoriale:

  • EUCLIDEAN: misura la distanza EUCLIDEA tra i vettori. Per saperne di più, vedi Euclidea.
  • COSINE: confronta i vettori in base all'angolo tra di loro, consentendoti di misurare la somiglianza che non si basa sulla grandezza del vettore. Ti consigliamo di utilizzare DOT_PRODUCT con vettori normalizzati dell'unità anziché la distanza COSINE, che è matematicamente equivalente con una le prestazioni dei dispositivi. Per saperne di più, vedi Somiglianza della coseno da imparare altro ancora.
  • DOT_PRODUCT: simile a COSINE, ma è influenzata dalla grandezza della vettori di rete. Per saperne di più, vedi Prodotto Dot.

Scegli la misura della distanza

A seconda che tutti gli embedding vettoriali siano o meno normalizzati, puoi determinare quale misura della distanza utilizzare per trovare la misura della distanza. Un modello l'incorporamento vettoriale ha una grandezza (lunghezza) esattamente 1,0.

Inoltre, se conosci la misura della distanza con cui è stato addestrato il modello, utilizzala per calcolare la distanza tra gli embedding vettore.

Dati normalizzati

Se si dispone di un set di dati in cui tutti gli incorporamenti vettoriali sono normalizzati, le misure di distanza forniscono gli stessi risultati di ricerca semantici. In sostanza, anche se ogni distanza misura restituisce un valore diverso, i valori vengono ordinati nello stesso modo. Quando gli embedding sono normalizzati, DOT_PRODUCT è in genere il più efficiente dal punto di vista computazionale, ma la differenza è trascurabile nella maggior parte dei casi. Tuttavia, se le tue è molto sensibile alle prestazioni, DOT_PRODUCT potrebbe aiutarti con dell'ottimizzazione delle prestazioni.

Dati non normalizzati

Se hai un set di dati in cui gli incorporamenti vettoriali non sono normalizzati, allora non è matematicamente corretto usare DOT_PRODUCT come distanza misurare perché il prodotto scalare non misura la distanza. A seconda su come sono stati generati gli incorporamenti e sul tipo di ricerca preferito, la misura della distanza COSINE o EUCLIDEAN produce risultati di ricerca soggettivamente migliori rispetto alle altre misure di distanza. La sperimentazione con COSINE o EUCLIDEAN potrebbe per stabilire qual è la migliore per il tuo caso d'uso.

Non so se i dati sono normalizzati o non normalizzati

Se non sai con certezza se i tuoi dati sono normalizzati e vuoi utilizzare DOT_PRODUCT, ti consigliamo di utilizzare COSINE. COSINE è come DOT_PRODUCT con la normalizzazione integrata. La distanza misurata utilizzando COSINE va da 0 a 2. Un risultato un valore vicino a 0 indica che i vettori sono molto simili.

Prefiltra i documenti

Per prefiltrare i documenti prima di trovare i vicini più vicini, puoi combinare una ricerca di somiglianza con altri operatori di query. Le and e Sono supportati or filtri compositi. Per ulteriori informazioni sui filtri dei campi supportati, consulta Operatori di query.

Python
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

collection = db.collection("coffee-beans")

# Similarity search with pre-filter
# Requires a composite vector index
vector_query = collection.where("color", "==", "red").find_nearest(
    vector_field="embedding_field",
    query_vector=Vector([3.0, 1.0, 2.0]),
    distance_measure=DistanceMeasure.EUCLIDEAN,
    limit=5,
)
Node.js
// Similarity search with pre-filter
// Requires composite vector index
const preFilteredVectorQuery: VectorQuery = coll
    .where("color", "==", "red")
    .findNearest({
      vectorField: "embedding_field",
      queryVector: [3.0, 1.0, 2.0],
      limit: 5,
      distanceMeasure: "EUCLIDEAN",
    });

const vectorQueryResults = await preFilteredVectorQuery.get();

Recupera la distanza vettoriale calcolata

Puoi recuperare la distanza vettoriale calcolata assegnando una distance_result_field nome della proprietà di output nella query FindNearest, come come mostrato nell'esempio seguente:

Python
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

collection = db.collection("coffee-beans")

vector_query = collection.find_nearest(
    vector_field="embedding_field",
    query_vector=Vector([3.0, 1.0, 2.0]),
    distance_measure=DistanceMeasure.EUCLIDEAN,
    limit=10,
    distance_result_field="vector_distance",
)

docs = vector_query.stream()

for doc in docs:
    print(f"{doc.id}, Distance: {doc.get('vector_distance')}")
Node.js
const vectorQuery: VectorQuery = coll.findNearest(
    {
      vectorField: 'embedding_field',
      queryVector: [3.0, 1.0, 2.0],
      limit: 10,
      distanceMeasure: 'EUCLIDEAN',
      distanceResultField: 'vector_distance'
    });

const snapshot: VectorQuerySnapshot = await vectorQuery.get();

snapshot.forEach((doc) => {
  console.log(doc.id, ' Distance: ', doc.get('vector_distance'));
});

Se vuoi utilizzare una maschera di campo per restituire un sottoinsieme di campi del documento insieme a un distanceResultField, devi includere anche il valore distanceResultField nella maschera del campo, come mostrato nell'esempio seguente:

Python
vector_query = collection.select(["color", "vector_distance"]).find_nearest(
    vector_field="embedding_field",
    query_vector=Vector([3.0, 1.0, 2.0]),
    distance_measure=DistanceMeasure.EUCLIDEAN,
    limit=10,
    distance_result_field="vector_distance",
)
Node.js
const vectorQuery: VectorQuery = coll
    .select('color', 'vector_distance')
    .findNearest({
      vectorField: 'embedding_field',
      queryVector: [3.0, 1.0, 2.0],
      limit: 10,
      distanceMeasure: 'EUCLIDEAN',
      distanceResultField: 'vector_distance'
    });

Specifica una soglia di distanza

Puoi specificare una soglia di somiglianza che restituisce solo i documenti all'interno di soglia. Il comportamento del campo della soglia dipende dalla misura della distanza scegli:

  • Le distanze EUCLIDEAN e COSINE limitano la soglia ai documenti in cui la distanza è minore o uguale alla soglia specificata. Queste distanze diminuiscono man mano che i vettori diventano più simili.
  • La distanza DOT_PRODUCT limita la soglia ai documenti in cui la distanza è maggiore o uguale alla soglia specificata. Distanze prodotto scalare aumentano man mano che i vettori diventano più simili.

L'esempio seguente mostra come specificare una soglia di distanza per restituire fino a 10 documenti più vicini che si trovano a una distanza massima di 4,5 unità utilizzando la metrica di distanza EUCLIDEAN:

Python
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

collection = db.collection("coffee-beans")

vector_query = collection.find_nearest(
    vector_field="embedding_field",
    query_vector=Vector([3.0, 1.0, 2.0]),
    distance_measure=DistanceMeasure.EUCLIDEAN,
    limit=10,
    distance_threshold=4.5,
)

docs = vector_query.stream()

for doc in docs:
    print(f"{doc.id}")
Node.js
const vectorQuery: VectorQuery = coll.findNearest({
  vectorField: 'embedding_field',
  queryVector: [3.0, 1.0, 2.0],
  limit: 10,
  distanceMeasure: 'EUCLIDEAN',
  distanceThreshold: 4.5
});

const snapshot: VectorQuerySnapshot = await vectorQuery.get();

snapshot.forEach((doc) => {
  console.log(doc.id);
});

Limitazioni

Quando lavori con gli incorporamenti vettoriali, tieni presente le seguenti limitazioni:

  • La dimensione di incorporamento massima supportata è 2048. Per archiviare indici più grandi, utilizza riduzione della dimensionalità.
  • Il numero massimo di documenti da restituire da una query al vicino più prossimo è 1000.
  • La ricerca vettoriale non supporta i listener di snapshot in tempo reale.
  • Solo le librerie client Python e Node.js supportano la ricerca vettoriale.

Passaggi successivi