Milvus 稠密与稀疏向量核心解析
一、 核心概念
1. 稠密向量 (Dense Vector)
- 物理本质 :在多维空间中表现为一个绝对唯一、位置固定的"点"(或从原点出发的有向箭头)。
- 数据结构 :固定长度的高维浮点数数组(如智谱
embedding-2输出的 1024 维数组,在代码中表现为包含 1024 个float32元素的切片/列表)。 - 核心擅长 :重语义理解,轻字面匹配。能跨越字面表象,捕捉概念、意图、情感或跨模态特征(如图片、音频、多语言翻译)。
2. 稀疏向量 (Sparse Vector / BM25)
- 物理本质:基于全量词典维度的文本特征权重矩阵。
- 数据结构 :长度随词典大小波动(可达数万维),但绝大多数位置为 0,仅在命中关键词的维度上存在权重分值。
- 核心擅长 :精准字面匹配,拒绝胡乱联想。能确保专有名词、代码函数名、错误码、特定型号(如系列号、芯片名)的绝对对齐。
二、 实现原理与核心技术要点
1. 空间距离度量的抉择
在 Milvus 中,向量字段的度量类型(MetricType)必须在建索引与检索时显式指定且保持一致:
- L2(欧氏距离)
- 计算本质 :计算多维空间中两点间的绝对直线距离(∑(Ai−Bi)2\sqrt{\sum (A_i - B_i)^2}∑(Ai−Bi)2 )。值越小越相似。
- 适用场景 :图像/人脸识别、无监督聚类,或需要通过拼接特征并乘以系数来人为动态控制权重的场景(如:商品特征与用户画像拼接)。
- COSINE(余弦相似度)
- 计算本质:计算两个向量在多维空间中的夹角余弦值。值越大(越接近 1)越相似。
- 适用场景:文本长度严重不固定的原始稠密向量(能自动剥离文本长短带来的模长干扰,纯粹比对语义方向)。
- IP(内积 / 点积)
- 计算本质 :对应维度数值相乘后累加(∑AiBi\sum A_i B_i∑AiBi)。值越大越相似。
- 适用场景 :稀疏向量(BM25 唯一支持) ,或者已经过手动 L2 归一化处理的稠密向量。
2. 归一化 (Normalization) 的工程红利
- 数学变化:将空间中长短不一的向量投影并缩放到半径为 1 的单位超球面上,使其模长(长度)等于 1。
- 计算蜕变 :归一化后,复杂的余弦相似度公式的分母变为 1×11 \times 11×1,公式退化为纯粹的内积(IP)计算(COSINE=IP\text{COSINE} = \text{IP}COSINE=IP)。
- 工程价值 :省去了开平方与除法的高能耗操作,允许 Milvus 完美调用 CPU 的 SIMD 硬件指令集进行并行矩阵乘法,检索性能大幅暴增 。智谱
embedding-2等现代大模型在云端已默认完成此操作。
3. BM25 稀疏向量的抗噪机制
BM25 能够在不归一化的情况下直接使用内积(IP),因为其算法内部自带两道"刹车闸":
- 词频饱和机制 (k1k_1k1):限制单个词汇无休止刷分的上限,出现 100 次的得分和出现 5 次趋于一致。
- 篇幅惩罚机制 (bbb):引入文档长度与平均长度的比值,文章越长,分母越大,权重被整体稀释。
- 交集过滤:未被检索的无关词由于在查询向量中为 0,在内积计算中直接相乘归零,彻底消除了稠密向量中"长文本/高频词模长霸榜"的致命软肋。
4. 多路混合检索 (Hybrid Search) 的执行逻辑
- 分路封装 :将原始查询文本分别通过语义模型转化为稠密向量、通过词频算法转化为稀疏向量,并分别封装成
AnnSearchRequest任务对象(分别指定各自的anns_field、metric_type与limit候选集大小)。 - 原子提交 :将多路请求组合打包提交给 Milvus 的
hybrid_search接口。 - 重排融合:Milvus 底层并发计算后,通过融合算法(如 RRF 倒数排名融合或 WeightedRanker 权重重排)掐尖输出最终的 Top-K 文本结果。
💻 Python 落地代码实现
python
from pymilvus import MilvusClient, AnnSearchRequest, WeightedRanker
# 1. 准备多路召回的原始查询数据(必须先进行 Embedding 翻译)
user_query = "Milvus 怎么做分布式混合检索?"
query_dense = embeddings.embed_query(user_query) # 稠密向量:1024维浮点数 [0.015, -0.042, ...]
query_sparse = sparse_encoder.encode_query(user_query) # 稀疏向量:字典权重映射 {2831: 2.5, 9482: 1.8}
# 2. 封装第一路:稠密向量(语义)检索请求单
req_dense = AnnSearchRequest(
data=[query_dense],
anns_field="dense_vector_field",
param={"metric_type": "COSINE"}, # 选用余弦相似度剥离长短文本干扰
limit=50 # 先捞出 50 条候选集
)
# 3. 封装第二路:稀疏向量(字面关键词)检索请求单
req_sparse = AnnSearchRequest(
data=[query_sparse],
anns_field="sparse_vector_field",
param={"metric_type": "IP"}, # 稀疏向量天然且必须使用内积(IP)
limit=50 # 同样捞出 50 条候选集
)
# 4. 初始化 Milvus 客户端并执行多路原子提交
client = MilvusClient(uri="http://localhost:19530")
final_results = client.hybrid_search(
collection_name="enterprise_knowledge_base",
reqs=[req_dense, req_sparse], # 将两路请求打包作为一个原子操作提交
rerank=WeightedRanker(0.7, 0.3), # 混合重排:语义占 70% 权重,字面匹配占 30% 权重
limit=3, # 最终两路融合后,掐尖只要最精准的前 3 条
output_fields=["text_content"] # 额外随向量召回的原始文本内容
)
# 5. 打印最终合并重排后的精准结果
for hit in final_results[0]:
print(f"得分 (Score): {hit.distance}, 内容: {hit.entity.get('text_content')}")
三、 总结与注意事项
⚠️ 模型一致性铁律(重中之重)
数据的**写入(Insert)与查询(Search)**必须使用【完全相同】的 Embedding 模型。
- 若维度不同(如 1024 维对齐 1536 维):数学公式无法错位对齐,程序会直接崩溃(Crash)。
- 若维度相同但模型不同(如百度对齐智谱):由于各个模型的多维空间秩序与映射规则相互独立,会导致计算发生"错误的共鸣",检索结果退化为毫无逻辑的玄学瞎推荐。
💡 生产架构落地速查指南
| 业务场景 | 推荐检索组合方案 | 度量类型选择 | 架构设计建议 |
|---|---|---|---|
| 企业知识库 / 智能 RAG | 混合检索 (Hybrid) 稠密(语义)+ 稀疏(关键词) | 稠密:COSINE / IP 稀疏:IP |
推荐将 Embedding 逻辑收敛到统一的表征微服务,避免各业务线模型版本冲突。 |
| 高并发搜图 / 人脸识别 | 纯稠密向量检索 (Dense) | 优先 L2 (若归一化则选 IP) |
图片特征维度固定,应优先考虑硬件加速和聚类空间特性。 |
| 多特征个性化电商推荐 | 特征拼接稠密向量 | 强推 L2 |
将商品语义和用户画像拼接,在后端通过乘以标量系数动态调整商品的权重。 |