相似度与向量索引
概览篇说清了"为什么需要向量库",这一篇往下钻一层:相似度怎么算、索引为什么快。内容是书上看的 + 梳理的理解,还没有实操验证,标几个等我动手时要重点核对的点。
三种相似度:余弦、内积、欧氏
向量检索的本质是算"两个向量有多像",常用的就三种度量:
| 度量 | 含义 | 范围 | 备注 |
|---|---|---|---|
| 余弦相似度 | 两个方向的夹角,只看方向不看长度 | [-1, 1],越大越像 | 文本检索最常用 |
| 内积(点积) | 方向 × 长度一起算 | 不定 | 向量归一化后与余弦等价 |
| 欧氏距离 L2 | 空间直线距离,越小越近 | [0, ∞) | 图像特征常用 |
我理解的关键点:
- 文本 embedding 基本都会归一化(压成单位长度),此时三种度量排序结果完全一致(L2² = 2 − 2·cosθ,单调对应)——所以"选哪个"多数时候不纠结,跟着库默认走即可。
- 未归一化时内积会偏向"模长大"的向量,这是隐性坑:混着用不同长度的向量算内积,排序可能悄悄失真。
- 建库时就要定好度量方式(Chroma 默认 L2、建 collection 时可指定 cosine)——建完再换度量等于重建库,这是我标记的"动手时第一步要确认"的点。
为什么暴力检索不可行
最朴素的 KNN:查询向量和库里每条向量都算一遍距离,排个序取前 K。
问题在规模:100 万条 × 1536 维,每次查询要算 1 亿次浮点乘加,还要全部读内存——单次查询几百毫秒起步,而在线服务通常要求 <10ms。这就是维度灾难:维度一高,"距离"的计算量和区分度都崩了。
于是 ANN(近似最近邻)登场:放弃 100% 找到最近的,换几个数量级的速度。衡量代价的指标是 recall@K——ANN 返回的前 K 条与暴力检索真值前 K 条的重合率,生产一般要求 95%+。
三大索引家族
HNSW:分层图(当前最主流)
思想借鉴跳表:把图分成多层,上层稀疏、边长(跨度大),下层稠密、边短。查询从顶层出发贪心逼近,逐层下降,最后在底层精确搜索。
第 2 层: A ──────────────── E (稀疏,长边,快速跨域)
第 1 层: A ─── C ─── E ─── G (较密)
第 0 层: A - B - C - D - E - F - G (全量数据,短边,精细定位)
- 优点:查询快(毫秒级)、支持增量插入,是 Milvus/pgvector/Chroma 的默认或主力索引
- 关键参数:
M(每节点连接数,越大图越准越耗内存)、ef_construction(建图质量)、ef_search(查询时候选队列长度,调大它 = 更准但更慢,这是运行期最常调的旋钮) - 代价:图结构本身占内存大,内存富余的机器才玩得爽
IVF:倒排文件(聚类分桶)
先用 k-means 把向量空间聚成 nlist 个簇,每个簇记一个"质心";查询时只和质心比,挑最近的 nprobe 个簇进去精确算。
nprobe越大召回越高、速度越慢——和 HNSW 的 ef_search 一个道理,都是"扫多少"的旋钮- 特点:需要先用数据训练聚类;数据分布倾斜时(热门簇过大)会拖慢
- 适合超大规模 + 内存敏感的场景
PQ:乘积量化(压缩术)
严格说不是索引而是压缩:把 1536 维切成 m 段(比如 96 段 × 16 维),每段各自用 k-means 聚成 256 类,向量每段只用 1 字节存类号——6KB 压到 96 字节,代价是距离计算带量化误差。
实战里几乎都是组合拳:IVF-PQ(先分桶再段内压缩)是亿级向量的经典配方;HNSW 也有配 PQ 的方案。内存够就 HNSW 裸奔,内存紧再上量化。
💡 选型记忆:百十万条 → HNSW 随便选;千万到亿级、内存紧 → IVF-PQ。其余参数(ef_search / nprobe)先给个中等值,用 recall 测试集调,不背数字。
混合过滤:先过滤还是后过滤
真实业务几乎都是"带条件的相似检索"(比如:只在这个用户的知识库里搜)。条件过滤和 ANN 的顺序是个真问题:
- post-filter(先检索后过滤):先 ANN 拿 top-K 再按条件筛——风险是筛完剩不了几条(查出来的 K 条全不满足条件)
- pre-filter(先过滤后检索):先按条件筛出子集再做 ANN——子集分布变了,可能伤召回
- 主流库各有各的处理(Milvus 内部做了混合策略),用的时候查对应文档,别想当然——这是我标记的第二个"动手核对"点
⬅️ 00-向量数据库概览 🏠 00-数据库 ➡️ 02-Embedding 模型与文本切块
💬 评论