Buscar con embeddings de vectores
En esta página, se muestra cómo usar Firestore para realizar operaciones K-cerca de vectores vecino (KNN) con las siguientes técnicas:
- Almacena valores vectoriales
- Crea y administra índices de vectores de KNN
- Haz una consulta de K-vecino más cercano (KNN) mediante una de las medidas de distancia compatibles con vectores
Almacena embeddings de vectores
Puedes crear valores vectoriales como incorporaciones de texto a partir de tu y almacenarlos en documentos de Firestore.
Operación de escritura con una embedding de vector
En el siguiente ejemplo, se muestra cómo almacenar una incorporación vectorial en un Documento de Firestore:
Python
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]) });
Go
Java
import com.google.cloud.firestore.CollectionReference; import com.google.cloud.firestore.DocumentReference; import com.google.cloud.firestore.FieldValue; import com.google.cloud.firestore.VectorQuery; CollectionReference coll = firestore.collection("coffee-beans"); Map<String, Object> docData = new HashMap<>(); docData.put("name", "Kahawa coffee beans"); docData.put("description", "Information about the Kahawa coffee beans."); docData.put("embedding_field", FieldValue.vector(new double[] {1.0, 2.0, 3.0})); ApiFuture<DocumentReference> future = coll.add(docData); DocumentReference documentReference = future.get();
Calcula incorporaciones vectoriales con una Cloud Function
Para calcular y almacenar incorporaciones vectoriales cada vez que se actualiza o crea un documento puedes configurar una función de 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, }); }
Go
// Not yet supported in the Go client library
Java
// Not yet supported in the Java client library
Crea y administra índices vectoriales
Antes de realizar una búsqueda de vecino más cercano con tus incorporaciones vectoriales, debes crear un índice correspondiente. En los siguientes ejemplos, se muestran cómo crear y administrar índices vectoriales.
Crea un índice vectorial
Antes de crear un índice vectorial, actualiza a la versión más reciente de Google Cloud CLI:
gcloud components update
Para crear un índice de vectores, usa
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
donde:
- collection-group es el ID del grupo de colecciones.
- vector-field es el nombre del campo que contiene la embedding de vector.
- database-id es el ID de la base de datos.
- vector-configuration incluye el vector
dimension
y el tipo de índice.dimension
es un número entero hasta 2,048. El tipo de índice debe serflat
. Da formato a la configuración del índice de la siguiente manera:{"dimension":"DIMENSION", "flat": "{}"}
.
En el siguiente ejemplo, se crea un índice compuesto, que incluye un índice de vectores para el campo vector-field
y un índice ascendente para el campo color
. Puedes usar este tipo de índice para realizar un filtro previo de datos
antes de buscar un vecino más cercano.
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
Enumerar todos los índices vectoriales
gcloud
gcloud firestore indexes composite list --database=database-id
Reemplaza database-id por el ID de la base de datos.
Borra un índice vectorial
gcloud
gcloud firestore indexes composite delete index-id --database=database-id
donde:
- index-id es el ID del índice que se borrará.
Usa
indexes composite list
para recuperar el ID del índice. - database-id es el ID de la base de datos.
Describir un índice vectorial
gcloud
gcloud firestore indexes composite describe index-id --database=database-id
donde:
- index-id es el ID del índice que se describirá. Usar o
indexes composite list
para recuperar el ID del índice. - database-id es el ID de la base de datos.
Haz una consulta de vecino más cercano
Puedes realizar una búsqueda de similitud para encontrar los vecinos más cercanos de la embedding de vector. Las búsquedas de similitud requieren índices vectoriales. Si no existe un índice, Firestore sugiere que crees un índice con gcloud CLI.
En el siguiente ejemplo, se encuentran los 10 vecinos más cercanos del vector de consulta.
Python
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();
Go
Java
import com.google.cloud.firestore.VectorQuery; import com.google.cloud.firestore.VectorQuerySnapshot; VectorQuery vectorQuery = coll.findNearest( "embedding_field", new double[] {3.0, 1.0, 2.0}, /* limit */ 10, VectorQuery.DistanceMeasure.EUCLIDEAN); ApiFuture<VectorQuerySnapshot> future = vectorQuery.get(); VectorQuerySnapshot vectorQuerySnapshot = future.get();
Distancias vectoriales
Las consultas de vecino más cercano admiten las siguientes opciones para la distancia vectorial:
EUCLIDEAN
: Mide la distancia de EUCLIDEAN entre los vectores. Para obtener más información, consulta Euclidea.COSINE
: Compara vectores según el ángulo entre ellos, lo que te permite medir la similitud que no se basa en la magnitud de los vectores. Recomendamos usarDOT_PRODUCT
con vectores normalizados de unidades en lugar de la distancia de COSINE, que es matemáticamente equivalente con un mejor rendimiento. Para obtener más información, consulta Similitud coseno para obtener más información.DOT_PRODUCT
: Es similar aCOSINE
, pero se ve afectado por la magnitud de laos vectores. Para obtener más información, consulta Producto de punto.
Elige la medida de distancia
Según si todas tus embeddings de vectores están normalizadas o no, puedes determinar qué medida de distancia usar para encontrar la medida de distancia. Una embedding de vector normalizada tiene una magnitud (longitud) de exactamente 1.0.
Además, si sabes con qué medida de distancia se entrenó tu modelo, usa esa medida para calcular la distancia entre tus embeddings de vectores.
Datos normalizados
Si tienes un conjunto de datos en el que todas las embeddings de vectores están normalizadas, las tres medidas de distancia proporcionan los mismos resultados de la búsqueda semántica. En esencia, aunque cada medida de distancia muestra un valor diferente, esos valores se ordenan de la misma manera. Cuando las embeddings se normalizan, DOT_PRODUCT
suele ser la más eficiente en términos de procesamiento, pero la diferencia es despreciable en la mayoría de los casos. Sin embargo, si tu aplicación es muy sensible al rendimiento, DOT_PRODUCT
podría ayudarte a ajustarlo.
Datos no normalizados
Si tienes un conjunto de datos en el que las embedding de vector no están normalizadas, no es matemáticamente correcto usar DOT_PRODUCT
como medida de distancia, porque el producto punto no mide la distancia. Según cómo se generaron las embeddings y el tipo de búsqueda que se prefiera, la medida de distancia COSINE
o EUCLIDEAN
produce resultados de búsqueda que son subjetivamente mejores que las otras medidas de distancia.
Es posible que sea necesario experimentar con COSINE
o EUCLIDEAN
para determinar cuál es la mejor opción para tu caso de uso.
No sabes si los datos están normalizados o no
Si no sabes si tus datos están normalizados y quieres usar DOT_PRODUCT
, te recomendamos que uses COSINE
.
COSINE
es como DOT_PRODUCT
con la normalización integrada.
La distancia medida con COSINE
varía de 0
a 2
. Un resultado cercano a 0
indica que los vectores son muy similares.
Cómo aplicar un filtro previo a los documentos
Para filtrar previamente los documentos antes de encontrar los vecinos más cercanos, puedes combinar
la búsqueda de similitud con otros operadores de consulta. Se admiten los filtros compuestos and
y or
. Para obtener más información sobre los filtros de campo admitidos, consulta Operadores de consulta.
Python
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();
Go
Java
import com.google.cloud.firestore.VectorQuery; import com.google.cloud.firestore.VectorQuerySnapshot; VectorQuery preFilteredVectorQuery = coll .whereEqualTo("color", "red") .findNearest( "embedding_field", new double[] {3.0, 1.0, 2.0}, /* limit */ 10, VectorQuery.DistanceMeasure.EUCLIDEAN); ApiFuture<VectorQuerySnapshot> future = preFilteredVectorQuery.get(); VectorQuerySnapshot vectorQuerySnapshot = future.get();
Cómo recuperar la distancia del vector calculada
Para recuperar la distancia vectorial calculada, asigna un nombre de propiedad de salida distance_result_field
en la consulta FindNearest
, como se muestra en el siguiente ejemplo:
Python
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')); });
Go
Java
import com.google.cloud.firestore.VectorQuery; import com.google.cloud.firestore.VectorQueryOptions; import com.google.cloud.firestore.VectorQuerySnapshot; VectorQuery vectorQuery = coll.findNearest( "embedding_field", new double[] {3.0, 1.0, 2.0}, /* limit */ 10, VectorQuery.DistanceMeasure.EUCLIDEAN, VectorQueryOptions.newBuilder().setDistanceResultField("vector_distance").build()); ApiFuture<VectorQuerySnapshot> future = vectorQuery.get(); VectorQuerySnapshot vectorQuerySnapshot = future.get(); for (DocumentSnapshot document : vectorQuerySnapshot.getDocuments()) { System.out.println(document.getId() + " Distance: " + document.get("vector_distance")); }
Si deseas usar una máscara de campo para mostrar un subconjunto de campos de documentos junto con un distanceResultField
, también debes incluir el valor de distanceResultField
en la máscara de campo, como se muestra en el siguiente ejemplo:
Python
Node.js
const vectorQuery: VectorQuery = coll .select('name', 'description', 'vector_distance') .findNearest({ vectorField: 'embedding_field', queryVector: [3.0, 1.0, 2.0], limit: 10, distanceMeasure: 'EUCLIDEAN', distanceResultField: 'vector_distance' });
Go
Java
import com.google.cloud.firestore.VectorQuery; import com.google.cloud.firestore.VectorQueryOptions; import com.google.cloud.firestore.VectorQuerySnapshot; VectorQuery vectorQuery = coll .select("name", "description", "vector_distance") .findNearest( "embedding_field", new double[] {3.0, 1.0, 2.0}, /* limit */ 10, VectorQuery.DistanceMeasure.EUCLIDEAN, VectorQueryOptions.newBuilder() .setDistanceResultField("vector_distance") .build()); ApiFuture<VectorQuerySnapshot> future = vectorQuery.get(); VectorQuerySnapshot vectorQuerySnapshot = future.get(); for (DocumentSnapshot document : vectorQuerySnapshot.getDocuments()) { System.out.println(document.getId() + " Distance: " + document.get("vector_distance")); }
Especifica un umbral de distancia
Puedes especificar un umbral de similitud que muestre solo los documentos dentro del umbral. El comportamiento del campo de umbral depende de la medida de distancia que elijas:
- Las distancias
EUCLIDEAN
yCOSINE
limitan el umbral a los documentos en los que la distancia es menor o igual al umbral especificado. Estas medidas de distancia disminuyen a medida que los vectores se vuelven más similares. - La distancia
DOT_PRODUCT
limita el umbral a los documentos en los que la distancia es mayor o igual al umbral especificado. Las distancias del producto punto aumentan a medida que los vectores se vuelven más similares.
En el siguiente ejemplo, se muestra cómo especificar un umbral de distancia para mostrar hasta 10 documentos más cercanos que estén, como máximo, a 4.5 unidades de distancia con la métrica de distancia EUCLIDEAN
:
Python
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); });
Go
Java
import com.google.cloud.firestore.VectorQuery; import com.google.cloud.firestore.VectorQueryOptions; import com.google.cloud.firestore.VectorQuerySnapshot; VectorQuery vectorQuery = coll.findNearest( "embedding_field", new double[] {3.0, 1.0, 2.0}, /* limit */ 10, VectorQuery.DistanceMeasure.EUCLIDEAN, VectorQueryOptions.newBuilder() .setDistanceThreshold(4.5) .build()); ApiFuture<VectorQuerySnapshot> future = vectorQuery.get(); VectorQuerySnapshot vectorQuerySnapshot = future.get(); for (DocumentSnapshot document : vectorQuerySnapshot.getDocuments()) { System.out.println(document.getId()); }
Limitaciones
Cuando trabajes con incorporaciones vectoriales, ten en cuenta las siguientes limitaciones:
- La dimensión de incorporación máxima admitida es 2,048. Para almacenar índices más grandes, usa reducción de dimensiones.
- La cantidad máxima de documentos que se pueden mostrar con una consulta de vecino más cercano es de 1,000.
- La búsqueda vectorial no admite objetos de escucha de instantáneas en tiempo real.
- Solo las bibliotecas cliente Python, Node.js, Go y Java admiten la búsqueda de vectores.
¿Qué sigue?
- Obtén información sobre las prácticas recomendadas para Firestore.
- Comprende las operaciones de lectura y escritura a gran escala.