【Faiss】索引選擇指南(三)

https://github.com/liqima/faiss_note/blob/master/3.Basics%20%E9%80%89%E6%8B%A9%E5%90%88%E9%80%82%E7%9A%84index%E7%B1%BB%E5%9E%8B.ipynb 

選擇合適的index類型

選擇index類型並沒有一套精準的法則可以依據,需要根據自己的實際情況選取。下面的幾個問題可以作爲選取index的參考。

是否需要精確的結果

如果需要,應該使用“Flat” 只有 IndexFlatL2 能確保返回精確結果。一般將其作爲baseline與其他索引方式對比,以便在精度和時間開銷之間做權衡。
不支持add_with_ids,如果需要,可以用“IDMap, Flat”。
支持GPU。

#導入faiss
import sys
sys.path.append('/home/maliqi/faiss/python/')
import faiss

#數據
import numpy as np 
d = 512          #維數
n_data = 2000   
np.random.seed(0) 
data = []
mu = 3
sigma = 0.1
for i in range(n_data):
    data.append(np.random.normal(mu, sigma, d))
data = np.array(data).astype('float32')

#ids, 6位隨機數
ids = []
start = 100000
for i in range(data.shape[0]):
    ids.append(start)
    start += 100
ids = np.array(ids)

 

#不支持add_with_ids
index = faiss.index_factory(d, "Flat")
index.add(data)
dis, ind = index.search(data[:5], 10)
print(ind)
[[   0  798  879  223  981 1401 1458 1174  919   26]
 [   1  981 1524 1639 1949 1472 1162  923  840  300]
 [   2 1886  375 1351  518 1735 1551 1958  390 1695]
 [   3 1459  331  389  655 1943 1483 1723 1672 1859]
 [   4   13  715 1470  608  459  888  850 1080 1654]]

 

index = faiss.index_factory(d, "IDMap, Flat")
index.add_with_ids(data, ids)
dis, ind = index.search(data[:5], 10)
print(ind)   # 返回的結果是我們自己定義的id

結果爲:
[[100000 179800 187900 122300 198100 240100 245800 217400 191900 102600]
 [100100 198100 252400 263900 294900 247200 216200 192300 184000 130000]
 [100200 288600 137500 235100 151800 273500 255100 295800 139000 269500]
 [100300 245900 133100 138900 165500 294300 248300 272300 267200 285900]
 [100400 101300 171500 247000 160800 145900 188800 185000 208000 265400]]

關心內存開銷

需要注意的是faiss在索引時必須將index讀入內存。

如果不在意內存佔用空間,使用“HNSWx”

如果內存空間很大,數據庫很小,HNSW是最好的選擇,速度快,精度高,一般4<=x<=64。不支持add_with_ids,不支持移除向量,不需要訓練,不支持GPU。

index = faiss.index_factory(d, "HNSW8")
index.add(data)
dis, ind = index.search(data[:5], 10)
print(ind)

結果爲:
[[ 879  981   26 1132  807 1639   28 1334 1832 1821]
 [   1  981 1524 1639 1949 1472 1162  923  840  300]
 [   2 1886  375 1351  518 1958  390 1695 1707 1080]
 [   3 1459  331  389  655 1483 1723 1672 1859  650]
 [   4   13  715 1470  608  459 1080 1654  665  154]]

如果稍微有點在意,使用“..., Flat“

"..."是聚類操作,聚類之後將每個向量映射到相應的bucket。該索引類型並不會保存壓縮之後的數據,而是保存原始數據,所以內存開銷與原始數據一致。通過nprobe參數控制速度/精度。
支持GPU,但是要注意,選用的聚類操作必須也支持。

index = faiss.index_factory(d, "IVF100, Flat")
index.train(data)
index.add(data)
dis, ind = index.search(data[:5], 10)
print(ind)

結果爲:
[[   0  879  981 1401  919  143    2  807 1515 1393]
 [   1  511 1504  987  747  422 1911  638  851 1198]
 [   2  879  807  981 1401 1143  733  441 1324 1280]
 [   3  740  155 1337 1578 1181 1743  290  588 1340]
 [   4 1176  256 1186  574 1459  218  480 1828  942]]

如果很在意,使用”PCARx,...,SQ8“

如果保存全部原始數據的開銷太大,可以用這個索引方式。包含三個部分,
1.降維
2.聚類
3.scalar 量化,每個向量編碼爲8bit 不支持GPU

index = faiss.index_factory(d, "PCAR16,IVF50,SQ8")  #每個向量降爲16維
index.train(data)
index.add(data)
dis, ind = index.search(data[:5], 10)
print(ind)

結果爲:
[[   0  671  196 1025  624 1521  724  879 1281  533]
 [   1 1008  698  206  101  657  294  383  700  574]
 [   2 1594  754 1850  266  559  154 1723 1949 1910]
 [   3 1758  820  869 1067   14  211 1214   78 1445]
 [   4 1457  466  557 1604 1951  912  736 1974  836]]

如果非常非常在意,使用"OPQx_y,...,PQx"

y需要是x的倍數,一般保持y<=d,y<=4*x。 支持GPU。

index = faiss.index_factory(d, "OPQ32_512,IVF50,PQ32")  
index.train(data)
index.add(data)
dis, ind = index.search(data[:5], 10)
print(ind)
[[   0 1686 1186 1552   47  517 1563 1738 1748  125]
 [   1  747 1816   41 1599  380 1179  803 1964  422]
 [   2 1610 1886  928  397  874  676  535 1401  929]
 [   3  548   89  509 1337  865 1472 1210 1181 1578]
 [   4  260 1781 1001 1179   41   20  747 1803 1055]]

當數據集很大該怎麼辦

這個就需要使用上面"..."的聚類方法。數據集被聚類成多個桶,在搜索時,只訪問了一小部分桶(nprobe個桶)

聚類是在數據集向量的一個有代表性的樣本上執行的,通常是數據集的一個樣本。我們指出了這個樣本的最佳尺寸。(這裏也不懂什麼意思)

如果低於1M條矢量:"...,IVFx,..."

N是數據集中向量個數,x一般取值[4sqrt(N),16sqrt(N)],需要30x ~ 256x個向量的數據集去訓練。

GPU支持:是。

老版本推薦

如果在1M-10M,使用"...,IMI2x10,..."

使用k-means將訓練集聚類爲2^10個類,但是執行過程是在數據集的兩半部分獨立執行,即聚類中心有2^(2*10)個。

如果在10M-100M,使用"...,IMI2x12,..."

如果1M-10M條矢量:"...,IVF65536_HNSW32,..."

IVF與HNSW聯合使用HNSW進行聚類分配(不懂)。你將需要在30*65536和256*65536之間數量的數據進行訓練。

GPU支持:否(在GPU上,使用上述IVF)。

如果10M-100M:"...,IVF262144_HNSW32,..."

與上述相同,將65536替換爲262144(2^18)。請注意,訓練將是很慢的。僅在gpu上進行訓練是可能的,所有在cpu上運行的東西,請參閱列車IVF_與__gpu.ipynb.

import numpy as np
import faiss

faiss.get_num_gpus()
#Out[2]:
#8

index = faiss.index_factory(128, "PCA64,IVF16384_HNSW32,Flat")
# get some training data
xt = faiss.rand((1000000, 128))


# baseline training without GPU
index.train(xt)
#CPU times: user 48min 58s, sys: 3min 44s, total: 52min 42s
#Wall time: 6min 28s

index2 = faiss.index_factory(128, "PCA64,IVF16384_HNSW32,Flat")
index_ivf = faiss.extract_index_ivf(index2)
clustering_index = faiss.index_cpu_to_all_gpus(faiss.IndexFlatL2(64))
index_ivf.clustering_index = clustering_index
# training with GPU
index2.train(xt)
#CPU times: user 8.82 s, sys: 2.51 s, total: 11.3 s
#Wall time: 3.62 s

如果100M-1B條矢量:"...,IVF1048576_HNSW32,..."

與上述相同,將65536替換爲1048576(2^20)。訓練會更慢!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章