本頁面說明如何使用 Cloud Firestore,透過下列技術執行 K 近鄰 (KNN) 向量搜尋:
- 儲存向量值
- 建立及管理 KNN 向量索引
- 使用其中一種支援的向量距離測量方式,執行 K 近鄰 (KNN) 查詢
事前準備
將嵌入項目儲存至 Cloud Firestore 之前,您必須先生成向量嵌入。Cloud Firestore 不會生成嵌入。您可以使用 Agent Platform 等服務,從 Cloud Firestore 資料建立向量值,例如文字嵌入。接著,您可以將這些嵌入內容存回 Cloud Firestore 文件。
如要進一步瞭解嵌入,請參閱「什麼是嵌入?」一文。
如要瞭解如何使用 Agent Platform 取得文字嵌入,請參閱「取得文字嵌入」。
儲存向量嵌入
以下範例說明如何在 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 控制台
如要透過 Google Cloud 控制台手動建立新索引,請按照下列步驟操作:
- 前往 Google Cloud 控制台的「Databases」(資料庫) 頁面。
- 從資料庫清單中選取所需資料庫。
- 在導覽選單中,依序點選「Indexes」(索引) 和「Manual」(手動) 分頁標籤。
- 按一下「Create Index」(建立索引)。
如要為向量搜尋建立向量欄位索引,請選取「Create vector index」(建立向量索引)。
-
輸入集合 ID。輸入向量欄位路徑和向量嵌入維度數量。新增要建立索引的其他欄位名稱,以及各欄位的索引模式。
按一下「Save Index」(儲存索引)。
新的索引會顯示在手動索引清單中,Cloud Firestore 則會開始建立索引。索引建立完成後,旁邊會顯示綠色勾號。
gcloud
建立向量索引前,請先升級至最新版本 Google Cloud CLI:
gcloud components update
如要建立向量索引,請使用 gcloud firestore indexes composite create:
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 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
列出所有向量索引
Google Cloud 控制台
- 前往 Google Cloud 控制台的「Databases」(資料庫) 頁面。
- 從資料庫清單中選取所需資料庫。
-
在導覽選單中,依序點選「Indexes」(索引) 和「Manual」(手動) 分頁標籤。
索引表格會列出資料庫的所有索引。向量索引包含帶有 圖示的向量欄位。
gcloud
如要列出所有索引並擷取索引 ID,請按照下列步驟操作:
gcloud firestore indexes composite list --database=database-id
將 database-id 替換為資料庫 ID。
您可以使用索引 ID 查看索引的詳細資料:
gcloud firestore indexes composite describe index-id --database=database-id
其中:
- index-id 是要說明的索引 ID。
- database-id 是資料庫 ID。
刪除向量索引
Google Cloud 控制台
- 前往 Google Cloud 控制台的「Databases」(資料庫) 頁面。
- 從資料庫清單中選取所需資料庫。
-
在導覽選單中,依序點選「Indexes」(索引) 和「Manual」(手動) 分頁標籤。
- 在手動索引清單中,按一下要刪除的索引對應的「More」(更多) 按鈕 。按一下「刪除」。
- 在快訊中按一下「Delete Index」(刪除索引),確認要刪除這個索引。
gcloud
gcloud firestore indexes composite delete 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搭配單位正規化向量,而非 COSINE 距離,兩者在數學上等效,但前者效能較佳。詳情請參閱「餘弦相似度」。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距離會將門檻限制在距離大於或等於指定門檻的文件。向量越相似,點積距離越大。
以下範例說明如何指定距離門檻,使用 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 (僅限 Standard 版)。
- 向量搜尋不支援即時快照監聽器。
- 只有 Python、Node.js、Go 和 Java 用戶端程式庫支援向量搜尋。