官方教程
開始
對於以下內容,我們假設已安裝Faiss。 我們在C ++和Python中提供代碼示例。 可以通過複製/粘貼代碼或從Faiss發行版的tutorial /子目錄運行代碼來運行代碼。
生成一些數據
Faiss處理固定維度d的向量集合,通常爲幾十到幾百。 這些集合可以存儲在矩陣中。 我們假設行主存儲,例如,矢量編號i的第j個分量存儲在矩陣的第i行,第j列中。 Faiss僅使用32位浮點矩陣。
我們需要兩個矩陣:
- 數據庫的xb,包含必須編入索引的所有向量,以及我們要搜索的向量。其大小爲nb-by-d
- 查詢向量的xq,我們需要找到最近的鄰居。 它的大小是nq-by-d。 如果我們有一個查詢向量,則nq = 1
在下面的例子中,我們將使用在d = 64維中以均勻分佈繪製的向量。 出於趣味性,我們在第一維上添加小的平移,第一維取決於矢量索引。
Python
import numpy as np
d = 64 # dimension
nb = 100000 # database size
nq = 10000 # nb of queries
np.random.seed(1234) # make reproducible
xb = np.random.random((nb, d)).astype('float32')
xb[:, 0] += np.arange(nb) / 1000.
xq = np.random.random((nq, d)).astype('float32')
xq[:, 0] += np.arange(nq) / 1000.
C++
int d = 64; // dimension
int nb = 100000; // database size
int nq = 10000; // nb of queries
float *xb = new float[d * nb];
float *xq = new float[d * nq];
for(int i = 0; i < nb; i++) {
for(int j = 0; j < d; j++) xb[d * i + j] = drand48();
xb[d * i] += i / 1000.;
}
for(int i = 0; i < nq; i++) {
for(int j = 0; j < d; j++) xq[d * i + j] = drand48();
xq[d * i] += i / 1000.;
}
此示例使用普通數組,因爲這是所有C ++矩陣庫支持的最低公分母。 Faiss可以容納任何矩陣庫,只要它提供指向底層數據的指針。 例如,std :: vector 的內部指針由data()方法給出。
建立一個index並向裏面加入矢量
Faiss是圍繞Index對象構建的。 它封裝了一組數據庫向量,並可選擇預處理它們以使搜索更有效。 有許多類型的索引,我們將使用最簡單的版本,它只對它們執行brute-force L2距離搜索:IndexFlatL2
所有索引都需要知道它們何時構建,這是它們運行的向量的維數,在我們的例子中是d。 然後,大多數索引還需要訓練階段,以分析向量的分佈。 對於IndexFlatL2,我們可以跳過此操作。
構建和訓練索引時,可以對索引執行兩個操作:添加和搜索。
要向索引添加元素,我們在xb上調用add。 我們還可以顯示索引的兩個狀態變量:is_trained,一個指示是否需要訓練的布爾值和ntotal,即索引向量的數量。
某些索引還可以存儲與每個向量(但不是IndexFlatL2)對應的整數ID。 如果沒有提供ID,則add只使用向量序號作爲id,eg. 第一個矢量得0,第二個1等。
Python
import faiss # make faiss available
index = faiss.IndexFlatL2(d) # build the index
print(index.is_trained)
index.add(xb) # add vectors to the index
print(index.ntotal)
C++
faiss::IndexFlatL2 index(d); // call constructor
printf("is_trained = %s\n", index.is_trained ? "true" : "false");
index.add(nb, xb); // add vectors to the index
printf("ntotal = %ld\n", index.ntotal);
這應該只顯示true(索引已經過訓練)和100000(向量存儲在索引中)
可以對索引執行的基本搜索操作是k最近鄰搜索,即對於每個查詢向量,在數據庫中找到它的k個neighbors。
該操作的結果可以方便地存儲在大小爲nq-by-k的整數矩陣中,其中行i包含查詢向量i的鄰居的ID,按增加的距離排序。 除了該矩陣之外,搜索操作還返回具有相應平方距離的nq-by-k浮點矩陣。
結論:
這應該只顯示true(索引被訓練)和100000(向量存儲在索引中)。
搜索
可以對索引執行的基本搜索操作是k-最近鄰搜索,即。 對於每個查詢向量,在數據庫中找到它的k個最近鄰居。
該操作的結果可以方便地存儲在大小爲nq-by-k的整數矩陣中,其中行i包含查詢向量i的鄰居的ID,按增加的距離排序。 除了該矩陣之外,搜索操作還返回具有相應平方距離的nq-by-k浮點矩陣。
作爲一個完整性檢查,我們可以首先搜索一些數據庫向量,以確保最近的鄰居確實是向量本身。
Python
k = 4 # we want to see 4 nearest neighbors
D, I = index.search(xb[:5], k) # sanity check
print(I)
print(D)
D, I = index.search(xq, k) # actual search
print(I[:5]) # neighbors of the 5 first queries
print(I[-5:]) # neighbors of the 5 last queries
C++
int k = 4;
{ // sanity check: search 5 first vectors of xb
long *I = new long[k * 5];
float *D = new float[k * 5];
index.search(5, xb, k, D, I);
printf("I=\n");
for(int i = 0; i < 5; i++) {
for(int j = 0; j < k; j++) printf("%5ld ", I[i * k + j]);
printf("\n");
}
...
delete [] I;
delete [] D;
}
{ // search xq
long *I = new long[k * nq];
float *D = new float[k * nq];
index.search(nq, xq, k, D, I);
...
}
編輯提取是因爲否則C ++版本變得非常冗長,請參閱Faiss的tutorial / cpp子目錄中的完整代碼。
結論:
理智檢查的輸出應該是這樣的
[[ 0 393 363 78]
[ 1 555 277 364]
[ 2 304 101 13]
[ 3 173 18 182]
[ 4 288 370 531]]
[[ 0. 7.17517328 7.2076292 7.25116253]
[ 0. 6.32356453 6.6845808 6.79994535]
[ 0. 5.79640865 6.39173603 7.28151226]
[ 0. 7.27790546 7.52798653 7.66284657]
[ 0. 6.76380348 7.29512024 7.36881447]]
ie.每個查詢的最近鄰居確實是向量的索引,並且相應的距離是0.並且在一行內,距離正在增加。
實際搜索的輸出類似於
[[ 381 207 210 477]
[ 526 911 142 72]
[ 838 527 1290 425]
[ 196 184 164 359]
[ 526 377 120 425]]
[[ 9900 10500 9309 9831]
[11055 10895 10812 11321]
[11353 11103 10164 9787]
[10571 10664 10632 9638]
[ 9628 9554 10036 9582]]
由於添加到矢量的第一個分量的值,數據集在d-dim空間中沿着第一軸被模糊。 因此,前幾個向量的鄰居在數據集的開頭附近,並且~10000左右的向量之一也在數據集中的索引10000附近。
在2016年的電腦上執行上述搜索需要大約3.3秒。