黑白梦黑白梦

toggle navtoggle nav
  • 文章
  • 专栏
  • 文章
  • 专栏

向量度量基础与混合检索开发实战

发布于 2026-06-22

本文对比了稠密向量与稀疏向量的几何特征,并结合 PyMilvus SDK 提供了双路混合检索(加权分数融合与倒数排名融合 RRF 算法)从特征生成、多特征建表索引到单路与多路检索调用的开发实战。

向量度量基础

Embedding 向量基础

在 AI 与 NLP 领域,一段文本经过嵌入模型(Embedding Model)处理后,会被映射为一个包含数百乃至上千维度的浮点数组。这个数组在高维空间中构成一条有方向、有长度的"箭头":

  • 方向:编码了文本的核心语义特征。语义相近的文本,其向量方向在空间中高度一致。
  • 模长(大小):可能携带信息强度、置信度等附加特征,但是否具有实际语义含义取决于具体模型。对于已做 L2 归一化的 Embedding,模长固定为 1,不再承载额外信息。

通过对比这些高维向量的夹角或几何距离,机器便能量化文本之间的语义相似度。

稠密向量与余弦相似度

  • 形态特征:固定长度(如 768 或 1024 维)的浮点数数组,几乎没有零值。
  • 核心优势:捕捉深层语义——即便两个句子字面完全不同(如"我想买台智能设备"与"苹果手机多少钱"),也能感知语义上的相近关系。
  • 度量方式:余弦相似度 (Cosine Similarity)。

关键特性:值域有界。 余弦相似度的值域被限制在 [-1, 1] 之间,值越接近 1 表示向量方向越一致(语义越相近)。相比于内积等值域无界的度量,余弦分数具备明确的几何含义,在同一模型、同一任务、同一语料分布下,可以通过标定获得相对稳定的业务阈值。但需注意:不同模型对同一组文本的余弦相似度打分区间存在系统性差异,阈值在切换模型、更换业务场景后必须重新标定。

稀疏向量与内积度量

  • 形态特征:维度极大(通常对应分词词表大小,动辄十万维)的数组,绝大多数位置的值为 0,只有文本中实际出现的词对应维度为非零值。
  • 核心优势:捕捉精准关键词匹配——对专有名词、产品型号、规范编号等,能确保严格的字面对齐,弥补稠密向量过度泛化的不足。
  • 度量方式:内积 / 点积 (Inner Product / Dot Product)。

关键特性:值域无界。 内积得分等于匹配词权重的累加,其上限完全取决于分词器设计和文本长度,可能是 [0, 1],也可能是 [0, 50] 甚至更高。不同模型、不同分词器产出的稀疏分数量级可能天差地别,无法直接跨模型比较。

L2 归一化分析

L2 归一化(L2 Normalization,又称欧几里得范数归一化)是向量空间中一种常见的缩放操作。

  • 通俗定义:先计算向量中所有元素的平方和,再对该平方和进行开根号,得到向量的“模长”(几何长度)。然后将向量中的每一个元素都除以这个模长。
  • 几何意义:经过 L2 归一化后,所有向量的模长(长度)都固定为 1.0。在几何空间中,这意味着把所有长短不一的向量都等比例缩放,使其终点刚好落在半径为 1 的超球面上。这样就只保留了向量的方向(语义关系),而抹平了因为长度或信息强度差异带来的影响。

稠密向量的 L2 归一化优化:

  • 在 Milvus 等检索引擎中,通常建议入库前对稠密向量做 L2 归一化(将模长缩放至 1.0)。这可以将开销较大的余弦相似度计算等价转化为高效的内积(点积)计算,从而大幅提升检索吞吐。
  • 当前阿里云 DashScope(text-embedding-v4)等托管嵌入服务返回的 dense embedding 经实测接近单位向量,可直接用于 Cosine/IP 检索。

对于 BM25 及其变体等传统稀疏检索,不建议额外进行 L2 归一化,因为会改变原有的长度归一化机制和权重分布。如果强行对这类稀疏特征做 L2 归一化,会产生长文本稀释效应:

  • 长文档中的关键词权重会被较多无关词均摊
  • 短文档中相同的关键词却会被分配到较高的归一化权重
  • 结果:包含精确匹配词的长文档,得分反而可能低于不相关但更短的文档

因此,此类稀疏匹配通常采用非归一化的点积累加,其分值上限在数学上不可控。(注:部分神经稀疏模型如 SPLADE 在数学上可以做归一化,此处主要指 BM25 类的传统稀疏表示)

混合向量生成与建表存储

在 RAG 检索系统中,我们需要为文档数据同时生成稠密特征(Dense)与稀疏特征(Sparse),并利用 PyMilvus SDK 的原生接口定义支持混合检索的多列索引集合。

混合向量特征生成

我们可以利用 DashScope 的 text-embedding-v4 模型同时生成两路特征。以下是调用 SDK 获取特征并转换为键值映射字典的 Python 示例:

import dashscope

def generate_hybrid_vectors(text: str, api_key: str) -> tuple[list[float], dict[int, float]]:
    """
    生成输入文本的稠密向量与稀疏向量特征
    """
    response = dashscope.TextEmbedding.call(
        model="text-embedding-v4",
        input=[text],
        output_type="dense&sparse",  # 同时获取两种表征
        api_key=api_key
    )

    emb_data = response.output["embeddings"][0]
    
    # 稠密向量为 1024 维浮点数列表
    dense_vector = emb_data["embedding"]
    
    # 将稀疏结果映射为 {token_id: weight} 的稀疏字典形式
    raw_sparse = emb_data["sparse_embedding"]
    sparse_vector = {item["index"]: item["value"] for item in raw_sparse}

    return dense_vector, sparse_vector

多特征建表与数据入库

有了特征向量后,我们使用 MilvusClient 的原生 API 创建带有双路特征字段的 Schema,分别为其建立专属索引,并完成数据写入。

from pymilvus import MilvusClient, DataType

# 1. 初始化客户端(支持本地 SQLite 数据库或远程连接)
milvus_client = MilvusClient(uri="./milvus_test.db")
collection_name = "travel_hybrid_docs"

# 模拟已生成的稠密与稀疏特征向量(实际应用中由上述 generate_hybrid_vectors 生成)
dense_vector = [0.1] * 1024
sparse_vector = {101: 0.9, 202: 0.7}

# 2. 定义支持多特征检索的集合结构 (Schema)
schema = milvus_client.create_schema(auto_id=True, enable_dynamic_field=True)
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True)
schema.add_field(field_name="text_content", datatype=DataType.VARCHAR, max_length=65535)
schema.add_field(field_name="dense_vector", datatype=DataType.FLOAT_VECTOR, dim=1024)
schema.add_field(field_name="sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR)

# 3. 分别为稠密向量和稀疏向量字段配置索引参数
index_params = milvus_client.prepare_index_params()

# 稠密索引使用 COSINE 余弦度量
index_params.add_index(
    field_name="dense_vector",
    index_name="dense_index",
    metric_type="COSINE",
    index_type="FLAT"
)
# 稀疏索引使用 IP 内积度量
index_params.add_index(
    field_name="sparse_vector",
    index_name="sparse_index",
    metric_type="IP",
    index_type="SPARSE_INVERTED_INDEX"
)

# 4. 创建集合 (Collection)
if not milvus_client.has_collection(collection_name):
    milvus_client.create_collection(
        collection_name=collection_name,
        schema=schema,
        index_params=index_params
    )

# 5. 混合写入稠密与稀疏特征向量
data_to_insert = [{
    "text_content": "北京故宫门票必须提前7天在线预约,入园时请务必刷身份证凭证。",
    "dense_vector": dense_vector,   # 1024 维稠密特征向量
    "sparse_vector": sparse_vector  # 稀疏特征字典 {token_id: weight}
}]
milvus_client.insert(
    collection_name=collection_name,
    data=data_to_insert
)

混合检索策略

混合检索的核心是并发执行稠密向量与稀疏向量检索,并将各自召回的文档集合融合成单一的排序队列,以达到在单次检索中平衡语义泛化与精确匹配的目的。

加权分数融合检索

https://milvus.io/docs/zh/weighted-ranker.md

加权分数融合通过为不同的检索通道分配不同的权重比例,将各通道得到的相似度得分进行线性加权求和,以此计算出最终的融合得分并进行重排。在执行时,系统通常先对两路分数进行归一化映射以消除量纲差异,然后再进行权重的乘积与累加。在 Milvus 中,我们通过原生构建两路子请求,并指定 WeightedRanker 重排器来实现这一融合策略。

from pymilvus import MilvusClient, AnnSearchRequest, WeightedRanker

milvus_client = MilvusClient(uri="./milvus_test.db")

# 模拟查询所对应的稠密与稀疏特征向量
query_dense = [0.1] * 1024
query_sparse = {101: 0.85, 202: 0.65}

# 1. 构造稠密与稀疏两路检索子请求
req_dense = AnnSearchRequest(
    data=[query_dense],                      # 稠密查询特征
    anns_field="dense_vector",
    param={"metric_type": "COSINE"},         # 稠密检索使用余弦相似度
    limit=5
)
req_sparse = AnnSearchRequest(
    data=[query_sparse],                     # 稀疏查询特征
    anns_field="sparse_vector",
    param={"metric_type": "IP"},             # 稀疏检索使用内积相似度
    limit=5
)

# 2. 调用 hybrid_search 执行两路检索融合,并使用 WeightedRanker 进行加权融合重排
results = milvus_client.hybrid_search(
    collection_name="travel_hybrid_docs",
    reqs=[req_dense, req_sparse],
    ranker=WeightedRanker(0.7, 0.3, norm_score=True),  # 分别指定稠密(0.7)和稀疏(0.3)的权重
    limit=5,
    output_fields=["text_content"]
)

倒数排名融合(RRF)

https://milvus.io/docs/rrf-ranker.md

如果需要规避各检索通道分数体系和量纲差异的影响,可以使用倒数排名融合(Reciprocal Rank Fusion, RRF)算法。RRF 不直接依赖原始的分数,而只关注文档在各检索通道中的排名位次。它通过累计各通道排名的倒数得分计算最终的重排得分,能够规避不同通道的分值量纲差异,算法表现最为稳健。

from pymilvus import MilvusClient, AnnSearchRequest, RRFRanker

milvus_client = MilvusClient(uri="./milvus_test.db")

# 模拟查询所对应的稠密与稀疏特征向量
query_dense = [0.1] * 1024
query_sparse = {101: 0.85, 202: 0.65}

# 1. 构造稠密与稀疏两路检索子请求
req_dense = AnnSearchRequest(
    data=[query_dense],                      # 稠密查询特征
    anns_field="dense_vector",
    param={"metric_type": "COSINE"},         # 稠密检索使用余弦相似度
    limit=5
)
req_sparse = AnnSearchRequest(
    data=[query_sparse],                     # 稀疏查询特征
    anns_field="sparse_vector",
    param={"metric_type": "IP"},             # 稀疏检索使用内积相似度
    limit=5
)

# 2. 调用 hybrid_search 执行两路检索融合,并使用 RRFRanker 进行无分数排名融合
results = milvus_client.hybrid_search(
    collection_name="travel_hybrid_docs",
    reqs=[req_dense, req_sparse],
    ranker=RRFRanker(k=60),                  # 使用 RRF 融合,k 为平滑常数(默认推荐值为 60)
    limit=5,
    output_fields=["text_content"]
)

融合策略选型总结

  • 朴素加权求和:由于余弦与内积的分值范围及量纲完全失衡,不加归一化的线性相加在实际检索中极易导致某单通道完全主导,在生产中不建议直接采用。
  • 加权分数融合(WeightedRanker):通过归一化(norm_score=True)抹平物理量纲,能在融合中保留局部的相对分数梯度,但对跨查询(Cross-Query)的阈值卡点依然比较敏感,常作为第二阶段重排前的数据初筛手段。
  • 倒数排名融合(RRF):完全无视绝对分值,仅依据各子通道排名进行融合计算。RRF 对量纲差异天然免疫,且抗噪性能极佳,是目前最稳健、最受工业界青睐的双路检索融合机制。
目录
向量度量基础Embedding 向量基础稠密向量与余弦相似度稀疏向量与内积度量L2 归一化分析混合向量生成与建表存储混合向量特征生成多特征建表与数据入库混合检索策略加权分数融合检索倒数排名融合(RRF)融合策略选型总结

©2015-2026 黑白梦 粤ICP备15018165号

联系: heibaimeng@foxmail.com