Faiss 入门

1. Faiss 原理

  • 单元-探测(Cell-probe) 方法

以失去保证以找到最近邻居为代价来加速该过程的典型方法是采用诸如k均值的分区技术。 相应的算法有时被称为 cell-probe 方法:

我们使用基于多探测的基于分区的方法(可以联想到best-bin KD-tree的一种变体)。

特征空间被划分为 ncells 个单元格。
由于散列函数(在k均值的情况下,对最靠近查询的质心的分配),数据库向量被分配给这些单元中的一个,并且存储在由ncells反向列表形成的反向文件结构中。
在查询时,会选择一组 nprobe 个的反向列表
将查询与分配给这些列表的每个数据库向量进行比较
这样做,只有一小部分数据库与查询进行比较:作为第一个近似值,这个比例是 nprobe / ncells,但请注意,这个近似值通常被低估,因为反向列表的长度不相等。 当未选择给定查询的最近邻居的单元格时,将显示失败案例。

在C++中,相应的索引是索引IndexIVFFlat。

构造函数将索引作为参数,用于对反转列表进行赋值。 在该索引中搜索查询,并且返回的向量id(s)是应该被访问的反向列表。

  • 具有平坦索引作为粗量化器的单元探测方法

一般的,我们使用一个 Flat index 作为粗糙量化。 IndexIVF 的训练方法给 flat index 添加了质心。 nprobe 的值在搜索时设置(对调节速度-准确比很管用)。

注意: 根据经验,n 表示要被索引的点的数量, 一般确定合适质心数量的方法是在“分配向量到质心的开销(如果是纯粹的kmeans:ncentroids * d)” 和 “解析反转列表时执行的精确距离计算的数量(按照 kprobe / ncells * n * C 的顺序,其中常量 C 考虑了列表的不均匀分布, 以及当使用质心批处理时单个矢量比较更有效的事实,比如 C = 10)”之间找平衡。

这导致了许多质心的数量都遵循 ncentroids = C * sqrt(n)。

注意:在引擎盖下,IndexIVFKmeans 和 IndexIVFSphericalKmeans 不是对象,而是返回正确设置的 IndexIVFFlat 对象的函数。

警告:分区方法容易受到维数的诅咒。对于真正高维数据,实现良好的召回需要具有非常多的probe。

  • 基于量化的方法

2. Faiss 安装流程

  • 安装OpenBLAS
tar -xvf OpenBLAS-0.3.2.tar.gz
cd OpenBLAS-0.3.2
make
make PREFIX=/usr/local/ install
  • 安装lapack
yum install lapack-devel.x86_64 -y
  • 安装faiss
./configure -with-blas="-L/usr/local/lib -lopenblas -llapack -lgfortran -lpthread" --without-cuda
make && make install

3. 核心API说明

  • 定义核心量化器(通常使用多列倒排索引,实现更快训练)
faiss::MultiIndexQuantizer coarse_quantizer (d, nhash, nbits_subq);
  • 索引初始化
faiss::IndexIVFFlat index (&coarse_quantizer, d, ncentroids, metric);
faiss::IndexIVFPQ index (&coarse_quantizer, d, ncentroids, bytes_per_code, 8);

  • 训练模型
std::vector <float> trainvecs (nt * d);
index.train (nt, trainvecs.data());
  • 给索引添加数据
std::vector <float> database (nb * d);
std::vector <long> ids (nb);
index.add (nb, database.data());
 index.add_with_ids (end - begin, database.data() + d * begin, ids.data() + begin);
  • 搜索数据
std::vector<float> queries;
std::vector<faiss::Index::idx_t> nns (k * nq);
std::vector<float> dis (k * nq);
index.search (nq, queries.data(), k, dis.data(), nns.data());
  • 索引保存
faiss::write_index(&index, "/tmp/populated_index.faissindex");
  • 索引读取
 faiss::Index * idx = faiss::read_index("/tmp/trained_index.faissindex");
  • IndexShards:组合来自多个索引的结果

当数据集分布在多个索引上时,可以通过它们调度查询,并将结果与IndexShards 结合使用。 如果索引分布在多个GPU上并且查询可以并行完成,这也很有用. 请参阅在 GpuClonerOptions中 将 shards 设置为 true 的 index_cpu_to_gpus 。

3. 索引选型

  • 精确搜索

IndexFlatL2 不压缩向量,但不会在它们之上增加开销。 它不支持添加id(add_with_ids),只支持顺序添加,因此如果需要 add_with_ids,请使用“IDMap,Flat”。支持GPU。

  • 内存无限制

HNSWx 非常快速和准确的索引。 x 的范围是[4, 64],它表示了每个向量的链接数量,越大越精确,但是会使用越多的内存。只支持顺序添加(不是add_with_ids),所以在这里再次使用 IDMap 作为前缀(如果需要)。 HNSW 不需要训练,也不支持从索引中删除矢量。不支持GPU。

  • 内存限制

“…,Flat”  “…” 表示必须事先执行数据集的聚类(如下所示)。 在聚类之后,“Flat”只是将向量组织到不同桶中,因此它不会压缩它们,存储大小与原始数据集的大小相同。 速度和精度之间的权衡是通过 nprobe 参数设置的。支持GPU,聚类方法也需要支持GPU。

  • 内存限制严重

“PCARx,…,SQ8” 如果存储整个向量太昂贵,则执行两个操作:(1) 使用尺寸为x的PCA以减小尺寸
(2) 每个矢量分量的标量量化为1个字节。因此,总存储量是每个向量 x 个字节。不支持GPU。

“OPQx_y,…,PQx” Qx 代表了通过一个product quantizer压缩向量为 x 字节。 x 一般 <= 64,对于较大的值,SQ 通常是准确和快速的。OPQ 是向量的线性变换,使其更容易压缩。 y是一个维度: (1)y是x的倍数(必需)(2)y <= d,d为输入向量的维度(最好)
(3)y <= 4*x(最好)

4. 数据集和索引选型

  • 如果向量数量低于1百万:”…,IVFx,…”

当数据集的数量为 N 时,那么 x 应该处于 4 * sqrt(N) 和 16 * sqrt(N) 之间。 这只是用k-means聚类向量。 你需要 30 * x 到 256 * x 的矢量进行训练(越多越好)。支持 GPU。

  • 如果向量数量位于1百万-1千万之间:”…,IMI2x10,…”

IMI在训练向量上执行具有2^10个质心的 k-means,但它在向量的前半部分和后半部分独立地执行。 这将簇的数量增加到 2^(2 * 10)。您将需要大约64 * 2 ^ 10个向量进行训练。 不支持GPU。

  • 如果向量数量位于1千万-1亿之间:”…,IMI2x12,…”

与上面相同,将10替换为12。不支持GPU。

  • 如果向量数量位于1亿-10亿之间:”…,IMI2x14,…”

与上面相同,将10替换为14。不支持GPU。

5. Faiss ID映射

默认情况下,Faiss 为添加到索引的向量分配顺序 id。 本页介绍如何将其更改为任意ID。

一些Index类实现了 add_with_ids 方法,除了向量之外,还可以提供64位向量id。 在搜索时,类将返回存储的id而不是初始向量。

IndexIDMap:此索引封装了另一个索引,并在添加和搜索时转换ID。它维护一个带有映射的表。

index = faiss.IndexFlatL2(xb.shape[1]) 
ids = np.arange(xb.shape[0])
index.add_with_ids(xb, ids)  # this will crash, because IndexFlatL2 does not support add_with_ids
index2 = faiss.IndexIDMap(index)
index2.add_with_ids(xb, ids) # works, the vectors are stored in the underlying index

IndexIVF 中的 IDs: IndexIVF 子类始终存储矢量ID。 因此,IndexIDMap 的附加表是浪费空间。 IndexIVF 本身提供 add_with_ids。

参考文档:

https://waltyou.github.io/Faiss-Introduce/
https://waltyou.github.io/Faiss-Indexs/#%E6%8C%91%E4%B8%80%E4%B8%AA%E5%90%88%E9%80%82%E7%9A%84-index

https://github.com/facebookresearch/faiss/wiki/Faiss-indexes

### FAISS 安装教程与快速入门 FAISS 是一个用于高效相似度搜索和聚类的库,广泛应用于大规模数据集上的向量检索场景。以下是关于 FAISS 的安装方法以及快速入门的内容。 #### 一、FAISS 支持的操作系统 FAISS 能够运行于多种主流操作系统上,包括 Linux、macOS 和 Windows。这使得开发者可以在不同平台上轻松部署其功能[^3]。 #### 二、通过 Pip 工具安装 FAISS 为了简化安装流程,FAISS 提供了基于 Python 的 pip 包管理工具的支持。这是最为推荐的方式之一,因为它能够自动解决依赖关系并完成环境配置。具体命令如下所示: ```bash pip install faiss-cpu ``` 如果目标设备具备 NVIDIA GPU 并希望利用 CUDA 加速,则可以改用以下命令来安装带有 GPU 支持的版本: ```bash pip install faiss-gpu ``` 需要注意的是,在执行上述命令之前,请确认已正确设置好对应的开发环境,尤其是当涉及 GPU 版本时需预先满足特定硬件条件及驱动程序需求[^2]。 #### 三、从源码编译安装 FAISS (可选) 除了使用 pip 进行便捷式安装外,用户还可以选择下载 FAISS 源代码自行编译。这种方式适合那些需要定制化修改或者针对特殊平台优化的情况。不过此部分内容较为复杂,通常仅适用于高级使用者或有特别需求的情形下采用。 #### 四、FAISS 快速入门实例 下面给出一段简单的例子展示如何创建一个基本的索引结构,并实现最近邻查找操作: ```python import numpy as np import faiss # 设定维度大小 d=64 d = 64 nb = 100 # 数据集中样本数量 np.random.seed(1234) # 设置随机种子以便结果重现 data = np.random.random((nb, d)).astype('float32') # 构造测试数据集合 # 创建 L2 距离度量下的平面索引(index type),无需训练阶段 index = faiss.IndexFlatL2(d) print(f"Is trained: {index.is_trained}") # 验证当前状态是否已完成初始化准备 # 将生成的数据加入到索引当中去 index.add(data) k = 4 # 查询返回前四个最接近的结果数目 query_vector = data[:5] # 使用前五个作为查询向量 distances, indices = index.search(query_vector, k) print(distances) # 输出对应的距离值数组 print(indices) # 打印匹配项的位置编号列表 ``` 以上脚本展示了怎样定义一个基础类型的索引来存储高维空间中的点位信息,并演示了一个典型的 KNN(K Nearest Neighbor)算法应用场景[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值