Pesquise com incorporações vetoriais
Esta página mostra como usar o Firestore para realizar pesquisas de vetores de K-vizinhos mais próximos (KNN) através das seguintes técnicas:
- Armazene valores vetoriais
- Crie e faça a gestão de índices de vetores KNN
- Faça uma consulta de vizinho mais próximo (KNN) usando uma das medidas de distância vetorial suportadas
Antes de começar
Antes de armazenar incorporações no Firestore, tem de gerar incorporações de vetores. O Firestore não gera as incorporações. Pode usar um serviço como o Vertex AI para criar valores vetoriais, por exemplo, incorporações de texto a partir dos seus dados do Firestore. Em seguida, pode armazenar estas incorporações novamente em documentos do Firestore.
Para saber mais sobre incorporações, consulte o artigo O que são incorporações?
Para saber como obter incorporações de texto com a Vertex AI, consulte o artigo Obtenha incorporações de texto.
Armazene incorporações vetoriais
Os exemplos seguintes demonstram como armazenar incorporações vetoriais no Firestore.
Operação de escrita com uma incorporação de vetor
O exemplo seguinte mostra como armazenar uma incorporação vetorial num documento do 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();
Calcule incorporações vetoriais com uma função do Cloud
Para calcular e armazenar incorporações de vetores sempre que um documento é atualizado ou criado, pode configurar uma função do 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
Crie e faça a gestão de índices vetoriais
Antes de poder realizar uma pesquisa de vizinho mais próximo com as suas incorporações de vetores, tem de criar um índice correspondente. Os exemplos seguintes demonstram como criar e gerir índices vetoriais com a CLI do Google Cloud. Os índices vetoriais também podem ser geridos com a Firebase CLI e o Terraform.
Crie um índice vetorial
Antes de criar um índice vetorial, atualize para a versão mais recente da Google Cloud CLI:
gcloud components update
Para criar um índice vetorial, use 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
where:
- collection-group é o ID do grupo de coleções.
- vector-field é o nome do campo que contém a incorporação de vetores.
- database-id é o ID da base de dados.
- vector-configuration inclui o vetor
dimension
e o tipo de índice. Odimension
é um número inteiro até 2048. O tipo de índice tem de serflat
. Formate a configuração do índice da seguinte forma:{"dimension":"DIMENSION", "flat": "{}"}
.
O exemplo seguinte cria um índice composto, incluindo um índice vetorial para o campo vector-field
e um índice ascendente para o campo color
. Pode usar este tipo de índice para pré-filtrar
dados antes de uma pesquisa de vizinhos mais próximos.
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
Apresentar todos os índices vetoriais
gcloud
gcloud firestore indexes composite list --database=database-id
Substitua database-id pelo ID da base de dados.
Elimine um índice vetorial
gcloud
gcloud firestore indexes composite delete index-id --database=database-id
where:
- index-id é o ID do índice a eliminar.
Use
indexes composite list
para obter o ID do índice. - database-id é o ID da base de dados.
Descreva um índice de vetores
gcloud
gcloud firestore indexes composite describe index-id --database=database-id
where:
- index-id é o ID do índice a descrever. Use
indexes composite list
para obter o ID do índice. - database-id é o ID da base de dados.
Faça uma consulta de vizinho mais próximo
Pode fazer uma pesquisa de similaridade para encontrar os vizinhos mais próximos de uma incorporação de vetores. As pesquisas de similaridade requerem índices vetoriais. Se não existir um índice, o Firestore sugere um índice a criar através da CLI gcloud.
O exemplo seguinte encontra os 10 vizinhos mais próximos do vetor 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();
Distâncias vetoriais
As consultas de vizinho mais próximo suportam as seguintes opções para a distância vetorial:
EUCLIDEAN
: mede a distância EUCLIDEAN entre os vetores. Para saber mais, consulte o artigo Euclidiana.COSINE
: compara vetores com base no ângulo entre eles, o que lhe permite medir a semelhança que não se baseia na magnitude dos vetores. Recomendamos que useDOT_PRODUCT
com vetores normalizados por unidade em vez da distância COSINE, que é matematicamente equivalente com um melhor desempenho. Para saber mais, consulte o artigo Similaridade de cossenos para saber mais.DOT_PRODUCT
: semelhante aCOSINE
, mas é afetado pela magnitude dos vetores. Para saber mais, consulte o artigo Produto escalar.
Escolha a medida de distância
Consoante todas as suas incorporações vetoriais estejam ou não normalizadas, pode determinar que medida de distância usar para encontrar a medida de distância. Uma incorporação de vetor normalizada tem uma magnitude (comprimento) de exatamente 1,0.
Além disso, se souber com que medida de distância o seu modelo foi preparado, use essa medida de distância para calcular a distância entre as suas incorporações de vetores.
Dados normalizados
Se tiver um conjunto de dados em que todas as incorporações de vetores estão normalizadas, todas as três medidas de distância fornecem os mesmos resultados de pesquisa semântica. Essencialmente, embora cada medida de distância devolva um valor diferente, esses valores são ordenados da mesma forma. Quando as incorporações são normalizadas, DOT_PRODUCT
é geralmente a mais eficiente do ponto de vista computacional, mas a diferença é insignificante na maioria dos casos. No entanto, se a sua aplicação for muito sensível ao desempenho, DOT_PRODUCT
pode ajudar a otimizar o desempenho.
Dados não normalizados
Se tiver um conjunto de dados em que as incorporações de vetores não estão normalizadas,
não é matematicamente correto usar DOT_PRODUCT
como uma medida de distância, porque o produto escalar não mede a distância. Consoante a forma como as incorporações foram geradas e o tipo de pesquisa preferido, a medida de distância COSINE
ou EUCLIDEAN
produz resultados da pesquisa que são subjetivamente melhores do que as outras medidas de distância.
A experimentação com COSINE
ou EUCLIDEAN
pode ser necessária para determinar qual é a melhor opção para o seu exemplo de utilização.
Não sabe se os dados são normalizados ou não normalizados
Se não tiver a certeza se os seus dados estão normalizados e quiser usar
DOT_PRODUCT
, recomendamos que use COSINE
.
COSINE
é semelhante a DOT_PRODUCT
com a normalização incorporada.
A distância medida com COSINE
varia entre 0
e 2
. Um resultado
próximo de 0
indica que os vetores são muito semelhantes.
Pré-filtre documentos
Para pré-filtrar documentos antes de encontrar os vizinhos mais próximos, pode combinar uma pesquisa de similaridade com outros operadores de consulta. Os filtros compostos and
e or
são suportados. Para mais informações sobre os filtros de campos suportados, consulte o artigo 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();
Recupere a distância vetorial calculada
Pode obter a distância vetorial calculada atribuindo um nome de propriedade de saída distance_result_field
na consulta FindNearest
, conforme mostrado no exemplo seguinte:
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")); }
Se quiser usar uma máscara de campo para devolver um subconjunto de campos de documentos juntamente com um distanceResultField
, também tem de incluir o valor de distanceResultField
na máscara de campo, conforme mostrado no exemplo seguinte:
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")); }
Especifique um limite de distância
Pode especificar um limite de semelhança que devolve apenas documentos dentro do limite. O comportamento do campo de limite depende da medida de distância que escolher:
- As distâncias
EUCLIDEAN
eCOSINE
limitam o limite aos documentos em que a distância é inferior ou igual ao limite especificado. Estas medidas de distância diminuem à medida que os vetores se tornam mais semelhantes. DOT_PRODUCT
distance limita o limite aos documentos em que a distância é superior ou igual ao limite especificado. As distâncias do produto escalar aumentam à medida que os vetores se tornam mais semelhantes.
O exemplo seguinte mostra como especificar um limite de distância para devolver até 10 documentos mais próximos que estejam, no máximo, a 4,5 unidades de distância usando a métrica de distância 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()); }
Limitações
Quando trabalhar com incorporações vetoriais, tenha em atenção as seguintes limitações:
- A dimensão de incorporação máxima suportada é 2048. Para armazenar índices maiores, use a redução da dimensionalidade.
- O número máximo de documentos a devolver de uma consulta de vizinho mais próximo é 1000.
- A pesquisa vetorial não suporta ouvintes de instantâneos em tempo real.
- A pesquisa vetorial só é suportada pelas bibliotecas cliente Python, Node.js, Go e Java.
O que se segue?
- Leia acerca das práticas recomendadas para o Firestore.
- Compreenda as leituras e escritas em grande escala.