本頁面說明如何使用 Cloud Firestore 執行 K 近鄰 (KNN) 向量搜尋,並使用下列技巧:
- 儲存向量值
- 建立及管理 KNN 向量索引
- 使用支援的其中一個向量距離度量方式,建立 K 近鄰 (KNN) 查詢
儲存向量嵌入
您可以從 Cloud Firestore 資料建立向量值,例如文字嵌入,並將這些值儲存在 Cloud Firestore 文件中。
使用向量嵌入功能的寫入作業
以下範例說明如何在 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();
使用 Cloud 函式計算向量嵌入
如要在建立或更新文件時計算及儲存向量嵌入資料,您可以設定 Cloud 函式:
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
建立及管理向量索引
您必須先建立對應的索引,才能使用向量嵌入資料執行最近鄰搜尋。以下範例說明如何使用 Google Cloud CLI 建立及管理向量索引。您也可以使用 Firebase CLI 和 Terraform 管理向量索引。
建立向量索引
建立向量索引之前,請先升級至 Google Cloud CLI 的最新版本:
gcloud components update
如要建立向量索引,請使用 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
其中:
- collection-group 是集合群組的 ID。
- vector-field 是包含向量嵌入的欄位名稱。
- database-id 是資料庫的 ID。
- vector-configuration 包含向量
dimension
和索引類型。dimension
是整數,最多可達 2048。索引類型必須為flat
。設定索引格式如下:{"dimension":"DIMENSION", "flat": "{}"}
。
以下範例會建立複合式索引,包括欄位 vector-field
的向量索引,以及欄位 color
的遞增索引。您可以使用這類索引,在最鄰近搜尋之前預先篩選資料。
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
列出所有向量索引
gcloud
gcloud firestore indexes composite list --database=database-id
將 database-id 替換為資料庫的 ID。
刪除向量索引
gcloud
gcloud firestore indexes composite delete index-id --database=database-id
其中:
- index-id 是要刪除的索引 ID。使用
indexes composite list
擷取索引 ID。 - database-id 是資料庫的 ID。
說明向量索引
gcloud
gcloud firestore indexes composite describe index-id --database=database-id
其中:
- index-id 是所要描述索引的 ID。使用
indexes composite list
擷取索引 ID。 - database-id 是資料庫的 ID。
提出最鄰近查詢
您可以執行相似度搜尋,找出向量嵌入項目的最鄰近項目。相似度搜尋需要向量索引。如果不存在索引,Cloud Firestore 會建議使用 gcloud CLI 建立索引。
以下範例會找出查詢向量的 10 個最近鄰。
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();
向量距離
最鄰近查詢支援下列向量距離選項:
EUCLIDEAN
:測量向量之間的歐氏距離。詳情請參閱 Euclidean。COSINE
:根據向量之間的夾角比較向量,讓您測量不以向量大小為依據的相似度。建議您使用DOT_PRODUCT
搭配單位正規化向量,而不要使用餘弦距離,因為在數學上,這兩者是等價的,且DOT_PRODUCT
的效能更好。如需更多資訊,請參閱「餘弦相似度」。DOT_PRODUCT
:類似COSINE
,但會受到向量大小的影響。詳情請參閱「內積」。
選擇距離測量單位
視所有向量嵌入項目是否已正規化而定,您可以決定要使用哪種距離測量方法來找出距離測量值。經過規格化的向量嵌入值大小 (長度) 會精確為 1.0。
此外,如果您知道模型是使用哪種距離測量方式進行訓練,請使用該距離測量方式計算向量嵌入值之間的距離。
規一化資料
如果資料集中的所有向量嵌入值都已標準化,則所有三種距離評估方式都會提供相同的語意搜尋結果。從本質上來說,雖然每個距離測量值都會傳回不同的值,但這些值的排序方式相同。當嵌入值經過標準化後,DOT_PRODUCT
通常是最具運算效率的做法,但在大多數情況下,差異不大。不過,如果您的應用程式對效能極為敏感,DOT_PRODUCT
可能有助於調整效能。
非標準化資料
如果向量嵌入未經過標準化,則使用 DOT_PRODUCT
做為距離測量值,在數學上並不正確,因為點積運算式無法測量距離。視嵌入資料的產生方式和偏好的搜尋類型而定,COSINE
或 EUCLIDEAN
距離評估值產生的搜尋結果,在主觀上會比其他距離評估值更優。您可能需要實驗 COSINE
或 EUCLIDEAN
,才能判斷哪一個最適合您的用途。
不確定資料是否已標準化
如果您不確定資料是否已標準化,但仍想使用 DOT_PRODUCT
,建議改用 COSINE
。COSINE
就像內建規範化的 DOT_PRODUCT
。使用 COSINE
測量的距離範圍從 0
到 2
。如果結果接近 0
,表示向量非常相似。
預先篩選文件
如要在尋找最近鄰點之前預先篩選文件,可以將相似度搜尋與其他查詢運算子結合。支援 and
和 or
複合篩選器。如要進一步瞭解支援的欄位篩選器,請參閱「查詢運算子」。
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();
擷取計算的向量距離
您可以在 FindNearest
查詢中指派 distance_result_field
輸出屬性名稱,藉此擷取計算的向量距離,如以下範例所示:
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")); }
如果您想使用欄位遮罩來傳回部分文件欄位和 distanceResultField
,則必須在欄位遮罩中加入 distanceResultField
的值,如以下範例所示:
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")); }
指定距離門檻
您可以指定相似度門檻,只傳回門檻內的文件。門檻值欄位的行為取決於您選擇的距離測量單位:
EUCLIDEAN
和COSINE
距離會將閾值限制在距離小於或等於指定閾值的文件。這些距離測量值會隨著向量相似度降低。DOT_PRODUCT
distance 會將閾值限制在距離大於或等於指定閾值的文件。點積距離會隨著向量相似度的增加而增加。
以下範例說明如何指定距離門檻,以便使用 EUCLIDEAN
距離指標,傳回最多 10 個距離為 4.5 單位以內的最近文件:
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()); }
限制
使用向量嵌入功能時,請注意下列限制:
- 支援的嵌入維度上限為 2048。如要儲存較大的索引,請使用降維。
- 最接近查詢傳回的文件數量上限為 1000 個。
- 向量搜尋不支援即時快照事件監聽器。
- 只有 Python、Node.js、Go 和 Java 用戶端程式庫支援向量搜尋。