Na tej stronie dowiesz się, jak za pomocą usługi Cloud Firestore wykonywać wyszukiwania wektorów z użyciem algorytmu najbliższego sąsiada (KNN) przy użyciu tych technik:
- Zapisywanie wartości wektorów
- Tworzenie indeksów wektorowych KNN i zarządzanie nimi
- Wykonaj zapytanie dotyczące najbliższych sąsiadów (KNN) za pomocą jednej z obsługiwanych miar odległości wektorowej.
Przechowywanie wektorów dystrybucyjnych
Na podstawie danych Cloud Firestore możesz tworzyć wartości wektorów, np. tekstowe wektory dystrybucyjne, i przechowywać je w dokumentach Cloud Firestore.
Operacja zapisu z wektorem zastępczym
Ten przykład pokazuje, jak zapisać wektor dystrybucyjny w dokumencie Cloud 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();
Obliczanie wektorów dystrybucyjnych za pomocą funkcji w Cloud Functions
Aby obliczać i przechowywać wektory zanurzeniowe za każdym razem, gdy dokument jest aktualizowany lub tworzony, możesz skonfigurować funkcję usługi Cloud Functions:
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
Tworzenie indeksów wektorowych i zarządzanie nimi
Zanim wykonasz wyszukiwanie najbliższego sąsiada za pomocą wektorów zanurzonych, musisz utworzyć odpowiedni indeks. Poniższe przykłady pokazują, jak tworzyć indeksy wektorów i nimi zarządzać za pomocą interfejsu Google Cloud CLI. Indeksami wektorowymi można też zarządzać za pomocą wiersza poleceń Firebase i Terraform.
Tworzenie indeksu wektorowego
Zanim utworzysz indeks wektorowy, uaktualnij Google Cloud CLI do najnowszej wersji:
gcloud components update
Aby utworzyć indeks wektorów, użyj 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
gdzie:
- collection-group to identyfikator grupy kolekcji.
- vector-field to nazwa pola, które zawiera wektorową reprezentację.
- database-id to identyfikator bazy danych.
- vector-configuration obejmuje wektor
dimension
i typ indeksu. Wartośćdimension
to liczba całkowita do 2048. Typ indeksu musi byćflat
. Sformatuj konfigurację indeksu w ten sposób:{"dimension":"DIMENSION", "flat": "{}"}
.
W tym przykładzie tworzymy indeks złożony, w tym indeks wektorowy dla pola vector-field
i rosnący indeks dla pola color
. Za pomocą tego typu indeksu możesz wstępnie filtrować dane przed wyszukiwaniem najbliższych sąsiadów.
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
Wyświetlanie listy wszystkich indeksów wektorowych
gcloud
gcloud firestore indexes composite list --database=database-id
Zastąp database-id identyfikatorem bazy danych.
Usuwanie indeksu wektorowego
gcloud
gcloud firestore indexes composite delete index-id --database=database-id
gdzie:
- index-id to identyfikator indeksu do usunięcia.
Aby pobrać identyfikator indeksu, użyj polecenia
indexes composite list
. - database-id to identyfikator bazy danych.
Opisz indeks wektorowy
gcloud
gcloud firestore indexes composite describe index-id --database=database-id
gdzie:
- index-id to identyfikator indeksu, który chcesz opisać. Użyj polecenia
indexes composite list
, aby pobrać identyfikator indeksu. - database-id to identyfikator bazy danych.
Wykonywanie zapytania o najbliższego sąsiada
Możesz przeprowadzić wyszukiwanie podobieństwa, aby znaleźć najbliższych sąsiadów wektora. Wyszukiwanie podobieństwa wymaga indeksów wektorowych. Jeśli indeks nie istnieje, Cloud Firestore sugeruje utworzenie indeksu za pomocą gcloud CLI.
W tym przykładzie znaleziono 10 najbliższych sąsiadów wektora zapytania.
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();
Wektorowe odległości
Zapytania dotyczące najbliższego sąsiada obsługują te opcje odległości wektorowej:
EUCLIDEAN
: mierzy odległość EUKLIDOWSKĄ między wektorami. Więcej informacji znajdziesz w artykule [GA4] Metoda euklidyjska.COSINE
: porównuje wektory na podstawie kąta między nimi, co umożliwia pomiar podobieństwa bez uwzględniania wielkości wektorów. Zalecamy użycieDOT_PRODUCT
z jednostkami normalizowanymi zamiast odległości cosinusowej, która jest matematycznie równoważna, ale ma lepszą wydajność. Aby dowiedzieć się więcej, zapoznaj się z artykułem Współczynnik cosinusa.DOT_PRODUCT
: podobny doCOSINE
, ale zależy od wielkości wektorów. Więcej informacji znajdziesz w artykule Produkt skalarny.
Wybierz jednostkę miary odległości
W zależności od tego, czy wszystkie wektory zastępcze są znormalizowane, możesz określić, która miara odległości ma być używana do znajdowania odległości. Normalizowany wektor embeddingu ma wielkość (długość) dokładnie 1,0.
Jeśli dodatkowo wiesz, z którego rodzaju miarą odległości trenowano model, możesz użyć tej miary do obliczenia odległości między wektorami osadzania.
Normalizowane dane
Jeśli masz zbiór danych, w którym wszystkie wektory zastępcze są znormalizowane, wszystkie 3 wskaźniki odległości dają te same wyniki wyszukiwania semantycznego. Chociaż każda miara odległości zwraca inną wartość, w podstawie są one sortowane w taki sam sposób. Gdy wstępnie znormalizowane embeddingi są znormalizowane, DOT_PRODUCT
jest zwykle najbardziej wydajnym rozwiązaniem pod względem obliczeniowym, ale w większości przypadków różnica jest znikoma. Jeśli jednak Twoja aplikacja jest bardzo wrażliwa na wydajność, DOT_PRODUCT
może pomóc w jej optymalizacji.
Dane nieunormowane
Jeśli masz zbiór danych, w którym wektory dystrybucyjne nie są znormalizowane, użycie DOT_PRODUCT
jako miary odległości nie jest matematycznie poprawne, ponieważ iloczyn skalarny nie mierzy odległości. W zależności od tego, jak zostały wygenerowane wektory i który typ wyszukiwania jest preferowany, wyniki wyszukiwania z użyciem odległości COSINE
lub EUCLIDEAN
są subiektywnie lepsze od wyników z użyciem innych miar odległości.
Aby określić, która z nich najlepiej sprawdzi się w Twoim przypadku, konieczne może być przeprowadzenie eksperymentu z użyciem COSINE
lub EUCLIDEAN
.
Nie wiesz, czy dane są znormalizowane
Jeśli nie masz pewności, czy Twoje dane są znormalizowane, i chcesz użyć funkcji DOT_PRODUCT
, zalecamy użycie funkcji COSINE
.
COSINE
to funkcja DOT_PRODUCT
z wbudowaną normalizacją.
Odległość zmierzona za pomocą COSINE
ma zakres od 0
do 2
. Wynik zbliżony do 0
wskazuje, że wektory są bardzo podobne.
Filtrowanie wstępne dokumentów
Aby wstępnie odfiltrować dokumenty przed znalezieniem najbliższych sąsiadów, możesz połączyć wyszukiwanie podobieństwa z innymi operatorami zapytań. Obsługiwane są filtry złożone and
i or
. Więcej informacji o obsługiwanych filtrach pól znajdziesz w artykule Operatory zapytań.
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();
Pobieranie obliczonej odległości wektorowej
Aby pobrać obliczoną odległość wektorową, przypisz do zapytania FindNearest
nazwę właściwości wyjściowej distance_result_field
, jak pokazano w tym przykładzie:
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")); }
Jeśli chcesz użyć maski pola, aby zwrócić podzbiór pól dokumentu wraz z polem distanceResultField
, musisz też uwzględnić w niej wartość pola distanceResultField
, jak w tym przykładzie:
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")); }
Określanie progu odległości
Możesz określić próg podobieństwa, który zwraca tylko dokumenty w danym zakresie. Zachowanie pola progu zależy od wybranej miary odległości:
- Odległości
EUCLIDEAN
iCOSINE
ograniczają próg do dokumentów, w których odległość jest mniejsza lub równa określonemu progowi. Te miary odległości maleją, gdy wektory są bardziej podobne. DOT_PRODUCT
odległość ogranicza próg do dokumentów, w których odległość jest większa lub równa określonemu progowi. Odległości w formie iloczynu skalarnego rosną, gdy wektory stają się bardziej podobne.
Ten przykład pokazuje, jak określić próg odległości, aby zwrócić maksymalnie 10 najbliższych dokumentów, które znajdują się w odległości maksymalnie 4,5 jednostek, używając miary odległości 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()); }
Ograniczenia
Podczas pracy z wektorami dystrybucyjnymi należy pamiętać o tych ograniczeniach:
- Maksymalna obsługiwana wymiarość wstawiania to 2048. Aby przechowywać większe indeksy, użyj redukcji wymiarów.
- Maksymalna liczba dokumentów zwracanych przez zapytanie o najbliższego sąsiada to 1000.
- Wyszukiwanie wektorów nie obsługuje słuchaczy zrzutów w czasie rzeczywistym.
- Wyszukiwanie wektorów obsługują tylko biblioteki klienta Python, Node.js, Go i Java.
Co dalej?
- Dowiedz się więcej o sprawdzonych metodach dotyczących Cloud Firestore.
- Dowiedz się więcej o odczytach i zapisach na dużą skalę.