找出近似最鄰近項目、建立向量索引,以及查詢向量嵌入

本頁說明如何使用 Spanner 中的下列 ANN 距離函式,找出近似最鄰近項目 (ANN)、建立向量索引,以及查詢向量嵌入項目:

  • APPROX_COSINE_DISTANCE
  • APPROX_EUCLIDEAN_DISTANCE
  • APPROX_DOT_PRODUCT

如果資料集很小,可以使用 K 近鄰 (KNN) 找出精確的 k 個最鄰近向量。不過,隨著資料集擴大,KNN 搜尋的延遲時間和費用也會增加。您可以使用 ANN 找出近似的 k 個最鄰近項目,大幅降低延遲和成本。

近似 k-近鄰

在 ANN 搜尋中,傳回的 k 個向量並非真正的 k 個最鄰近鄰居,因為 ANN 搜尋會計算近似距離,且可能不會查看資料集中的所有向量。有時會傳回幾個不在前 k 個最鄰近項目中的向量。這就是所謂的「召回率損失」。 可接受的喚回度損失量取決於應用實例,但在大多數情況下,為了提升資料庫效能而損失部分喚回度,是可接受的取捨。

如要進一步瞭解 Spanner 近似距離函式,請參閱:

向量索引

Spanner 會使用專用的向量索引,加快 ANN 向量搜尋速度。這項索引採用 Google 研究團隊的可擴充最鄰近 (ScaNN) 演算法,這是一種效率極高的最鄰近演算法。

向量索引會使用樹狀結構來分割資料,並加快搜尋速度。Spanner 提供兩層和三層樹狀結構設定:

  • 兩層樹狀結構設定:葉節點 (num_leaves) 包含密切相關的向量群組,以及對應的質心。根層級包含所有葉節點的質心。
  • 三層樹狀結構設定:概念與兩層樹狀結構類似,但會導入額外的分支層 (num_branches),葉節點質心會進一步分割,形成根層級 (num_leaves)。

Spanner 會為您選擇索引。不過,如果您知道特定索引最適合,可以使用 FORCE_INDEX 提示,選擇最適合您用途的向量索引。

詳情請參閱VECTOR INDEX聲明

限制

建立向量索引

為提升向量索引的召回率和效能,建議您採取下列做法:

  • 將大部分含有嵌入的資料列寫入資料庫後,再建立向量索引。插入新資料後,您可能也需要定期重建向量索引。詳情請參閱「重建向量索引」。

  • 使用 STORING 子句將資料欄副本儲存在向量索引中。如果資料欄值儲存在向量索引中,Spanner 會在索引的葉層級執行篩選,以提升查詢效能。如果資料欄用於篩選條件,建議您儲存該資料欄。如要進一步瞭解如何在索引中使用 STORING,請參閱「建立僅供索引掃描的索引」。

建立資料表時,嵌入欄必須是 FLOAT32 (建議) 或 FLOAT64 資料類型的陣列,並具有 vector_length 註解,指出向量的維度。

下列 DDL 陳述式會建立 Documents 資料表,其中包含具有向量長度的嵌入資料欄 DocEmbedding

CREATE TABLE Documents (
  DocId INT64 NOT NULL,
  ...
  DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);

填入 Documents 資料表後,您可以在具有嵌入欄 DocEmbeddingDocuments 資料表上,使用餘弦距離建立向量索引,其中包含兩層樹狀結構和 1000 個葉節點:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(DocEmbedding)
  STORING (WordCount)
  OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);

如要建立含有三層樹狀結構和 1000000 個葉節點的向量索引,請執行下列操作:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(NullableDocEmbedding)
  STORING (WordCount)
  WHERE NullableDocEmbedding IS NOT NULL
  OPTIONS (distance_type = 'COSINE', tree_depth = 3, num_branches=1000, num_leaves = 1000000);

如果嵌入資料欄在資料表定義中未標示為 NOT NULL,您必須使用 WHERE COLUMN_NAME IS NOT NULL 子句宣告該資料欄:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(NullableDocEmbedding)
  STORING (WordCount)
  WHERE NullableDocEmbedding IS NOT NULL
  OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);

篩選向量索引

您也可以建立經過篩選的向量索引,在資料庫中找出符合篩選條件的最相似項目。經過篩選的向量索引會選擇性地為符合指定篩選條件的資料列建立索引,進而提升搜尋效能。

在下列範例中,資料表 Documents 有一個名為 Category 的資料欄。在向量搜尋中,我們想為「科技」類別建立索引,因此建立產生的資料欄,如果未符合類別條件,則評估結果為 NULL。

CREATE TABLE Documents (
  DocId INT64 NOT NULL,
  Category STRING(MAX),
  NullIfFiltered BOOL AS (IF(Category = 'Tech', TRUE, NULL)) HIDDEN,
  DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);

接著,我們建立含有篩選器的向量索引。TechDocEmbeddingIndex 向量索引只會為「技術」類別中的文件建立索引。

CREATE VECTOR INDEX TechDocEmbeddingIndex
  ON Documents(DocEmbedding)
  STORING(NullIfFiltered)
  WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
  OPTIONS (...);

當 Spanner 執行下列查詢時,如果篩選條件符合 TechDocEmbeddingIndex,系統會自動選取 TechDocEmbeddingIndex 並加速執行查詢。這項查詢只會搜尋「科技」類別的文件。您也可以使用 {@FORCE_INDEX=TechDocEmbeddingIndex} 強制 Spanner 明確使用 TechDocEmbeddingIndex

SELECT *
FROM Documents
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
ORDER BY APPROX_(....)
LIMIT 10;

查詢向量嵌入

如要查詢向量索引,請使用下列其中一個近似距離函式:

  • APPROX_COSINE_DISTANCE
  • APPROX_EUCLIDEAN_DISTANCE
  • APPROX_DOT_PRODUCT

使用概略距離函式時的限制包括:

  • 近似距離函式必須計算嵌入資料欄和常數運算式 (例如參數或常值) 之間的距離。
  • 概略距離函式輸出內容必須在 ORDER BY 子句中做為唯一的排序鍵,且 ORDER BY 後方必須指定 LIMIT
  • 查詢必須明確篩除未建立索引的資料列。在大多數情況下,這表示查詢必須包含與向量索引定義相符的 WHERE <column_name> IS NOT NULL 子句,除非資料欄已在表格定義中標示為 NOT NULL

如需詳細的限制清單,請參閱概略距離函式參考頁面

範例

如要搜尋最接近 [1.0, 2.0, 3.0] 的 100 個向量,請執行下列操作:

SELECT DocId
FROM Documents
WHERE WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
  ARRAY<FLOAT32>[1.0, 2.0, 3.0], DocEmbedding,
  options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100

如果嵌入資料欄可為空值:

SELECT DocId
FROM Documents
WHERE NullableDocEmbedding IS NOT NULL AND WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
  ARRAY<FLOAT32>[1.0, 2.0, 3.0], NullableDocEmbedding,
  options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100

最佳做法

請按照下列最佳做法,最佳化向量索引並改善查詢結果。

調整向量搜尋選項

最佳向量搜尋值取決於用途、向量資料集和查詢向量。您可能需要進行反覆調整,才能找出適合特定工作負載的最佳值。

以下是選取適當值時可參考的實用準則:

  • tree_depth (樹狀結構層級):如果建立索引的資料表少於 1 千萬列,請使用 2tree_depth。否則,tree_depth3 最多支援約 100 億列的資料表。

  • num_leaves:使用資料集中的資料列數平方根。值越大,向量索引的建構時間就越長。請避免將 num_leaves 設定為大於 table_row_count/1000,否則會導致葉片過小,進而影響效能。

  • num_leaves_to_search:這個選項會指定要搜尋的索引葉節點數量。增加 num_leaves_to_search 可提高召回率,但也會增加延遲時間和成本。建議您使用 CREATE VECTOR INDEX 陳述式中定義的葉節點總數的 1% 做為 num_leaves_to_search 的值。如果使用篩選子句,請增加這個值來擴大搜尋範圍。

如果已達到可接受的召回率,但查詢費用過高,導致最高每秒查詢次數偏低,請按照下列步驟增加 num_leaves

  1. num_leaves 設為原始值的某個倍數 k (例如 2 * sqrt(table_row_count))。
  2. num_leaves_to_search 設為原始值的相同倍數 k。
  3. 實驗減少 num_leaves_to_search,在維持召回率的同時,改善費用和每秒查詢次數。

提高召回率

召回率下降的原因有很多,包括:

  • num_leaves_to_search 太小:您可能會發現,要為某些查詢向量找出最鄰近的項目較為困難,因此增加 num_leaves_to_search 以搜尋更多葉節點,有助於提升喚回率。最近的查詢可能已轉移,包含更多這類具有挑戰性的向量。

  • 需要重建向量索引:向量索引的樹狀結構會在建立時針對資料集進行最佳化,之後會維持靜態。因此,如果在建立初始向量索引後加入差異極大的向量,樹狀結構可能會不盡理想,導致召回率較差。

重建向量索引

如要在不停機的情況下重建向量索引,請按照下列步驟操作:

  1. 在與目前向量索引相同的嵌入資料欄上建立新的向量索引,並視需要更新參數 (例如 OPTIONS)。
  2. 索引建立完成後,請使用 FORCE_INDEX 提示指向新索引,以更新向量搜尋查詢。確保查詢使用新的向量索引。您可能也需要在新查詢中重新調整num_leaves_to_search
  3. 捨棄過時的向量索引。

後續步驟