向量检索常见面试篇
来源:AiGC面试宝典 作者:宁静致远 发布时间:2024年1月12日
一、向量检索库总结
1.1 Annoy
1.1.1 Annoy 介绍
Annoy 是高维空间求近似最近邻的开源库。全称:Approximate Nearest Neighbors Oh Yeah,是一种适合实际应用的快速相似查找算法。
📝通俗解释:Annoy 的核心思想是将高维向量映射到低维空间,通过构建多棵随机二叉树来加速最近邻搜索。想象一下你在一个陌生的城市找最近的餐厅,Annoy 就像先把你带到一个大致区域,然后再细找,比逐个问所有餐厅要快得多。
Annoy 构建一个随机二叉树森林,查询时间为 $O(\log n)$。
📝通俗解释:$O(\log n)$ 意味着搜索时间随着数据量增长很慢。比如数据量从1000增加到100万,搜索时间只增加约3倍,非常高效。
1.1.2 Annoy 使用
通过 pip 安装:
pip install annoy基本使用示例:
from annoy import AnnoyIndex
import random
# 向量的维度
f = 40 # Length of item vector that will be indexed
# 返回一个可读可写的存储 f 维向量的索引
t = AnnoyIndex(f, 'angular')
for i in range(1000):
# random.gauss 为随机生成高斯分布的随机数
v = [random.gauss(0, 1) for z in range(f)]
# 在位置 i 添加向量
t.add_item(i, v)
# 建立 n_trees 的森林,树越多,精度越高
t.build(10) # 10 trees
# 保存索引到文件
t.save('test.ann')
# 加载索引
u = AnnoyIndex(f, 'angular')
u.load('test.ann') # super fast, will just mmap the file
# 查询:返回最接近 item 0 的 1000 个近邻
print(u.get_nns_by_item(0, 1000))📝通俗解释:上面的代码展示了 Annoy 的完整使用流程——创建索引、添加向量、构建索引、保存和加载。
angular是距离度量方式,类似于余弦相似度。
向量检索常用函数:
| 函数 | 说明 |
|---|---|
get_nns_by_item(i, n, search_k=-1, include_distances=False) | 根据 item ID 查询 n 个最近邻 |
get_nns_by_vector(v, n, search_k=-1, include_distances=False) | 根据向量查询 n 个最近邻 |
get_item_vector(i) | 获取索引 i 对应的向量 |
get_distance(i, j) | 返回 item_i 和 item_j 的平方距离 |
📝通俗解释:
get_nns_by_item常用于已知某个物品,找相似物品;get_nns_by_vector常用于给定用户 embedding,找推荐物品。
索引属性函数:
| 函数 | 说明 |
|---|---|
get_n_items() | 返回索引中的 items 个数 |
get_n_trees() | 返回索引树的个数 |
关键参数调优:
n_trees:在构建期间设置,影响构建时间和索引大小。值越大,结果越准确,但索引越大。search_k:在运行时设置,影响搜索性能。值越大,结果越准确,但查询时间越长。默认为n_trees * n。
📝通俗解释:
n_trees像是"多找几个朋友帮忙推荐",树越多,推荐越准但记忆(存储)需要越多;search_k像是"问多少人",问得多准但累。
1.2 Faiss
GitHub:https://github.com/facebookresearch/faiss Tutorial:https://github.com/facebookresearch/faiss/wiki/Getting-started
1.2.1 Faiss 介绍
Faiss 库是由 Meta(原 Facebook) 开发的适用于稠密向量匹配的开源库,支持 C++ 与 Python 调用。Faiss 提供了高效的索引类库,是向量化检索的开山鼻祖。
📝通俗解释:Faiss 是向量检索领域的"老前辈",很多其他库都是在 Faiss 基础上封装的。它就像一个"向量加速器",能让大规模的向量相似度计算变得飞快。
Faiss 支持多种向量检索方式,包括内积、欧氏距离等,同时支持精确检索与近似搜索。
📝通俗解释:精确检索就像逐一比对所有候选人找出最相似的;近似搜索则像通过简历快速筛选,再精细面试。Faiss 两者都支持。
1.2.2 Faiss 主要特性
| 特性 | 说明 |
|---|---|
| 支持相似度检索和聚类 | 既能找相似向量,也能对向量分组 |
| 支持多种索引方式 | 如 IVF、PQ、HNSW 等,适用于不同场景 |
| 支持 CPU 和 GPU 计算 | GPU 加速适合超大规模数据 |
| 支持 Python 和 C++ 调用 | 方便不同技术栈使用 |
📝通俗解释:Faiss 的多种索引方式就像不同的"找人之道"——有的是先分区再找(IVF),有的是压缩信息来快速比较(PQ),有的是用图结构导航(HNSW)。
1.2.3 Faiss 使用
pip install faiss-cpu # 或者 pip install faiss-gpuFaiss 使用流程(三个步骤):
- 构建向量库:对已知数据进行向量化,最终以矩阵形式表示
- 选择索引:为矩阵选择合适的 index,将向量 add 到 index 中
- 搜索:search 得到最终结果
完整示例:
import numpy as np
import faiss
# 参数设置
d = 64 # 向量维度
nb = 100000 # 数据库大小
nq = 10000 # 查询数量
# 构建向量库(训练数据)
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.
# 步骤2:创建索引(使用 L2 距离的 Flat 索引)
index = faiss.IndexFlatL2(d)
index.add(xb) # 将向量添加到索引
# 步骤3:搜索
k = 4 # 返回最近邻的个数
D, I = index.search(xq[:5], k) # D 返回距离,I 返回索引
print("距离:", D)
print("索引:", I)📝通俗解释:这个例子展示了最基础的 Faiss 用法:
IndexFlatL2是最简单但最准确的索引(暴力搜索),适合小规模数据。d是向量维度,nb是底库大小,k是要返回的近邻数量。
1.3 Milvus
官网:https://milvus.io/ 中文社区:https://gitee.com/milvus-io/milvus
Milvus 是一款开源的特征向量相似度搜索引擎,使用方便、实用可靠、易于扩展、稳定高效和搜索迅速。
| 特性 | 说明 |
|---|---|
| 高性能 | 涵盖 Faiss、Annoy 和 HNSWlib 等主流第三方索引库,性能高,支持对海量向量数据进行相似搜索 |
| 高可用、高可靠 | 支持使用 Kubernetes 部署,支持在云上扩展;容灾能力保证服务高可用;采用日志及数据分离的设计,使用 Pulsar、Kafka 等消息队列实现组件间通信 |
| 混合查询 | 支持在向量检索过程中进行标量字段过滤,实现混合查询(向量 + 条件) |
| 开发者友好 | 支持 Python、Java、Go、Node.js 等多语言;提供 Attu 等可视化工具 |
📝通俗解释:Milvus 是一个完整的向量数据库系统,就像 MySQL 存储结构化数据一样,Milvus 专门存储和检索向量数据。它内置了多种索引算法,可以一键部署使用,适合生产环境。
1.4 ElasticSearch
1.4.1 ElasticSearch 介绍
Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,建立在全文搜索引擎 Apache Lucene 基础上。
📝通俗解释:Elasticsearch 最初是为全文搜索设计的,比如在大量文章中快速找到包含某个关键词的内容。现在也支持向量检索(通过 k-NN 插件)。
Elasticsearch 不仅包括全文搜索功能,还可以:
- 分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索
- 实时分析的分布式搜索引擎
- 可以扩展到上百台服务器,处理 PB 级别的结构化或非结构化数据
ES 本质上是一个支持全文搜索的分布式内存数据库,特别适用于构建搜索系统,比如内容检索、文本检索、日志检索。其原因是采用了倒排索引。
📝通俗解释:ES 适合做搜索系统,但原始的 ES 并不是为向量检索设计的。如果要做高维向量检索,建议使用 ES 8.0+ 的 k-NN 插件或结合 Milvus 使用。
1.4.2 什么是倒排索引?
倒排索引是一种专为搜索设计的索引结构。
先对需要索引的字段进行分词,然后以分词为索引组成一个查找树,这样就把一个全文匹配的查找转换成了对树的查找。
📝通俗解释:想象一本书的目录(正排索引:章节→页码)和索引词(倒排索引:关键词→页码)。当你搜索"苹果"时,倒排索引直接告诉你"苹果"在哪些页面,不需要逐页翻找。
倒排索引相比于一般数据库采用的 B 树索引,写入和更新性能较差,因此倒排索引只适合全文搜索,不适合更新频繁的交易类数据。
📝通俗解释:倒排索引就像把一本小说的所有词汇都做成卡片,每张卡片标明在哪些页出现。找词快了,但每次小说内容改动(比如修改错别字),就要重新整理很多卡片。
1.4.3 ES 机制
Elasticsearch 是面向文档型数据库,一条数据就是一个文档,用 JSON 作为文档序列化的格式。
示例文档:
{
"name": "John",
"sex": "Male",
"age": 25,
"birthDate": "1990/05/01",
"about": "I love to go rock climbing",
"interests": ["sports", "music"]
}Elasticsearch 比较适合存储非结构化或半结构化数据。
📝通俗解释:ES 存储的是 JSON 文档,不像关系型数据库需要预先定义严格的表结构,非常灵活。
关系型数据库与 Elasticsearch 术语对照:
| 关系型数据库 | → | Elasticsearch |
|---|---|---|
| 数据库 | → | 索引(Index) |
| 表 | → | 类型(type) |
| 行 | → | 文档(Document) |
| 列(Columns) | → | 字段(Fields) |
📝通俗解释:可以把 Elasticsearch 的"索引"理解为关系型数据库的"数据库",里面可以存放各种类型的文档。就像一个大型图书馆,不同类型的书放在不同的书架上。
总结对比
| 向量检索库 | 特点 | 适用场景 |
|---|---|---|
| Annoy | 轻量级、内存占用小、适合静态数据 | 中小规模向量检索、Spotify 音乐推荐 |
| Faiss | 性能高、支持 GPU、算法丰富 | 大规模向量检索、深度学习向量匹配 |
| Milvus | 完整数据库功能、支持混合查询 | 生产环境、向量化搜索系统 |
| ElasticSearch | 全文搜索能力强、支持 k-NN | 已有 ES 生态、需要文本+向量混合搜索 |
📝通俗解释:选型建议——小数据、快速验证用 Annoy;大规模、高性能用 Faiss;生产环境、需要完整数据库功能用 Milvus;已有 ES 生态用 ES 的 k-NN 插件。