Faiss向量检索引擎入门指南

入门指南

以下内容假设您已经安装了 Faiss。我们提供了 C++ 和 Python 的代码示例。您可以直接复制粘贴这些代码,或者从 Faiss 发行版的 tutorial/
 子目录中运行它们。

获取数据

Faiss 处理固定维度 (d) 的向量集合,通常维度在几十到几百之间。这些集合可以存储在矩阵中。我们假设采用行主序存储,即第 (i) 个向量的第 (j) 个分量存储在矩阵的第 (i) 行、第 (j) 列。Faiss 只使用 32 位浮点矩阵。

我们需要两个矩阵:

  • xb
     用于数据库,包含所有需要索引的向量,我们将在其中进行搜索。其大小为 (nb \times d)。

  • xq
     用于查询向量,我们需要为这些向量查找最近邻。其大小为 (nq \times d)。如果只有一个查询向量,则 (nq=1)。

在以下示例中,我们将处理从 64 维均匀分布中抽取的向量。为了有趣起见,我们还根据向量索引在第一个维度上添加了一个小的平移量。

Python 版本

1
2
3
4
5
6
7
import numpy as npd =64                           # 维度nb =100000                      # 数据库大小nq =10000                       # 查询数量np.random.seed(1234)             # 使结果可复现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.
```

在 Python 中,矩阵始终以 NumPy 数组的形式表示。数据类型 dtype
 必须是 float32

### C++ 版本

int d =64;                            // 维度int nb =100000;                       // 数据库大小int nq =10000;                        // 查询数量floatxb =new float[d * nb];floatxq =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.;}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  
此示例使用普通数组,因为这是所有 C++ 矩阵库都支持的最低要求。Faiss 可以兼容任何矩阵库,只要它提供了底层数据的指针。例如,std::vector<float>
 的内部指针可以通过 data()
 方法获得。
## 构建索引并添加向量

Faiss 围绕 Index
 对象构建。它封装了数据库向量集合,并可选地对它们进行预处理,以提高搜索效率。有许多种索引类型,我们将使用最简单的版本,它仅执行基于 L2 距离的暴力搜索:IndexFlatL2


所有索引在构建时都需要知道它们操作的向量的维度,即我们这里的 (d)。然后,大多数索引还需要一个训练阶段,以分析向量的分布。对于 IndexFlatL2
,我们可以跳过这个操作。

当索引构建并训练完成后,可以在索引上执行两种操作:add
 和 search


要将元素添加到索引中,我们对 xb
 调用 add
。我们还可以显示索引的两个状态变量:is_trained
,一个布尔值,指示是否需要训练;ntotal
,索引向量的数量。

一些索引还可以存储与每个向量对应的整数 ID(但 IndexFlatL2
 不支持)。如果没有提供 ID,则 add
 只使用向量序号作为 ID,即第一个向量的 ID 为 0,第二个为 1,依此类推。
### Python 版本

import faiss                   # 使 Faiss 可用index = faiss.IndexFlatL2(d)   # 构建索引print(index.is_trained)index.add(xb)                  # 将向量添加到索引print(index.ntotal)

1
### C++ 版本  

faiss::IndexFlatL2 index(d);           // 调用构造函数printf(“is_trained = %s\n”, index.is_trained ? “true” : “false”);index.add(nb, xb);                     // 将向量添加到索引printf(“ntotal = %ld\n”, index.ntotal);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
### 结果  

这应该分别显示 true
(索引已训练)和 100000
(向量已存储在索引中)。
## 搜索

索引上可以执行的基本搜索操作是 (k) - 最近邻搜索,即对于每个查询向量,找到数据库中与其最近的 (k) 个最近邻。

此操作的结果可以方便地存储在一个大小为 (nq \times k) 的整数矩阵中,其中第 (i) 行包含查询向量 (i) 的最近邻的 ID,按距离递增排序。除了这个矩阵,search
 操作还返回一个 (nq \times k) 的浮点矩阵,其中包含相应的平方距离。

为了进行合理性检查,我们可以先搜索几个数据库向量,以确保最近邻确实是向量本身。
### Python 版本

k = 4                          # 我们希望看到 4 个最近邻D, I = index.search(xb[:5], k) # 合理性检查print(I)print(D)D, I = index.search(xq, k)     # 实际搜索print(I[:5])                   # 前 5 个查询的最近邻print(I[-5:])                  # 最后 5 个查询的最近邻

1
### C++ 版本  

int k =4;{       // 合理性检查:搜索 xb 的前 5 个向量    idx_t *I =new idx_t[k 5];    floatD =newfloat[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;}{       // 搜索 xq    idx_t I =new idx_t[k * nq];    floatD =newfloat[k * nq];    index.search(nq, xq, k, D, I);    …}

1
2
3
4
5
6
  
由于 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]]

1
2
3
4
  
即每个查询的最近邻确实是向量的索引,对应的距离为 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) 维空间中沿着第一个轴被拉伸。因此,前几个向量的最近邻位于数据集的开头附近,而大约在 10000 附近的向量的最近邻也位于数据集的 10000 附近。  
  
在 2016 年的机器上执行上述搜索大约需要 3.3 秒。  
  
  

![江达小记](/images/wechatmpscan.png)