尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
混合检索与RRF融合 - 核心知识点速记
第十一章 混合检索与RRF融合 - 代码详解版本章核心向量检索 BM25检索 → RRF融合 → 更精准的召回一、为什么需要混合检索面试必问向量检索擅长语义理解BM25擅长关键词匹配两者互补。场景向量检索BM25用户问申请条件 vs 文档写需要满足哪些要求能匹配匹配不到用户问社保缴纳记录可能模糊精确匹配用户问第二条 支持范围忽略数字精确匹配二、代码详解分词函数 tokenize_chineseimport reimport jiebaSTOP_WORDS {“的”, “了”, “是”, “在”, “和”, “有”, “就”, “都”, “而”, “及”}valid_pat re.compile(r^([\u4e00-\u9fff]|[A-Za-z0-9])$)def tokenize_chinese(text: str) - list[str]:text_low text.lower()words jieba.lcut_for_search(text_low)tokens []for w in words:if valid_pat.fullmatch(w) and w not in STOP_WORDS and len(w) 1:tokens.append(w)return tokens【代码解释】转小写让英文不区分大小写AI和ai能匹配lcut_for_search搜索引擎模式切得更细提高召回去停用词“的”了这种词出现太频繁没有区分度去单字词“我”你这种词没有区分度BM25检索函数 keyword_searchfrom rank_bm25 import BM25Okapifrom sqlalchemy import selectasync def keyword_search(session: Session,query: str,top_k: int,filters: RetrievalFilters,) - list[dict[str, Any]]:# 第1步从MySQL查询所有子块 statement ( select(DocumentChunk, PolicyDocument) .join(PolicyDocument, DocumentChunk.document_id PolicyDocument.id) .where(DocumentChunk.is_parent.is_(False)) ) # 第2步应用过滤条件 if filters.document_ids: statement statement.where(DocumentChunk.document_id.in_(filters.document_ids)) if filters.topic: statement statement.where(PolicyDocument.topic filters.topic) if filters.effective_before: statement statement.where( PolicyDocument.effective_date filters.effective_before ) rows list(session.execute(statement).all()) if not rows: return [] # 第3步构建BM25模型 corpus [tokenize_chinese(chunk.content) for chunk, _ in rows] bm25_model BM25Okapi(corpus) # 第4步计算分数 scores bm25_model.get_scores(tokenize_chinese(query)) # 第5步排序取Top-K ranked sorted(enumerate(scores), keylambda item: item[1], reverseTrue)[:top_k] # 第6步构造返回结果 return [ { chunk_id: rows[index][0].id, content: rows[index][0].content, metadata: { document_id: rows[index][1].id, filename: rows[index][1].filename, section: rows[index][0].section or , page: rows[index][0].page or 0, parent_id: rows[index][0].parent_id or , }, score: float(score), source: bm25, } for index, score in ranked if score 0 ]【代码解释】第1步为什么只查子块向量检索也只用子块保持两路范围一致第2步过滤条件和向量检索一致确保在同一范围内比较第3步BM25Okapi统计每个词的IDF记录每个文档长度第4步get_scores返回查询和每个文档的分数越高越相关第6步返回结构和向量检索一致方便RRF融合RRF融合函数def reciprocal_rank_fusion(result_lists: list[list[dict[str, Any]]], k: int 60) - list[dict[str, Any]]:fused {}for results in result_lists: for rank, item in enumerate(results, start1): chunk_id item[chunk_id] if chunk_id not in fused: fused[chunk_id] { **item, rrf_score: 0.0, sources: [] } fused[chunk_id][rrf_score] 1 / (k rank) fused[chunk_id][sources].append(item.get(source, unknown)) return sorted(fused.values(), keylambda x: x[rrf_score], reverseTrue)【代码解释】为什么用字典用chunk_id作key把同一子块的多路结果合并为什么k60RRF标准参数起平滑作用sources记录什么记录来源如[‘vector’]或[‘bm25’]或[‘vector’,‘bm25’]两路都命中的结果rrf_score更高排得更靠前retrieve中的混合检索核心vector_task VectorStoreService().search(question, top_k * 3, filters.document_ids or None)vector_results, keyword_results await asyncio.gather(vector_task,keyword_search(session, question, top_k, filters),)vector_results apply_metadata_filters(vector_results, filters)vector_results vector_results[:top_k]fused reciprocal_rank_fusion([vector_results, keyword_results])candidates fused[:top_k]【代码解释】为什么向量检索取 top_k * 3RRF会重新排序只取top_k可能漏掉BM25高分结果为什么用 asyncio.gather两路检索独立同时执行节省时间为什么向量结果要截断召回阶段多取进入融合前截断减少排序开销三、重点难点总结重点必须掌握BM25原理词频 逆文档频率 长度归一化中文分词jieba分词 → 去停用词 → 过滤单字RRF核心不看分数看排名公式 1 / (k rank)sources字段[‘vector’,‘bm25’]表示两路都命中最可信混合检索流程两路并行 → RRF融合 → 截取候选难点理解原理即可BM25的数学公式RRF中k值的意义平滑参数asyncio.gather的并行原理常见坑点BM25返回空检查MySQL是否有子块检查分词是否过滤太狠ModuleNotFoundErrorpip install rank-bm25jieba效果差用lcut_for_search模式四、面试题预测Q1: 为什么需要混合检索参考答案向量检索擅长语义理解能处理同义表达。但在政策问答中用户经常问精确的日期、政策名称、条款编号这些在向量检索中容易被弱化。BM25基于词频匹配能精确命中这些关键词。两者互补。Q2: 什么是RRF为什么不用加权平均参考答案RRF全称倒数排名融合不看具体分数只看排名。BM25分数和向量分数量纲不同不能直接相加。RRF把排名转换成统一分数。同时RRF对异常值不敏感比加权平均更鲁棒。Q3: 为什么向量检索要取 top_k * 3参考答案向量检索先多取一些因为后面的RRF融合会重新排序。如果在召回阶段只取top_k可能会漏掉在BM25中排名高但在向量中排名靠后的结果。召回阶段多取排序阶段再截断。Q4: sources字段中同时包含vector和bm25意味着什么参考答案说明这个结果在语义上和关键词上都匹配是最可靠的证据。在后续RAG中这些结果应被优先使用。Q5: 中文分词的难点是什么参考答案中文没有空格必须依赖分词工具。项目用jieba的lcut_for_search模式切分更细。切分后要去停用词、过滤单字词减少噪声。五、自测清单能说出BM25是什么、解决什么问题能说出为什么需要分词能说出RRF的全称和核心思想能画出混合检索的完整流程能解释sources字段的作用知道asyncio.gather的作用能说出向量检索和BM25各自的优缺点能说出RRF和加权平均的区别知道top_k * 3的原因能说出RRF公式1/(krank)
RELATED

相关推荐

NoSleep 防休眠工具指南:180KB 单文件,免管理员权限阻止 Windows 自动锁屏

NoSleep 防休眠工具指南:180KB 单文件,免管理员权限阻止 Windows 自动锁屏

NoSleep 防休眠工具指南:180KB 单文件,免管理员权限阻止 Windows 自动锁屏 【免费下载链接】NoSleep Lightweight Windows utility to prevent screen locking 项目地址: https://gitcode.com/gh_mirrors/nos/NoSleep 起身接个电话,回…

📅 2026/9/29 15:34:36
Codex编程助手实战指南:解决开发中的五类典型难题

Codex编程助手实战指南:解决开发中的五类典型难题

最近一周,你的开发工作流里,有没有出现一些“微小但恼人”的障碍?比如,一个复杂的正则表达式死活调不对,一个陌生的 API 接口文档看得云里雾里,或者一段遗留代码的逻辑像一团乱麻,重构起来无从下…

📅 2026/9/29 9:09:35
m4s-converter 实测:几秒合并 B站缓存视频

m4s-converter 实测:几秒合并 B站缓存视频

m4s-converter 实测:几秒合并 B站缓存视频 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你攒在 B站缓存里的那些 m4s 文件&#xf…

📅 2026/10/16 15:31:37
MORE NEWS

更多资讯

📰

llama.cpp 2-bit 量化深度解析:Q2_K 与 IQ2 系列原理及选型指南

1. 为什么 2-bit 量化值得单独拿出来聊大模型本地部署这件事,绕不开一个核心矛盾:模型参数越来越多,显存和内存却始终有限。一个 70 亿参数的模型,用 FP16 存下来大概要 14GB 左右,稍微加点上下文开销,一张…

📰

JDspider实战:京东商品爬虫登录管理、反爬规避与字段解析

简介:这是一份基于Python的京东商品价格监控爬虫源码,面向电商数据分析爱好者与入门爬虫开发者。爬虫以京东商品详情页为目标,自动抓取页面中的优惠价格,并利用定时任务机制周期运行;一旦检测到价格变动,便…

📰

MIMO 4x4 QPSK Matlab仿真:从系统模型到BER曲线详解

简介:面向无线通信与MATLAB仿真学习者,这份压缩包提供了一个完整的4x4 MIMO系统QPSK调制仿真示例,适合正在入门MIMO技术或需要参考完整代码框架的读者。包内包含1个MATLAB脚本(.m)和1个数据文件(.mat&#…

📰

MATLAB下的Occam2DMT大地电磁二维反演:原理、流程与工程实践

简介:面向MATLAB环境封装的多视角图像处理代码包,以Occam2DMT为核心,演示OCCAM算法在二维图像与多相机阵列数据中的优化处理流程,适合立体视觉、三维重建、全景成像等课题的算法研究与二次开发。压缩包共15个文件,主体…

📰

Flink 1.12.2 集成 Hudi 0.9.0 实战:流式入湖与 Hive 同步全解析

1. 为什么要在 Flink 1.12.2 上折腾 Hudi 0.9.0先把背景交代清楚。我手头有一套已经跑了很久的 Flink 1.12.2 实时链路,业务侧的数据源是埋点日志和业务库变更流,下游原本直接写 Hive 做离线分析。问题出在"实时入湖"这个环节:Hive…

📰

Flask+Vue校园快递代取系统:从状态机到并发接单实战

1. 校园快递代取需求为啥这么硬:从驿站排队说起每到中午下课时间,高校校园里的快递驿站门口就会排起长队,尤其是"双十一""618"这些大促节点,货架上的包裹堆积成山,找件、扫码、带走一套流程走下来…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬