根據(jù)向量查詢相近的目標。
常見的使用場景:
基于自然語言算法的相關性排名
相似推薦
相似的圖片或視頻搜索
提前需要做的準備
使用knn需要對數(shù)據(jù)進行預處理,你需要把需要匹配的數(shù)據(jù)轉換為有意義的向量值,然后寫入目標索引的dense_vector字段中。
KNN方法
對于KNN搜索,ES支持兩種方法:
使用script_score暴力查詢
近似KNN搜索
大多數(shù)情況下,我們推薦使用近似的KNN搜索,它會有較低的延遲,但是會犧牲索引的速度和結果的準確度。
如果使用暴力的script_score,那么需要使用query限定匹配的結果集,避免造成慢查詢,匹配的結果越小,效果就越好。
暴力script_score
先創(chuàng)建對應的索引,需要有一個或者多個dense_vector字段,如果不需要使用近似KNN搜索,可以忽略字段映射或者將index設置為false,這樣便于提高索引速度。
PUT product-index
{
"mappings": {
"properties": {
"product-vector": {
"type": "dense_vector",
"dims": 5,
"index": false
},
"price": {
"type": "long"
}
}
}
}
寫入數(shù)據(jù)
POST product-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 }
查詢
POST product-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]
}
}
}
}
}
提示:如果要限制匹配的文檔數(shù)量,推薦在script_score里面指定一個filter查詢,就像上面這樣。
近似KNN
注意:相較于其他查詢,近似KNN需要指定特殊的資源,因為需要把所有向量數(shù)據(jù)放在節(jié)點的頁緩存中。相關的資源配置參考近似knn搜索調整。
創(chuàng)建索引,下面兩個操作是必須的:
dense_vector字段需要開啟索引
指定similarity值,用于對匹配的相似度打分,參數(shù)的設置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ù)據(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" }
查詢
POST image-index/_search
{
"knn": {
"field": "image-vector",
"query_vector": [-5, 9, -12],
"k": 10,
"num_candidates": 100
},
"fields": [ "title", "file-type" ]
}
文檔的分數(shù)由查詢和文檔的向量決定,具體怎么計算,參考similarity參數(shù)。
knn api 會先去每個分片找num_candidates個最近鄰候選者,然后每個分片計算最優(yōu)的k個。最后把每個分片的結果合并,在計算出k個全局最優(yōu)。
num_candidates的值可以控制結果的精確度,但是更好的結果會帶來更多的消耗。
過濾后的KNN搜索
在KNN搜索里面可以指定的一個filter查詢,限定匹配的結果集:
POST image-index/_search
{
"knn": {
"field": "image-vector",
"query_vector": [54, 10, -2],
"k": 5,
"num_candidates": 50,
"filter": {
"term": {
"file-type": "png"
}
}
},
"fields": ["title"],
"_source": false
}
注意:這個查詢會在KNN搜索期間過濾結果,能夠確保返回的結果是k個。如果是post-filtering,那么是在knn搜索完之后再過濾的,返回的結果可能是會少于k個的。
結合近似KNN搜索和其他功能
可以使用knn搜索和不同的query混合搭配:
POST image-index/_search
{
"query": {
"match": {
"title": {
"query": "mountain lake",
"boost": 0.9
}
}
},
"knn": {
"field": "image-vector", (1)
"query_vector": [54, 10, -2], (2)
"k": 5, (3)
"num_candidates": 50, (4)
"boost": 0.1
},
"size": 10
}
要查詢的目標字段,必須是一個dense_vector類型。
查詢的向量,和目標字段的維度要一樣。
返回最相鄰的k個結果,這個值必須小于num_candidates。
每個分片要考慮的最近鄰候選者的數(shù)量。 不能超過 10000。 ES 從每個分片收集 num_candidates 個結果,然后合并它們以找到前 k 個結果。 增加 num_candidates 往往會提高最終 k 個結果的準確性。
提示:這里knn里面還可以使用filter,它可以過濾需要匹配的文檔,返回的k個文檔都會符合匹配的條件。
這個查詢是先得到全局5個最相鄰結果,然后將他們與匹配查詢匹配的結果組合,選出得分最高的前10個返回。分數(shù)的計算是這個樣子的:
score = 0.9 * match_score + 0.1 * knn_score
近似knn還可以搭配聚合,它聚合的是top k個鄰近文檔的結果。如果還有query,那么聚合的是混合查詢的結果。
索引注意事項
在索引是ES的每個段需要把dense_vector值存儲為HNSW graph,構建這些圖花費是巨大的。所以寫入的時候做好響應時間的調整,調優(yōu)參考近似KNN搜索調整。
另外,HNSW算法也有一些參數(shù)用來在構圖開銷,搜索速度和準確度之間進行權衡,可以使用index_options來調整這些參數(shù):
PUT image-index
{
"mappings": {
"properties": {
"image-vector": {
"type": "dense_vector",
"dims": 3,
"index": true,
"similarity": "l2_norm",
"index_options": {
"type": "hnsw", (1)
"m": 32, (2)
"ef_construction": 100 (3)
}
}
}
}
}
knn使用的算法,當前只支持hnsw。
HNSW 圖中每個節(jié)點將連接到的鄰居數(shù)量。 默認為 16。
在為每個新節(jié)點組裝最近鄰居列表時要跟蹤的候選者數(shù)量。 默認為 100。
近似KNN搜索限制
不能再一個nested 映射里面使用dense_vector。
使用用knn搜索做跨集群搜索時,ccs_minimize_roundtrips 操作不支持。
因為用了HNSW算法,保證了搜索的速度,但是犧牲掉了準確度。
注意:為了跨多個分片收集全局的top k個結果,近似KNN搜索使用dfs_query_then_fetch搜索類型。這個沒法更改。
近似KNN搜索調整
多用dot_product
cosine 相似性計算可以接受任何的浮點向量,對于測試來說它是非常方便的,但是它 得不到最優(yōu)的效果。相反的,推薦使用dot_product計算相似性。要使用dot_product,需要把所有向量歸一化長度為1。這樣就能夠獲得更快的速度,因為它避免了額外的向量長度計算。
確保數(shù)據(jù)節(jié)點有足夠的內存
ES的knn搜索用的是HNSW算法,HNSW算法需要大部分的向量數(shù)據(jù)在內存中才有效果,因此要確保數(shù)據(jù)節(jié)點有足夠的RAM保留向量數(shù)據(jù)和索引結構。如果要檢查向量數(shù)據(jù)的大小,可以使用 Analyze index disk usage API。
HNSW內存占用的預估方法可以參考這個公式:
num_vectors * 4 * (num_dimensions + 32)+ small buffer
特別注意的的是,需要的RAM是要與jvm的堆內存分開的。預留的一小部分緩沖,主要是考慮到其他可能需要用到緩存的地方,比如文本字段或者數(shù)值字段,也有可能需要使用文件緩存。
預熱文件系統(tǒng)緩存
當es重啟的時候,文件緩存是空的,如果等到熱數(shù)據(jù)都加載到內存中,是需要一定的時間的,這個時候可以通過設置index.store.preload提前加載需要的數(shù)據(jù)到文件緩存中。
注意:當緩存不足夠大時,加載過多的索引或者數(shù)據(jù)到緩存中會使搜索變慢,使用的時候要小心。
減少向量維度
knn搜索向量的維度和搜索速度呈線性關系,在可接受的效果下,應該盡量想辦法減少向量的維度??梢試L試使用一些像PCA這樣的數(shù)據(jù)對向量做降維處理。
查詢時排除召回向量字段
向量字段一般都比較大,如果返回到結果里面,會有很大的加載開銷。通常這個字段在結果里面也是不怎么需要的。在召回的時候應該盡量按需取數(shù)。
減少索引的段數(shù)量
ES的索引數(shù)據(jù)是放在segment上的,而向量數(shù)據(jù)在segment上是存成HNSW圖的。當搜索的時候需要掃描多個segment,一個接一個的搜索HNSW圖。如果segment過多,必然帶來更多的性能開銷。默認情況下,ES會有固定的策略把小的端合并成大的端。你也可以手動合并。
強制合并段
使用force merge api將數(shù)據(jù)合并到一個段里面。這樣在搜索的時候KNN搜索就只需要檢查一個HNSW圖。強制合并段是一個非常消耗資源的操作,要特別注意集群的壓力。
注意:段合并推薦在只讀索引上操作,當文檔更新時,ES只是標記文檔已被刪除,在常規(guī)的合并流程中這種文件會在段合并期間被清理掉。但是強制合并會產生非常大的段,這些段不符合常規(guī)合并的條件,因此會有大量的標記刪除文檔出現(xiàn),從而帶來更高的磁盤使用和更差的搜索性能。
在批量索引的時候創(chuàng)建一個大的段
在索引初始化的時候,可以通過索引的設置,讓ES創(chuàng)建一個大的段。
首先在初始化的時候要確保沒有查詢服務,并且要禁止索引刷新。
給 Elasticsearch 一個大的索引緩沖區(qū),這樣它可以在刷新之前接受更多的文檔。 默認情況下,indices.memory.index_buffer_size 設置為堆大小的 10%。 對于像 32GB 這樣大的堆大小,這通常就足夠了。 要允許使用完整的索引緩沖區(qū),您還應該增加限制 index.translog.flush_threshold_size。
避免在索引期間做很重的索引
主動索引文檔會給KNN搜索帶來不好的效果,因為它會占用計算資源。當搜索和索引同時發(fā)生的時候,ES也會刷新得比較頻繁,從而創(chuàng)建過多的小的segment,而過多的segment又會影響查詢的性能。
最好在knn搜索期間大量的索引文檔。如果是需要重新構建索引向量這種情況,應該新建一個索引做替換策略,而不是就地更新。
設置合適的預讀值
搜索會導致大量隨機讀取 I/O。 當?shù)讓訅K設備具有高預讀值時,可能會做大量不必要的讀取 I/O,尤其是在使用內存映射訪問文件時。
大多數(shù) Linux 發(fā)行版對單個普通設備使用 128KiB 的敏感預讀值,但是,當使用軟件 raid、LVM 或 dm-crypt 時,生成的塊設備(支持 Elasticsearch path.data)可能最終具有非常大的預讀值(在幾個 MiB 的范圍)。 這通常會導致嚴重的頁面(文件系統(tǒng))緩存抖動,從而對搜索(或更新)性能產生不利影響。
可以使用 lsblk -o NAME,RA,MOUNTPOINT,TYPE,SIZE 檢查 KiB 中的當前值。建議預讀值為 128KiB。
注意:blockdev 期望 512 字節(jié)扇區(qū)中的值,而 lsblk 報告 KiB 中的值。 例如,要臨時將 /dev/nvme0n1 的預讀設置為 128KiB,請指定 blockdev --setra 256 /dev/nvme0n1(todo)
similarity參數(shù)
knn計算相似度的算法,支持的值如下:
12_norm
基于向量的L^2(歐幾里德距離)計算,_score = 1 / (1 + l2_norm(query, vector)^2)。
dot_product
計算兩個向量的點積,能夠優(yōu)化cosine算法,是向量要做好歸一化。包括文檔向量和查詢向量。
_score = (1 + dot_product(query, vector)) / 2。
cosine
計算余旋相似度,計算余旋相似度最有效的辦法是將向量歸一化為固定長度,而不是使用dot product。如果沒有辦法做歸一化,那沒只能使用余旋。_score = (1 + cosine(query, vector)) / 2。余旋算法不允許向量幅度為0,因為這種情況下沒有定義余旋。文章來源:http://www.zghlxwxcb.cn/news/detail-821510.html
注意:向量的相似度和文本的相似度是不一樣的。文章來源地址http://www.zghlxwxcb.cn/news/detail-821510.html
到了這里,關于ES-KNN搜索的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!