Elasticsearch 8.0及以上版本新增向量近鄰檢索k-nearest neighbor(kNN)search功能,能夠幫助您快速實(shí)現(xiàn)圖像搜索、視頻指紋采樣、人臉識(shí)別、語(yǔ)音識(shí)別和商品推薦等向量檢索場(chǎng)景的需求。本文介紹如何使用kNN search功能。

背景信息

關(guān)于Elasticsearch向量近鄰檢索k-nearest neighbor(kNN)search的詳細(xì)說(shuō)明,請(qǐng)參見k-nearest neighbor(kNN)search

前提條件

  • 創(chuàng)建阿里云Elasticsearch 8.x版本實(shí)例,本文以阿里云Elasticsearch 8.5.1版本為例介紹。創(chuàng)建實(shí)例的方法,請(qǐng)參見創(chuàng)建阿里云Elasticsearch實(shí)例
  • 將業(yè)務(wù)數(shù)據(jù)轉(zhuǎn)換成有意義的向量值(根據(jù)相似性設(shè)計(jì)向量,文檔的向量與查詢向量越接近,向量相似度匹配越好),并將向量數(shù)據(jù)存儲(chǔ)在dense_vector類型的字段下。

注意事項(xiàng)

  • 需使用dense_vector類型的索引字段存儲(chǔ)向量值,且dense_vector類型不支持aggregations和sorting。
  • nesetd字段類型下不支持近似kNN查詢。
  • Elasticsearch ccs場(chǎng)景使用kNN檢索時(shí),不支持ccs_minimize_roundtrips參數(shù)。
  • kNN默認(rèn)使用dfs_query_then_fetch查詢類型,執(zhí)行kNN查詢時(shí),不能顯式設(shè)置search_type。

kNN檢索方式說(shuō)明

kNN支持兩種檢索方式:近似kNN精確kNN,兩者區(qū)別如下。
檢索方式查詢接口是否全內(nèi)存mapping要求特點(diǎn)
近似kNN通過search API指定kNN參數(shù)查詢向量字段下index參數(shù)需要設(shè)置為true,才能開啟近似kNN查詢。
說(shuō)明 近似kNN搜索是在8.0版本新增的,在此之前,dense_vector類型的字段不支持在mapping中設(shè)置index為true。如果低于8.0版本的集群升級(jí)到阿里云Elasticsearch 8.5版本,并且要使用kNN檢索,需要確保創(chuàng)建的索引包含dense_vector類型的字段。為了支持近似kNN搜索,還需要重建索引并且設(shè)置新索引mapping中的index為true。
近似kNN以較慢的索引速度和較低的準(zhǔn)確性為代價(jià)來(lái)降低延遲。
精確kNN帶向量函數(shù)的script_score查詢。向量字段下index參數(shù)設(shè)置false或不要指定,可提高檢索效率。script_score查詢將掃描每個(gè)匹配的文檔來(lái)計(jì)算向量函數(shù),會(huì)導(dǎo)致搜索速度變慢。可以通過query限制傳遞給向量函數(shù)的文檔數(shù)改善延遲。

近似kNN

調(diào)整性能

通過近似kNN檢索,您可以高效地找到與查詢向量最近的K個(gè)向量,其搜索方式與其他查詢存在差異,因此對(duì)集群性能有特殊要求,可參考以下方式調(diào)整:
  • Elasticsearch將每個(gè)segment的密集向量值以HNSW圖來(lái)存儲(chǔ),因此索引向量數(shù)據(jù)時(shí)主要耗時(shí)在HNSW圖的構(gòu)建過程中,建議您增加客戶端超時(shí)時(shí)間且使用bulk請(qǐng)求寫入數(shù)據(jù)。
  • 降低索引segment數(shù)或?qū)⑺衧egment合并為1個(gè)來(lái)提高檢索效率。
  • 數(shù)據(jù)節(jié)點(diǎn)的內(nèi)存空間大于所有向量數(shù)據(jù)和索引結(jié)構(gòu)所占空間。
  • 避免在kNN檢索期間大量寫入或更新數(shù)據(jù)。

創(chuàng)建索引

創(chuàng)建近似kNN時(shí),索引mapping必須設(shè)置indextrue,并指定similarity參數(shù)值。
PUT image-index
{
  "mappings": {
    "properties": {
      "image-vector": {
        "type": "dense_vector",
        "dims": 3,
        "index": true,
        "similarity": "l2_norm"
      },
      "title": {
        "type": "text"
      },
      "file-type": {
        "type": "keyword"
      }
    }
  }
}
向量參數(shù)說(shuō)明如下,更多參數(shù)說(shuō)明,請(qǐng)參見dense-vector
參數(shù)說(shuō)明
type用來(lái)存儲(chǔ)浮點(diǎn)數(shù)的密集向量。需要設(shè)置為dense_vector。
dims向量的維度大小。當(dāng)indextrue時(shí),不能超過1024;當(dāng)indexfalse時(shí),不能超過2048 。
index是否為kNN生成新的索引。實(shí)現(xiàn)近似kNN查詢時(shí),需要將index設(shè)置為true,默認(rèn)為false。
similarity文檔間的相似度算法。indextrue時(shí),此值必須設(shè)置。可選值:
  • l2_norm:計(jì)算向量間歐式距離。_score公式:1 / (1 + l2_norm(query, vector)^2)
  • dot_product:計(jì)算兩個(gè)向量點(diǎn)積,_score計(jì)算依賴element_type參數(shù)值。
    • element_typefloat,所有向量需歸一化為單位長(zhǎng)度。_score公式:(1 + dot_product(query, vector)) / 2
    • element_typebyte,所有向量需要有相同的長(zhǎng)度,否則結(jié)果不準(zhǔn)確。_score公式:0.5 + (dot_product(query, vector) / (32768 * dims))
  • cosine:計(jì)算向量間的余弦相似度。最有效的cosine使用方式是將所有向量歸一化為單位長(zhǎng)度代替dot_product。_score公式:(1 + cosine(query, vector)) / 2
    重要 余弦相似度算法不允許向量數(shù)據(jù)為0。

寫入數(shù)據(jù)

POST image-index/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "image-vector": [1, 5, -20], "title": "moose family", "file-type": "jpg" }
{ "index": { "_id": "2" } }
{ "image-vector": [42, 8, -15], "title": "alpine lake", "file-type": "png" }
{ "index": { "_id": "3" } }
{ "image-vector": [15, 11, 23], "title": "full moon", "file-type": "jpg" }

向量檢索

近似向量檢索需要通過search API調(diào)用knn參數(shù)
說(shuō)明 knn_search API在Elasticsearch 8.4版本之后被廢棄,請(qǐng)通過在search API中配置knn參數(shù)的方式進(jìn)行向量檢索。
POST image-index/_search
{
  "knn": {
    "field": "image-vector",
    "query_vector": [-5, 9, -12],
    "k": 10,
    "num_candidates": 100
  },
  "fields": [ "title", "file-type" ]
}
knn參數(shù)說(shuō)明如下,詳細(xì)說(shuō)明請(qǐng)參見search-api-knn
參數(shù)是否必選說(shuō)明
field要檢索的向量字段名稱。
query_vector查詢向量,必須與field指定的向量數(shù)據(jù)具有相同的維度。
k返回的最近鄰對(duì)象的數(shù)量。k的值需要小于num_candidates
num_candidates每個(gè)分片上需查找的最近鄰候選對(duì)象的個(gè)數(shù),不能超過10000。
說(shuō)明 增加num_candidates的值可提高最終K值的準(zhǔn)確性,但相應(yīng)搜索速度會(huì)變慢。
filter通過DSL語(yǔ)句過濾文檔。kNN從過濾后的文檔中返回前K個(gè)文檔,如果不指定過濾器,將對(duì)所有文檔做kNN近似計(jì)算。

精確kNN

創(chuàng)建索引

PUT zl-index
{
  "mappings": {
    "properties": {
      "product-vector": {
        "type": "dense_vector",
        "dims": 5,
        "index": false
      },
      "price": {
        "type": "long"
      }
    }
  }
}
定義向量字段部分參數(shù)說(shuō)明如下,更多參數(shù)說(shuō)明請(qǐng)參見dense-vector
參數(shù)說(shuō)明
type用來(lái)存儲(chǔ)浮點(diǎn)數(shù)的密集向量,需要設(shè)置為dense_vector
dim向量的維度大小。
index是否為kNN生成新的索引文件。默認(rèn)值為false。使用精確kNN檢索,可不配置index參數(shù)或?qū)⑵湓O(shè)置為false,可提高精確kNN的檢索效率。

寫入數(shù)據(jù)

POST zl-index/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "product-vector": [230.0, 300.33, -34.8988, 15.555, -200.0], "price": 1599 }
{ "index": { "_id": "2" } }
{ "product-vector": [-0.5, 100.0, -13.0, 14.8, -156.0], "price": 799 }
{ "index": { "_id": "3" } }
{ "product-vector": [0.5, 111.3, -13.0, 14.8, -156.0], "price": 1099 }

查詢向量

以下示例在script_score查詢中指定向量函數(shù)cosineSimilarity,并使用script_score.query指定過濾器限制傳遞給vector文檔數(shù)來(lái)降低搜索延遲。
POST zl-index/_search
{
  "query": {
    "script_score": {
      "query" : {
        "bool" : {
          "filter" : {
            "range" : {
              "price" : {
                "gte": 1000
              }
            }
          }
        }
      },
      "script": {
        "source": "cosineSimilarity(params.queryVector, 'product-vector') + 1.0",
        "params": {
          "queryVector": [-0.5, 90.0, -10, 14.8, -156.0]
        }
      }
    }
  }
}
script_score支持以下向量函數(shù),更多說(shuō)明請(qǐng)參見向量訪問
函數(shù)名說(shuō)明
cosineSimilarity計(jì)算查詢向量和文檔向量的余弦相似度。
dotProduct計(jì)算查詢向量和文檔向量之間的點(diǎn)乘距離。
l1norm計(jì)算查詢向量和文檔向量之間的L1距離(曼哈頓距離)。
l2norm計(jì)算查詢向量和文檔向量之間的L2距離(歐式距離)。