官方教程
对于以下内容,我们假设已安装Faiss。 我们在C ++和Python中提供代码示例。 可以通过复制/粘贴代码或从Faiss发行版的tutorial /子目录运行代码来运行代码。
Faiss处理固定维度d的向量集合,通常为几十到几百。 这些集合可以存储在矩阵中。 我们假设行主存储,例如,矢量编号i的第j个分量存储在矩阵的第i行,第j列中。 Faiss仅使用32位浮点矩阵。
我们需要两个矩阵:
在下面的例子中,我们将使用在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()方法给出。
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秒。