资讯动态

all in rag 检索优化 01

发布时间:2026/8/7 8:32:57 来源:尧图企业网站定制
一混合检索混合检索Hybrid Search是一种结合了稀疏向量Sparse Vectors和密集向量Dense Vectors优势的先进搜索技术。1 稀疏向量 VS 密集向量1.1稀疏向量稀疏向量也常被称为“词法向量”是基于词频统计的传统信息检索方法的数学表示。它通常是一个维度极高与词汇表大小相当但绝大多数元素为零的向量。它采用精准的“词袋”匹配模型将文档视为一堆词的集合不考虑其顺序和语法其中向量的每一个维度都直接对应一个具体的词非零值则代表该词在文档中的重要性权重。这类向量的经典权重计算方法是 TF-IDF。这种方法的优点是可解释性极强每个维度都代表一个确切的词无需训练能够实现关键词的精确匹配对于专业术语和特定名词的检索效果好。主要缺点是无法理解语义例如它无法识别“汽车”和“轿车”是同义词存在“词汇鸿沟”。1.2密集向量密集向量也常被称为“语义向量”是通过深度学习模型学习到的数据如文本、图像的低维、稠密的浮点数表示。这些向量旨在将原始数据映射到一个连续的、充满意义的“语义空间”中来捕捉“语义”或“概念”。在理想的语义空间中向量之间的距离和方向代表了它们所表示概念之间的关系。一个经典的例子是vector(国王) - vector(男人) vector(女人)的计算结果在向量空间中非常接近vector(女王)这表明模型学会了“性别”和“皇室”这两个维度的抽象概念。它的代表包括 Word2Vec、GloVe、以及所有基于 Transformer 的模型如 BERT、GPT生成的嵌入Embeddings。其主要优点是能够理解同义词、近义词和上下文关系泛化能力强在语义搜索任务中表现卓越。但缺点也同样明显可解释性差向量中的每个维度通常没有具体的物理意义需要大量数据和算力进行模型训练且对于未登录词OOV的处理相对困难。OOVOut-of-Vocabulary未登录词指在模型训练时没有出现在词汇表中但在实际使用时遇到的新词汇。1.3 实例对比稀疏向量表示:稀疏向量的核心思想是只存储非零值。例如一个8维的向量[0, 0, 0, 5, 0, 0, 0, 9]其大部分元素都是零。用稀疏格式表示可以极大地节约空间。常见的稀疏表示法有两种1.字典 / 键值对 (Dictionary / Key-Value):这种方式将非零元素的索引(0-based) 作为键值作为值。上面的向量可以表示为// {索引: 值} { 3: 5, 7: 9 }2.坐标列表 (Coordinate list - COO):这种方式通常用一个元组(维度, [索引列表], [值列表])来表示。上面的向量可以表示为(8, [3, 7], [5, 9])假设在一个包含5万个词的词汇表中“西红柿”在第88位“炒”在第666位“蛋”在第999位它们的BM25权重分别是1.2、0.8、1.5。那么它的稀疏表示采用字典格式就是// {索引: 权重} { 88: 1.2, 666: 0.8, 999: 1.5 }如果采用坐标列表COO格式它会是这样(50000, [88, 666, 999], [1.2, 0.8, 1.5])这两种格式都清晰地记录了文档的关键信息但它们的局限性也很明显如果我们搜索“番茄炒鸡蛋”由于“番茄”和“西红柿”是不同的词条索引不同模型将无法理解它们的语义相似性。密集向量表示:与稀疏向量不同密集向量的所有维度都有值因此使用数组[]来表示是最直接的方式。一个预训练好的语义模型在读取“西红柿炒蛋”后会输出一个低维的密集向量// 这是一个低维比如1024维的浮点数向量 // 向量的每个维度没有直接的、可解释的含义 [0.89, -0.12, 0.77, ..., -0.45]这个向量本身难以解读但它在语义空间中的位置可能与“番茄鸡蛋面”、“洋葱炒鸡蛋”等菜肴的向量非常接近因为模型理解了它们共享“鸡蛋类菜肴”、“家常菜”、“酸甜口味”等核心概念。因此当我们搜索“蛋白质丰富的家常菜”时即使查询中没有出现任何原文关键词密集向量也很有可能成功匹配到这份菜谱。2 混合检索通过上文可以看出稀疏向量和密集向量各有千秋那么将它们结合起来实现优势互补就成了一个不错的选择。混合检索便是基于这个思路通过结合多种搜索算法最常见的是稀疏与密集检索来提升搜索结果相关性和召回率。主要目标解决单一检索技术的局限性。例如关键词检索无法理解语义而向量检索则可能忽略掉必须精确匹配的关键词如产品型号、函数名等。混合检索旨在同时利用稀疏向量的精确性和密集向量的泛化性以应对复杂多变的搜索需求。2.1 技术原理与融合方法混合检索通常并行执行两种检索算法然后将两组异构的结果集融合成一个统一的排序列表。以下是两种主流的融合策略2.1.1 倒数排序组合RRFRRF 不关心不同检索系统的原始得分只关心每个文档在各自结果集中的排名。其思想是一个文档在不同检索系统中的排名越靠前它的最终得分就越高。2.1.2 加权线性组合这种方法需要先将不同检索系统的得分进行归一化例如统一到 0-1 区间然后通过一个权重参数α来进行线性组合。通过调整α的值可以灵活地控制语义相似性与关键词匹配在最终排序中的贡献比例。例如在电商搜索中可以调高关键词的权重而在智能问答中则可以侧重于语义。2.2 优势与局限优势局限召回率与准确率高能同时捕获关键词和语义显著优于单一检索。计算资源消耗大需要同时维护和查询两套索引。灵活性强可通过融合策略和权重调整适应不同业务场景。参数调试复杂融合权重等超参数需要反复实验调优。容错性好关键词检索可部分弥补向量模型对拼写错误或罕见词的敏感性。可解释性仍是挑战融合后的结果排序理由难以直观分析。3 代码实践 通过Milvus 实现混合索引接下来使用 Milvus 来实现一个完整的混合检索流程从定义 Schema、插入数据到执行查询。import json import os os.environ[HF_ENDPOINT] https://hf-mirror.com import numpy as np from pymilvus import connections, MilvusClient, FieldSchema, CollectionSchema, DataType, Collection, AnnSearchRequest, RRFRanker from pymilvus.model.hybrid import BGEM3EmbeddingFunction # 1. 初始化设置 COLLECTION_NAME dragon_hybrid_demo MILVUS_URI http://localhost:19530 # 服务器模式 DATA_PATH ../../data/C4/metadata/dragon.json # 相对路径 BATCH_SIZE 50 # 2. 连接 Milvus 并初始化嵌入模型 print(f-- 正在连接到 Milvus: {MILVUS_URI}) connections.connect(uriMILVUS_URI) print(-- 正在初始化 BGE-M3 嵌入模型...) ef BGEM3EmbeddingFunction(use_fp16False, devicecpu) print(f-- 嵌入模型初始化完成。密集向量维度: {ef.dim[dense]}) # 3. 创建 Collection milvus_client MilvusClient(uriMILVUS_URI) if milvus_client.has_collection(COLLECTION_NAME): print(f-- 正在删除已存在的 Collection {COLLECTION_NAME}...) milvus_client.drop_collection(COLLECTION_NAME) fields [ FieldSchema(namepk, dtypeDataType.VARCHAR, is_primaryTrue, auto_idTrue, max_length100), FieldSchema(nameimg_id, dtypeDataType.VARCHAR, max_length100), FieldSchema(namepath, dtypeDataType.VARCHAR, max_length256), FieldSchema(nametitle, dtypeDataType.VARCHAR, max_length256), FieldSchema(namedescription, dtypeDataType.VARCHAR, max_length4096), FieldSchema(namecategory, dtypeDataType.VARCHAR, max_length64), FieldSchema(namelocation, dtypeDataType.VARCHAR, max_length128), FieldSchema(nameenvironment, dtypeDataType.VARCHAR, max_length64), FieldSchema(namesparse_vector, dtypeDataType.SPARSE_FLOAT_VECTOR), FieldSchema(namedense_vector, dtypeDataType.FLOAT_VECTOR, dimef.dim[dense]) ] # 如果集合不存在则创建它及索引 if not milvus_client.has_collection(COLLECTION_NAME): print(f-- 正在创建 Collection {COLLECTION_NAME}...) schema CollectionSchema(fields, description关于龙的混合检索示例) # 创建集合 collection Collection(nameCOLLECTION_NAME, schemaschema, consistency_levelStrong) print(-- Collection 创建成功。) # 创建索引 print(-- 正在为新集合创建索引...) sparse_index {index_type: SPARSE_INVERTED_INDEX, metric_type: IP} collection.create_index(sparse_vector, sparse_index) print(稀疏向量索引创建成功。) dense_index {index_type: AUTOINDEX, metric_type: IP} collection.create_index(dense_vector, dense_index) print(密集向量索引创建成功。) collection Collection(COLLECTION_NAME) collection.load() print(f-- Collection {COLLECTION_NAME} 已加载到内存。)fields字段类型分析pk: 主键设计auto_idTrue让 Milvus 自动生成唯一标识避免主键冲突标量字段: 7个VARCHAR字段用于存储元数据max_length根据实际数据分布优化存储稀疏向量:SPARSE_FLOAT_VECTOR类型存储关键词权重密集向量:FLOAT_VECTOR类型固定1024维存储语义特征BGE-M3 双向量生成# 数据加载与预处理 if collection.is_empty: print(f-- Collection 为空开始插入数据...) with open(DATA_PATH, r, encodingutf-8) as f: dataset json.load(f) docs, metadata [], [] for item in dataset: parts [ item.get(title, ), item.get(description, ), item.get(location, ), item.get(environment, ), ] docs.append( .join(filter(None, parts))) metadata.append(item)Collection 此时已加载到内存但为空状态。通过is_empty检查避免重复插入。多字段文本合并中每个实体对应一个完整的数据记录。# 向量生成 print(-- 正在生成向量嵌入...) embeddings ef(docs) print(-- 向量生成完成。) # 获取两种向量 sparse_vectors embeddings[sparse] # 稀疏向量词频统计 dense_vectors embeddings[dense] # 密集向量语义编码这里的enbeddings 返回的是字典普通的HuggingFaceEmbeddings返回的是list[list[float]]query_embeddings[dense] [ [0.012, 0.231, -0.113, ... , 共1024个float] ]稠密向量也是list[list[float]]因为只有一句话所以只有一行数据。稀疏向量是特殊的类。但是也要获取第一个元素。Collection 批量数据插入# 为每个字段准备批量数据 img_ids [doc[img_id] for doc in metadata] paths [doc[path] for doc in metadata] titles [doc[title] for doc in metadata] descriptions [doc[description] for doc in metadata] categories [doc[category] for doc in metadata] locations [doc[location] for doc in metadata] environments [doc[environment] for doc in metadata] # 插入数据 collection.insert([ img_ids, paths, titles, descriptions, categories, locations, environments, sparse_vectors, dense_vectors ]) collection.flush()字段映射: 严格按照 Schema 定义的字段顺序插入9个字段7个标量2个向量flush()作用: 强制将内存缓冲区数据写入磁盘使数据立即可搜索最终状态: Collection 包含6个Entity索引层使用稀疏向量的SPARSE_INVERTED_INDEX和密集向量的AUTOINDEX实现混合检索最后使用 milvus 中封装好的 RRF 排序算法来完成混合检索# 生成查询向量 # 执行搜索 search_query 悬崖上的巨龙 search_filter category in [western_dragon, chinese_dragon, movie_character] top_k 5 print(f\n{*20} 开始混合搜索 {*20}) print(f查询: {search_query}) print(f过滤器: {search_filter}) # 生成查询向量 query_embeddings ef([search_query]) dense_vec query_embeddings[dense][0] sparse_vec query_embeddings[sparse]._getrow(0)使用 RRF 算法进行混合检索通过 milvus 封装的 RRFRanker 实现。RRFRanker 的核心参数是k值默认60用于控制 RRF 算法中的排序平滑程度。其中k值越大排序结果越平滑越小则高排名结果的权重越突出# 定义搜索参数 search_params {metric_type: IP, params: {}} # 先执行单独的搜索 print(\n--- [单独] 密集向量搜索结果 ---) dense_results collection.search( [dense_vec], anns_fielddense_vector, paramsearch_params, limittop_k, exprsearch_filter, output_fields[title, path, description, category, location, environment] )[0] for i, hit in enumerate(dense_results): print(f{i1}. {hit.entity.get(title)} (Score: {hit.distance:.4f})) print(f 路径: {hit.entity.get(path)}) print(f 描述: {hit.entity.get(description)[:100]}...) print(\n--- [单独] 稀疏向量搜索结果 ---) sparse_results collection.search( [sparse_vec], anns_fieldsparse_vector, paramsearch_params, limittop_k, exprsearch_filter, output_fields[title, path, description, category, location, environment] )[0] for i, hit in enumerate(sparse_results): print(f{i1}. {hit.entity.get(title)} (Score: {hit.distance:.4f})) print(f 路径: {hit.entity.get(path)}) print(f 描述: {hit.entity.get(description)[:100]}...) print(\n--- [混合] 稀疏密集向量搜索结果 ---) # 创建 RRF 融合器 rerank RRFRanker(k60) # 创建搜索请求 dense_req AnnSearchRequest([dense_vec], dense_vector, search_params, limittop_k) sparse_req AnnSearchRequest([sparse_vec], sparse_vector, search_params, limittop_k) # 执行混合搜索 results collection.hybrid_search( [sparse_req, dense_req], rerankrerank, limittop_k, output_fields[title, path, description, category, location, environment] )[0] # 打印最终结果 for i, hit in enumerate(results): print(f{i1}. {hit.entity.get(title)} (Score: {hit.distance:.4f})) print(f 路径: {hit.entity.get(path)}) print(f 描述: {hit.entity.get(description)[:100]}...)结果分析--- [单独] 密集向量搜索结果 --- 1. 悬崖上的白龙 (Score: 0.7219) 路径: ../../data/C3/dragon/dragon02.png 描述: 一头雄伟的白色巨龙栖息在悬崖边缘背景是金色的云霞和远方的海岸。它拥有巨大的翅膀和优雅的身姿是典型的西方奇幻生物。... 2. 中华金龙 (Score: 0.5131) 路径: ../../data/C3/dragon/dragon06.png 描述: 一条金色的中华龙在祥云间盘旋它身形矫健龙须飘逸展现了东方神话中龙的威严与神圣。... 3. 驯龙高手无牙仔 (Score: 0.5119) 路径: ../../data/C3/dragon/dragon05.png 描述: 在电影《驯龙高手》中主角小嗝嗝骑着他的龙伙伴无牙仔在高空飞翔。他们飞向灿烂的太阳下方是岛屿和海洋画面充满了冒险与友谊。... --- [单独] 稀疏向量搜索结果 --- 1. 悬崖上的白龙 (Score: 0.2319) 路径: ../../data/C3/dragon/dragon02.png 描述: 一头雄伟的白色巨龙栖息在悬崖边缘背景是金色的云霞和远方的海岸。它拥有巨大的翅膀和优雅的身姿是典型的西方奇幻生物。... 2. 中华金龙 (Score: 0.0923) 路径: ../../data/C3/dragon/dragon06.png 描述: 一条金色的中华龙在祥云间盘旋它身形矫健龙须飘逸展现了东方神话中龙的威严与神圣。... 3. 驯龙高手无牙仔 (Score: 0.0691) 路径: ../../data/C3/dragon/dragon05.png 描述: 在电影《驯龙高手》中主角小嗝嗝骑着他的龙伙伴无牙仔在高空飞翔。他们飞向灿烂的太阳下方是岛屿和海洋画面充满了冒险与友谊。... --- [混合] 稀疏密集向量搜索结果 --- 1. 悬崖上的白龙 (Score: 0.0328) 路径: ../../data/C3/dragon/dragon02.png 描述: 一头雄伟的白色巨龙栖息在悬崖边缘背景是金色的云霞和远方的海岸。它拥有巨大的翅膀和优雅的身姿是典型的西方奇幻生物。... 2. 中华金龙 (Score: 0.0320) 路径: ../../data/C3/dragon/dragon06.png 描述: 一条金色的中华龙在祥云间盘旋它身形矫健龙须飘逸展现了东方神话中龙的威严与神圣。... 3. 霸王龙的怒吼 (Score: 0.0318) 路径: ../../data/C3/dragon/dragon03.png 描述: 史前时代的霸王龙张开血盆大口发出震天的怒吼。在它身后几只翼龙在阴沉的天空中盘旋展现了白垩纪的原始力量。... 4. 奔跑的奶龙 (Score: 0.0313) 路径: ../../data/C3/dragon/dragon04.png 描述: 一只Q版的黄色小恐龙有着大大的绿色眼睛和友善的微笑。是一部动画中的角色非常可爱。... 5. 驯龙高手无牙仔 (Score: 0.0310) 路径: ../../data/C3/dragon/dragon05.png 描述: 在电影《驯龙高手》中主角小嗝嗝骑着他的龙伙伴无牙仔在高空飞翔。他们飞向灿烂的太阳下方是岛屿和海洋画面充满了冒险与友谊。...分析代码为什么在密集向量检索和稀疏向量检索中排名第三的驯龙高手在混合检索中反而排在了第五单独 search 打印出来驯龙高手是 sparse 第 3这是 filter 删掉其他人之后的 “表观名次” hybrid 不经过 filter直接使用 sparse 向量原生召回名次驯龙高手在 sparse 原生池子就是第 5 位。基于上一节的多模态检索代码04_multi_milvus.py结合本节的检索代码加入多模态信息融合的功能并尝试使用混合检索。参考代码

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价