Halaman ini menunjukkan cara menggunakan Cloud Firestore untuk melakukan penelusuran vektor tetangga K-terdekat (KNN) menggunakan teknik berikut:
- Menyimpan nilai vektor
- Membuat dan mengelola indeks vektor KNN
- Membuat kueri tetanggan K-terdekat (KNN) menggunakan salah satu jarak vektor yang didukung
Menyimpan embedding vektor
Anda dapat membuat nilai vektor seperti embedding teks dari data Cloud Firestore, dan menyimpannya dalam dokumen Cloud Firestore.
Operasi tulis dengan embedding vektor
Contoh berikut menunjukkan cara menyimpan embedding vektor dalam dokumen 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();
Menghitung embedding vektor dengan Cloud Function
Untuk menghitung dan menyimpan embedding vektor setiap kali dokumen diperbarui atau dibuat, Anda dapat menyiapkan Cloud Function:
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
Membuat dan mengelola indeks vektor
Sebelum Anda dapat melakukan penelusuran tetangga terdekat dengan embedding vektor, Anda harus membuat indeks yang sesuai. Contoh berikut menunjukkan cara membuat dan mengelola indeks vektor dengan Google Cloud CLI. Indeks vektor juga dapat dikelola dengan Firebase CLI dan Terraform.
Membuat indeks vektor
Sebelum membuat indeks vektor, upgrade Google Cloud CLI ke versi terbaru:
gcloud components update
Untuk membuat indeks vektor, gunakan 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
dengan:
- collection-group adalah ID grup koleksi.
- vector-field adalah nama kolom yang berisi embedding vektor.
- database-id adalah ID database.
- vector-configuration mencakup vektor
dimension
dan jenis indeks.dimension
adalah bilangan bulat hingga 2048. Jenis indeks harusflat
. Format konfigurasi indeks sebagai berikut:{"dimension":"DIMENSION", "flat": "{}"}
.
Contoh berikut membuat indeks komposit, termasuk indeks vektor untuk kolom vector-field
dan indeks menaik untuk kolom color
. Anda dapat menggunakan jenis indeks ini untuk melakukan pra-pemfilteran data sebelum penelusuran tetangga terdekat.
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
Mencantumkan semua indeks vektor
gcloud
gcloud firestore indexes composite list --database=database-id
Ganti database-id dengan ID database.
Menghapus indeks vektor
gcloud
gcloud firestore indexes composite delete index-id --database=database-id
dengan:
- index-id adalah ID indeks yang akan dihapus.
Gunakan
indexes composite list
untuk mengambil ID indeks. - database-id adalah ID database.
Menjelaskan indeks vektor
gcloud
gcloud firestore indexes composite describe index-id --database=database-id
dengan:
- index-id adalah ID indeks yang akan dijelaskan. Gunakan atau
indexes composite list
untuk mengambil ID indeks. - database-id adalah ID database.
Membuat kueri tetangga terdekat
Anda dapat melakukan penelusuran kesamaan untuk menemukan tetangga terdekat dari embedding vektor. Penelusuran kesamaan memerlukan indeks vektor. Jika indeks tidak ada, Cloud Firestore akan menyarankan indeks yang akan dibuat menggunakan gcloud CLI.
Contoh berikut menemukan 10 tetangga terdekat dari vektor kueri.
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();
Jarak vektor
Kueri tetangga terdekat mendukung opsi berikut untuk jarak vektor:
EUCLIDEAN
: Mengukur jarak EUCLIDEAN antarvektor. Untuk mempelajari lebih lanjut, lihat Euclidean.COSINE
: Membandingkan vektor berdasarkan sudut antarvektor yang memungkinkan Anda mengukur kesamaan yang tidak didasarkan pada besaran vektor. Sebaiknya gunakanDOT_PRODUCT
dengan vektor yang dinormalisasi unit, bukan jarak COSINE, yang secara matematis setara dengan performa yang lebih baik. Untuk mempelajari lebih lanjut, lihat Kesamaan kosinus untuk mempelajari lebih lanjut.DOT_PRODUCT
: Serupa denganCOSINE
tetapi dipengaruhi oleh besarnya vektor. Untuk mempelajari lebih lanjut, lihat Perkalian titik.
Memilih ukuran jarak
Anda dapat menentukan ukuran jarak yang akan digunakan untuk menemukan ukuran jarak, tergantung pada apakah semua embedding vektor Anda dinormalisasi atau tidak. Embedding vektor yang dinormalisasi memiliki besaran (panjang) tepat 1,0.
Selain itu, jika Anda mengetahui ukuran jarak yang digunakan untuk melatih model, gunakan ukuran jarak tersebut untuk menghitung jarak antara embedding vektor Anda.
Data yang dinormalisasi
Jika Anda memiliki set data tempat semua embedding vektor dinormalisasi, ketiga
ukuran jarak akan memberikan hasil penelusuran semantik yang sama. Pada dasarnya, meskipun setiap
ukuran jarak menampilkan nilai yang berbeda, nilai tersebut diurutkan dengan cara yang sama. Saat
embedding dinormalisasi, DOT_PRODUCT
biasanya paling efisien
secara komputasi, tetapi perbedaannya dapat diabaikan dalam sebagian besar kasus. Namun, jika
aplikasi Anda sangat sensitif terhadap performa, DOT_PRODUCT
dapat membantu
penyesuaian performa.
Data yang tidak dinormalisasi
Jika Anda memiliki set data yang tidak melakukan normalisasi pada embedding vektor,
maka secara matematis tidak benar untuk menggunakan DOT_PRODUCT
sebagai ukuran jarak
karena perkalian titik tidak mengukur jarak. Bergantung
pada cara pembuatan embedding dan jenis penelusuran yang lebih disukai,
ukuran jarak COSINE
atau EUCLIDEAN
akan menghasilkan
hasil penelusuran yang secara subjektif lebih baik daripada pengukuran jarak lainnya.
Eksperimen dengan COSINE
atau EUCLIDEAN
mungkin
diperlukan guna menentukan pilihan terbaik untuk kasus penggunaan Anda.
Tidak yakin apakah data dinormalisasi atau tidak
Jika Anda tidak yakin apakah data dinormalisasi atau tidak dan ingin menggunakan
DOT_PRODUCT
, sebaiknya gunakan COSINE
.
COSINE
mirip dengan DOT_PRODUCT
dengan normalisasi bawaan.
Jarak yang diukur menggunakan COSINE
berkisar dari 0
hingga 2
. Hasil
yang mendekati 0
menunjukkan bahwa vektor sangat mirip.
Pra-filter dokumen
Untuk melakukan prafilter dokumen sebelum menemukan tetangga terdekat, Anda dapat menggabungkan penelusuran kesamaan dengan operator kueri lainnya. Filter komposit and
dan or
didukung. Untuk mengetahui informasi selengkapnya tentang filter kolom yang didukung, lihat Operator kueri.
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();
Mengambil jarak vektor yang dihitung
Anda dapat mengambil jarak vektor yang dihitung dengan menetapkan nama properti output distance_result_field
pada kueri FindNearest
, seperti yang ditunjukkan dalam contoh berikut:
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")); }
Jika ingin menggunakan mask kolom untuk menampilkan subset kolom dokumen beserta distanceResultField
, Anda juga harus menyertakan nilai distanceResultField
dalam mask kolom, seperti yang ditunjukkan dalam contoh berikut:
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")); }
Menentukan nilai minimum jarak
Anda dapat menentukan nilai minimum kesamaan yang hanya menampilkan dokumen dalam nilai minimum. Perilaku kolom nilai minimum bergantung pada ukuran jarak yang Anda pilih:
- Jarak
EUCLIDEAN
danCOSINE
membatasi nilai minimum untuk dokumen dengan jarak kurang dari atau sama dengan nilai minimum yang ditentukan. Ukuran jarak ini menurun seiring dengan semakin miripnya vektor. - Jarak
DOT_PRODUCT
membatasi nilai minimum untuk dokumen yang jaraknya lebih besar dari atau sama dengan nilai minimum yang ditentukan. Jarak perkalian titik akan meningkat seiring dengan semakin miripnya vektor.
Contoh berikut menunjukkan cara menentukan nilai minimum jarak untuk menampilkan hingga 10 dokumen terdekat yang jaraknya paling jauh 4,5 unit menggunakan metrik jarak 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()); }
Batasan
Saat Anda bekerja dengan embedding vektor, perhatikan batasan berikut ini:
- Dimensi embedding maksimal yang didukung adalah 2048. Untuk menyimpan indeks yang lebih besar, gunakan pengurangan dimensi.
- Jumlah maksimal dokumen untuk ditampilkan dari kueri tetangga terdekat adalah 1.000.
- Penelusuran vektor tidak mendukung pemroses snapshot real-time.
- Hanya library klien Python, Node.js, Go, dan Java yang mendukung penelusuran vektor.
Langkah berikutnya
- Baca praktik terbaik untuk Cloud Firestore.
- Memahami pembacaan dan penulisan dalam skala besar.