🔎 向量检索 ANN
近似最近邻搜索算法,亿级向量库中的高效检索技术
📖 技术概述
🎯 什么是 ANN 搜索?
近似最近邻(Approximate Nearest Neighbor, ANN)搜索是一种在大规模高维向量空间中快速查找与查询向量最相似向量的技术。与精确最近邻搜索不同,ANN 通过牺牲少量精度换取显著的速度提升。
核心问题:给定查询向量 q 和包含 n 个向量的数据库 D,找到 k 个与 q 最相似的向量。
相似度度量:
- 欧氏距离(L2 Distance) - 向量间的直线距离,越小越相似
- 余弦相似度(Cosine Similarity) - 向量夹角的余弦值,越大越相似
- 内积(Inner Product) - 向量点积,越大越相似
💡 为什么需要 ANN?
精确最近邻搜索(如暴力搜索)的时间复杂度为 O(n),在亿级向量库中完全不可行。ANN 算法通过以下策略实现亚线性时间复杂度:
- 空间划分 - 将向量空间划分为多个区域,只在相关区域搜索
- 哈希映射 - 将相似向量映射到相同或相近的哈希桶
- 图结构 - 构建导航图,通过贪心搜索快速定位
- 量化压缩 - 压缩向量表示,加速距离计算
🔧 主流算法
🎲 LSH - 局部敏感哈希
局部敏感哈希(Locality Sensitive Hashing, LSH)的核心思想是:相似的向量以高概率映射到相同的哈希桶中。
工作原理
- 设计哈希函数 h,使得相似向量碰撞概率高
- 使用多个哈希函数构建哈希表
- 查询时,计算查询向量的哈希值,只在对应桶中搜索
查看代码示例
# LSH 简易实现 - 基于随机超平面
import numpy as np
from collections import defaultdict
class LSHTable:
"""LSH 哈希表 - 随机超平面哈希"""
def __init__(self, num_hyperplanes, dim):
# 随机超平面法向量
self.hyperplanes = np.random.randn(num_hyperplanes, dim)
self.hash_table = defaultdict(list)
def _hash(self, vector):
"""计算向量的哈希值(二进制)"""
projections = np.dot(self.hyperplanes, vector)
# 根据投影正负生成二进制哈希
return tuple((projections > 0).astype(int))
def insert(self, idx, vector):
"""插入向量"""
hash_key = self._hash(vector)
self.hash_table[hash_key].append(idx)
def query(self, vector):
"""查询候选集"""
hash_key = self._hash(vector)
return self.hash_table[hash_key]
class LSHIndex:
"""LSH 索引 - 多哈希表增强"""
def __init__(self, dim, num_tables=10, num_hyperplanes=16):
self.tables = [
LSHTable(num_hyperplanes, dim)
for _ in range(num_tables)
]
def build(self, vectors):
"""构建索引"""
for idx, vec in enumerate(vectors):
for table in self.tables:
table.insert(idx, vec)
def search(self, query_vector, k=5):
"""搜索最近邻"""
# 收集所有候选
candidates = set()
for table in self.tables:
candidates.update(table.query(query_vector))
# 在候选集中精确计算距离
scores = []
for idx in candidates:
dist = np.linalg.norm(query_vector - self.vectors[idx])
scores.append((idx, dist))
# 返回 top-k
scores.sort(key=lambda x: x[1])
return scores[:k]
# 使用示例
np.random.seed(42)
n_samples, dim = 10000, 128
vectors = np.random.randn(n_samples, dim)
# 构建索引
lsh = LSHIndex(dim=dim, num_tables=10)
lsh.vectors = vectors # 存储原始向量
lsh.build(vectors)
# 查询
query = np.random.randn(dim)
results = lsh.search(query, k=5)
print(f"Top-5 最近邻:{results}")
🕸️ HNSW - 分层可导航小世界
HNSW(Hierarchical Navigable Small World)是目前最高效的 ANN 算法之一,结合了跳表(Skip List)和小世界图(Small World Graph)的思想。
核心思想
- 多层图结构 - 顶层稀疏用于长距离导航,底层稠密用于精细搜索
- 贪心搜索 - 从顶层入口开始,每层找到局部最优后进入下一层
- 可导航小世界 - 通过添加长边实现 O(log n) 搜索
算法特点
- 优点 - 搜索速度快、召回率高、支持动态插入
- 缺点 - 构建时间较长、内存占用大
- 应用 - Milvus、Qdrant、Weaviate 等向量数据库
| 参数 | 说明 | 影响 |
|---|---|---|
| M | 最大连接数 | 越大图越稠密,精度高但内存大 |
| efConstruction | 构建时搜索范围 | 越大构建质量越高,速度慢 |
| efSearch | 查询时搜索范围 | 越大召回率越高,速度慢 |
📦 乘积量化(Product Quantization)
乘积量化(PQ)是一种向量压缩技术,将高维向量分解为多个子向量,分别量化,大幅减少内存占用和计算量。
工作原理
- 将 D 维向量分解为 m 个子向量,每个 D/m 维
- 对每个子空间独立进行 k-means 聚类,得到 k 个聚类中心
- 每个子向量用最近的聚类中心 ID 表示(log₂k 比特)
- 原向量用 m 个 ID 组成的编码表示
距离计算优化
使用查找表(LUT)预计算查询向量与所有聚类中心的距离,查询时只需查表累加,避免实时计算。
🚀 FAISS - Facebook AI 相似性搜索
FAISS(Facebook AI Similarity Search)是 Meta 开源的向量检索库,集成了多种 ANN 算法,支持 GPU 加速。
核心索引类型
| 索引类型 | 算法 | 特点 |
|---|---|---|
| IndexFlatL2 | 暴力搜索 | 精确但慢,作为基准 |
| IndexIVFFlat | 倒排文件 | 空间划分,中等速度 |
| IndexIVFPQ | IVF + PQ | 压缩存储,速度快 |
| IndexHNSW | HNSW | 高质量,支持动态插入 |
| IndexIVFHNSW | IVF + HNSW | 结合两者优势 |
查看代码示例
# FAISS 使用示例
import faiss
import numpy as np
# 准备数据
d = 128 # 向量维度
nb = 100000 # 数据库大小
np.random.seed(42)
xb = np.random.random((nb, d)).astype('float32')
# ========== 1. 暴力搜索(精确) ==========
index_flat = faiss.IndexFlatL2(d)
index_flat.add(xb)
# 查询
nq = 10
xq = np.random.random((nq, d)).astype('float32')
D, I = index_flat.search(xq, k=5)
print(f"暴力搜索 - 距离:{D[0]}, 索引:{I[0]}")
# ========== 2. IVF 倒排索引 ==========
nlist = 100 # 聚类中心数量
quantizer = faiss.IndexFlatL2(d)
index_ivf = faiss.IndexIVFFlat(quantizer, d, nlist)
# 训练(需要)
index_ivf.train(xb)
index_ivf.add(xb)
# 设置查询参数
index_ivf.nprobe = 10 # 搜索的聚类数量
D, I = index_ivf.search(xq, k=5)
print(f"IVF 搜索 - 距离:{D[0]}, 索引:{I[0]}")
# ========== 3. IVF + PQ(压缩) ==========
m = 8 # 子向量数量
nbits = 8 # 每子向量比特数
index_pq = faiss.IndexIVFPQ(quantizer, d, nlist, m, nbits)
index_pq.train(xb)
index_pq.add(xb)
index_pq.nprobe = 10
D, I = index_pq.search(xq, k=5)
print(f"IVF+PQ 搜索 - 距离:{D[0]}, 索引:{I[0]}")
# ========== 4. HNSW ==========
M = 16 # 连接数
index_hnsw = faiss.IndexHNSWFlat(d, M)
index_hnsw.hnsw.efSearch = 64 # 查询参数
index_hnsw.add(xb)
D, I = index_hnsw.search(xq, k=5)
print(f"HNSW 搜索 - 距离:{D[0]}, 索引:{I[0]}")
# ========== 5. 索引持久化 ==========
faiss.write_index(index_hnsw, "hnsw.index")
loaded_index = faiss.read_index("hnsw.index")
📊 性能对比
⚡ 算法对比(100 万向量,128 维)
| 算法 | 构建时间 | 查询时间 | 召回率@10 | 内存占用 |
|---|---|---|---|---|
| 暴力搜索 | - | ~500ms | 100% | 512MB |
| LSH | ~10s | ~5ms | 60-80% | ~600MB |
| IVF | ~30s | ~2ms | 80-90% | ~550MB |
| IVF+PQ | ~60s | ~1ms | 75-85% | ~100MB |
| HNSW | ~120s | ~0.5ms | 95-99% | ~2GB |
* 数据仅供参考,实际性能取决于具体参数设置和硬件环境
🎯 选择指南
- 追求最高精度 - 暴力搜索或 HNSW(efSearch 调大)
- 追求最快速度 - HNSW 或 IVF+PQ
- 内存受限 - IVF+PQ(压缩比高)
- 动态数据 - HNSW(支持实时插入删除)
- 静态数据 - IVF 系列(构建一次,多次查询)
- 十亿级规模 - 多索引组合 + 分布式部署
💼 实际应用
🔍 应用场景
- 语义搜索 - 搜索引擎、文档检索
- 推荐系统 - 相似物品推荐、用户匹配
- 图像检索 - 以图搜图、人脸识别
- 去重检测 - 文本/图像/视频去重
- RAG 系统 - 检索增强生成,为大模型提供上下文
- 异常检测 - 识别远离正常分布的样本
🗄️ 向量数据库
| 数据库 | 核心算法 | 特点 |
|---|---|---|
| Milvus | HNSW, IVF, ANNOY | 开源、功能全面、支持混合查询 |
| Qdrant | HNSW | Rust 实现、高性能、支持过滤 |
| Pinecone | 专有算法 | 托管服务、易用、自动扩展 |
| Weaviate | HNSW | 支持 GraphQL、多模态 |
| Chroma | HNSW | 轻量级、适合本地开发 |