1.最近鄰檢索(Nearest Neighbor Search)
最近鄰檢索就是根據數據的相似性,從數據庫中尋找與目標數據最相似的項目。這種相似性通常會被量化到空間上數據之間的距離,可以認為數據在空間中的距離越近,則數據之間的相似性越高。
k最近鄰(K-Nearest Neighbor,K-NN)檢索:當需要查找離目標數據最近的前k個數據項時。
最近鄰檢索是線性復雜度的,不能滿足對於大規模數據檢索的時間性能要求。
2.ANN(Approximate Nearest Neighbor)
面對龐大的數據量以及數據庫中高維的數據信息,現有的基於 NN 的檢索方法無法獲得理想的檢索效果與可接受的檢索時間。因此,研究人員開始關注近似最近鄰檢索因此,最佳實踐是使用逼近方法搜索最近鄰。目前,近似最近鄰縮短搜索時間有兩類基本方案:第一類是縮短距離計算的時間,例如降維,第二類是減少距離計算的次數。
宏觀上對ANN有下面的認知顯得很有必要:brute-force搜索的方式是在全空間進行搜索,為了加快查找的速度,幾乎所有的ANN方法都是通過對全空間分割,將其分割成很多小的子空間,在搜索的時候,通過某種方式,快速鎖定在某一(幾)子空間,然后在該(幾個)子空間里做遍歷。可以看到,正是因為縮減了遍歷的空間大小范圍,從而使得ANN能夠處理大規模數據的索引。
主要分為:
樹方法,如 KD-tree,Ball-tree,Annoy
哈希方法,如 Local Sensitive Hashing (LSH)
矢量量化方法,如 Product Quantization (PQ)
近鄰圖方法,如 Hierarchical Navigable Small World (HNSW)
3.樹方法-annoy
3.1原理介紹
-
annoy 算法的目標:建立一個數據結構能夠在較短的時間內找到任何查詢點的最近點,在精度允許的條件下通過犧牲准確率來換取比暴力搜索要快的多的搜索速度。
-
如下圖如所示:一個二叉樹來使得每個點查找時間復雜度:O(logn)。
建立索引
-
隨機選擇兩個點,以這兩個節點為初始中心節點,執行聚類數為2的kmeans過程,最終產生收斂后兩個聚類中心點。這兩個聚類中心點之間連一條線段(灰色短線),建立一條垂直於這條灰線,並且通過灰線中心點的線(黑色粗線)。這條黑色粗線把數據空間分成兩部分。在多維空間的話,這條黑色粗線可以看成等距垂直超平面。在划分的子空間內進行不停的遞歸迭代繼續划分,直到每個子空間最多只剩下K個數據節點。通過多次遞歸迭代划分的話,最終原始數據會形成二叉樹結構。二叉樹底層是葉子節點記錄原始數據節點,其他中間節點記錄的是分割超平面的信息。Annoy建立這樣的二叉樹結構是希望滿足這樣的一個假設:相似的數據節點應該在二叉樹上位置更接近,一個分割超平面不應該把相似的數據節點分在二叉樹的不同分支上。
查詢過程
-
二叉樹底層是葉子節點記錄原始數據節點,其他中間節點記錄的是分割超平面的信息。Annoy建立這樣的二叉樹結構是希望滿足這樣的一個假設:相似的數據節點應該在二叉樹上位置更接近,一個分割超平面不應該把相似的數據節點分在二叉樹的不同分支上。查找的過程就是不斷看查詢數據節點在分割超平面的哪一邊。從二叉樹索引結構來看,就是從根節點不停的往葉子節點遍歷的過程。通過對二叉樹每個中間節點(分割超平面相關信息)和查詢數據節點進行相關計算來確定二叉樹遍歷過程是往這個中間節點左子節點走還是右子節點走。
返回最終近鄰節點
-
每棵樹都返回一堆近鄰點后,如何得到最終的Top N相似集合呢?首先所有樹返回近鄰點都插入到優先隊列中,求並集去重, 然后計算和查詢點距離, 最終根據距離值從近距離到遠距離排序, 返回Top N近鄰節點集合。
3.2annoy最突出特性
-
使用靜態索引文件,這意味着不同進程可以共享索引。
3.2annoy常見API整理
-
AnnoyIndex(f, metric)返回可讀寫的新索引,用於存儲f維度向量。metric 是"angular","euclidean","manhattan","hamming",或"dot"。
-
a.add_item(i, v)用於給索引添加向量v
-
a.build(n_trees)用於構建 n_trees 的森林。查詢時,樹越多,精度越高。在調用build后,無法再添加任何向量。
-
a.save(fn, prefault=False)將索引保存到磁盤。保存后,不能再添加任何向量。
-
a.load(fn, prefault=False)從磁盤加載索引。如果prefault設置為True,它將把整個文件預讀到內存中。默認值為False。
-
a.unload() 釋放索引。
-
a.get_nns_by_item(i, n, search_k=-1, include_distances=False)返回第i 個item的n個最近鄰的item。在查詢期間,它將檢索多達search_k(默認n_trees * n)個點。search_k為您提供了更好的准確性和速度之間權衡。
-
a.get_nns_by_vector(v, n, search_k=-1, include_distances=False)與上面的相同,但按向量v查詢。
-
a.get_distance(i, j)返回向量i和向量j之間的距離。注意:此函數用於返回平方距離。
-
a.get_n_items() 返回索引中的向量數。
-
a.get_n_trees() 返回索引中的樹的數量。
-
a.on_disk_build(fn) 用以在指定文件而不是RAM中建立索引(在添加向量之前執行,在建立之后無需保存)。
權衡
調整Annoy僅需要兩個主要參數:樹的數量 n_trees 和搜索期間要檢查的節點的數量search_k。
-
n_trees在構建索引期間提供該值,並且會影響構建時間和索引大小。較大的值將給出更准確的結果,但索引較大。
-
search_k是在運行時提供的,並且會影響搜索性能。較大的值將給出更准確的結果,但返回時間將更長
效果對比圖
https://yongyuan.name/blog/ann-search.html