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 wektorów 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
Z 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 po każdej aktualizacji lub utworzeniu dokumentu, możesz skonfigurować funkcję 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
. Tego typu indeksu możesz używać do wstępnego filtrowania danych 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, który chcesz usunąć.
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 Euclidean.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 jednostkowo znormalizowanymi wektorami zamiast odległości cosinusowej, która jest matematycznie równoważna, ale zapewnia 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 wiesz, z którego rodzaju miarą odległości był trenowany Twój 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 osadzone 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 obliczeń, 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ą do siebie coraz 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 za pomocą 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ść umieszczania 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 odbiorwników 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ę.