带神经重排的混合检索管线


文档摘要

源文件:chapter3/retrieval-pipeline/README.md 带神经重排的混合检索管线 一个用于教学的检索管线,结合稠密嵌入、稀疏检索与神经重排,展示不同检索方法的长处与短板。 教学目标 本项目演示: 稠密 vs 稀疏检索:各自擅长什么、为什么 混合检索:组合多种检索方法获得更好结果 神经重排:用 transformer 模型对检索结果重排序 并行处理:跨多个服务高效地索引与检索 真实工程模式:面向生产的 API 设计与错误处理 架构 关键概念 稠密嵌入(语义检索) 模型:BGE-M3(多语言,1024 维向量) 优点: 语义相似(能找到相关概念) 跨语言检索(跨语言工作) 概念理解(处理同义词) 上下文感知(理解含义) 短板: 可能漏掉精确字符串 对代码 / ID

源文件:chapter3/retrieval-pipeline/README.md

带神经重排的混合检索管线

一个用于教学的检索管线,结合稠密嵌入、稀疏检索与神经重排,展示不同检索方法的长处与短板。

教学目标

本项目演示:

  1. 稠密 vs 稀疏检索:各自擅长什么、为什么
  2. 混合检索:组合多种检索方法获得更好结果
  3. 神经重排:用 transformer 模型对检索结果重排序
  4. 并行处理:跨多个服务高效地索引与检索
  5. 真实工程模式:面向生产的 API 设计与错误处理

架构

┌──────────────────────────────────────────────┐ │ Client Application │ └────────────────────┬─────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ Retrieval Pipeline (Port 4242) │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ Document Store (In-Memory) │ │ │ └──────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ BGE-Reranker-v2 (Local Model) │ │ │ └──────────────────────────────────────┘ │ └────────┬──────────────────┬─────────────────┘ │ │ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ Dense Service │ │ Sparse Service │ │ (Port 4240) │ │ (Port 4241) │ │ │ │ │ │ BGE-M3 Model │ │ BM25 Engine │ └─────────────────┘ └─────────────────┘

关键概念

稠密嵌入(语义检索)

  • 模型:BGE-M3(多语言,1024 维向量)
  • 优点
    • 语义相似(能找到相关概念)
    • 跨语言检索(跨语言工作)
    • 概念理解(处理同义词)
    • 上下文感知(理解含义)
  • 短板
    • 可能漏掉精确字符串
    • 对代码 / ID 效果较差
    • 计算开销较大

稀疏检索(BM25)

  • 算法:BM25(Best Matching 25)
  • 优点
    • 精确词项匹配
    • 专有名称和代码
    • 技术标识符
    • 快速高效
  • 短板
    • 无语义理解
    • 依赖具体语言
    • 需要精确词项

结果融合(RRF / 加权)

  • 模块fusion.py
  • 用途:在重排前把分别排序的稠密与稀疏候选列表合并为
    一个统一池(即融合阶段)
  • 两种策略(书中均有讨论):
    • 倒数排名融合(RRF)score(d) = Σ 1/(k + rank_r(d))k=60
      只用排名,因此永远不必把余弦相似度与 BM25
      分数直接比较——稳健且与尺度无关。
    • 加权分数融合:对每个列表做 min-max 归一化到 [0,1],再加权
      求和。保留原始相关性信号,代价是需要调优尺度对齐。

神经重排

  • 模型:BGE-Reranker-v2-M3(生产用);BAAI/bge-reranker-base(更轻量,evaluate.py 使用)
  • 用途:对融合后的候选池重新打分并重排序
  • 好处
    • 更好的相关性排序
    • 综合两种方法的信号
    • 上下文感知打分

快速开始

前置条件

  1. Python 3.8+
  2. 搭载 M1/M2 芯片的 macOS(或其他平台需修改设备设置)
  3. 至少 8GB 内存
  4. 约 5GB 磁盘空间用于模型

安装

# 克隆仓库 cd projects/week3/retrieval-pipeline # 安装依赖 pip install -r requirements.txt # 模型会在首次运行时自动下载: # - BGE-M3:约 2.3GB # - BGE-Reranker-v2-M3:约 1.1GB

启动服务

  1. 启动全部服务(推荐):
./start_all_services.sh

会启动:

  • 4240 端口的稠密嵌入服务
  • 4241 端口的稀疏嵌入服务
  • 4242 端口的检索管线
  1. 或分别启动
# 终端 1:稠密服务 cd ../dense-embedding python main.py --port 4240 # 终端 2:稀疏服务 cd ../sparse-embedding python server.py --port 4241 # 终端 3:管线 cd ../retrieval-pipeline python main.py --port 4242

测试管线

  1. 运行教学测试
python test_client.py

会运行一组全面的测试用例,展示稠密与稀疏各自擅长的场景。

  1. 运行交互式演示
python demo.py

用真实查询做演示并附带讲解。

  1. 访问 API 文档
http://localhost:4242/docs

离线评测 CLI(evaluate.py

上面的 test_client.py / demo.py 需要三个微服务(端口
4240/4241/4242)在运行。evaluate.py 在单进程内跑完整条
管线,完全离线
,因此你可以在没有服务的情况下、用本地模型复现"每个阶段都改善排序"的故事。

它在一个小型带标注评测集上走完整管线——切块 → 嵌入 → 检索 → 融合 → 重排——并打印一张分阶段对比表和
逐查询的明细。CLI 提供完整的中文 --help

python evaluate.py --help # 中文帮助:语料/查询/阶段/top-k/模型/输出等 python evaluate.py # 内置评测集,完整对比表(默认) python evaluate.py --no-dense # 仅 BM25,纯离线、无需任何模型 python evaluate.py --no-rerank # 跳过重排阶段 python evaluate.py --query "XR-7003" # 单条查询逐阶段排名追踪 python evaluate.py --embed-model BAAI/bge-m3 --pooling cls # 换稠密模型 python evaluate.py --output result.json # 结果写入 JSON

本地组件(每个阶段都是真实模型 / 算法,非 mock):

阶段 组件(默认) 是否离线?
chunk 字符窗口切分器 是 纯 Python
sparse BM25(rank_bm25 是 无需下载模型
dense sentence-transformers/all-MiniLM-L6-v2(约 90MB) 是 经由 transformers(多语言:可换 Qwen/Qwen3-Embedding-0.6B / BAAI/bge-m3
fuse RRF + 加权(fusion.py 是 纯 Python
rerank BAAI/bge-reranker-base(约 1.1GB,首次运行下载) 是 缓存后即可

说明--no-dense 完全不需要任何 ML 模型(仅 BM25)。稠密和
重排阶段需要本地模型;首次运行会从 HuggingFace 下载,
之后 --offline 可让一切走本地缓存。在 Apple Silicon 上,
某些 transformers 版本在 MPS 设备上会输出 NaN——CLI 会检测到这一情况
并自动回退到 CPU,因此结果始终是有限的。

真实输出(本机复现)

内置评测集故意包含两个难点簇:近似重复的代码
XR-7001..XR-7006HTTP-400..HTTP-500)会让稠密检索失效(向量几乎
完全相同,只有精确词项匹配能找到正确的那条),以及
零词面重叠的改写(查询"reclaiming unused heap space without
programmer effort" → 文档"Automatic memory management frees developers…")会让
BM25 失效。

Stage / Method Recall@3 MRR nDCG@3 ------------------------------------------------------------------------------ BM25 (sparse) 0.9000 0.8500 0.8631 Dense 1.0000 0.9000 0.9262 Hybrid-RRF 1.0000 1.0000 1.0000 Hybrid-Weighted 1.0000 0.9500 0.9631 Hybrid-RRF+Rerank 1.0000 0.9500 0.9631 逐条查询 MRR 明细(1.00=正确文档排在第 1 位) Query BM25 Dense RRF Wgt Rerank ------------------------------------------------------------------------------ XR-7003 1.00 0.50 1.00 1.00 1.00 XR-7005 1.00 0.50 1.00 1.00 1.00 HTTP-403 1.00 1.00 1.00 1.00 1.00 HTTP-400 1.00 1.00 1.00 1.00 0.50 a beginner friendly language with tidy... 1.00 1.00 1.00 1.00 1.00 reclaiming unused heap space without p... 0.00 1.00 1.00 1.00 1.00 how vegetation turns light into food 0.50 1.00 1.00 0.50 1.00 hiding a note so eavesdroppers cannot ... 1.00 1.00 1.00 1.00 1.00 how does water move between the ocean ... 1.00 1.00 1.00 1.00 1.00 how are volcanoes formed from molten rock 1.00 1.00 1.00 1.00 1.00

如何解读:

  • BM25(稀疏) 命中每一个精确代码,但在两条改写查询上崩盘
    reclaiming…=0.00、vegetation…=0.50)。
  • 稠密 则是镜像:在改写上完美,却混淆了
    近似重复的代码(XR-7003/XR-7005=0.50——它把一个兄弟代码排到了第一)。
  • Hybrid-RRF 把两者结合,整体达到完美的 1.00
    融合救回了两种单方法的失败。这是实验 3-6 的核心结论。
  • 加权融合 也很强,但在此处不如 RRF 稳健(vegetation
    查询掉到 0.50,因为一个词面近似匹配扭曲了归一化分数——
    正是"尺度对齐难以调优"的告诫)。
  • 重排:在这个 17 篇文档的玩具语料上 RRF 已经最优,因此重排
    没有提升空间;交叉编码器本质是语义匹配器,甚至可能轻微
    重排一个简单的精确代码查询(HTTP-400)。它的价值在更大的
    候选池和自然语言查询上才显现——见下方单查询追踪。

单查询追踪让融合机制变得直观:

$ python evaluate.py --query "XR-7003" [BM25 (sparse)] 1. xr_7003 score= 3.2260 Product model XR-7003 is a smartphone available now. [Dense] 1. xr_7001 score= 0.5247 Product model XR-7001 ... # 稠密把错误的兄弟排到第一 2. xr_7003 score= 0.5195 Product model XR-7003 ... [Hybrid-RRF] 1. xr_7003 score= 0.0325 Product model XR-7003 ... # 融合把精确匹配提升到第一

教学测试用例

测试用例 1:语义相似(稠密胜出)

# 查询:"kitty behavior" # 文档包含:"feline"、"cat" # 稠密找到语义匹配,稀疏漏掉

测试用例 2:精确名称(稀疏胜出)

# 查询:"Alexander Humphrey" # 稀疏找到精确名称匹配 # 稠密可能返回其他人

测试用例 3:多语言(稠密胜出)

# 查询:"人工智能" # 稠密能找到任意语言的 AI 文档 # 稀疏只能找到中文文本

测试用例 4:技术代码(稀疏胜出)

# 查询:"HTTP-403" # 稀疏找到精确错误码 # 稠密可能返回其他错误

测试用例 5:概念(稠密胜出)

# 查询:"happiness and excitement" # 文档包含:"joy"、"elation" # 稠密理解情绪概念

API 端点

索引文档

POST /index { "text": "Document content", "doc_id": "optional_id", "metadata": {"category": "example"} }

检索

POST /search { "query": "search terms", "mode": "hybrid", # 或 "dense" 或 "sparse" "top_k": 20, "rerank_top_k": 10 }

响应包含:

  • 原始稠密排名与分数
  • 原始稀疏排名与分数
  • 最终重排结果
  • 排名变化统计
  • 性能指标

统计

GET /stats

列出文档

GET /documents?limit=10&offset=0

解读结果

检索响应提供了教学性的洞察:

{ "dense_results": [...], // 语义检索的顶部结果 "sparse_results": [...], // BM25 的顶部结果 "reranked_results": [ // 最终重排结果 { "rank": 1, "doc_id": "doc_1", "rerank_score": 0.95, "original_ranks": { "dense": 3, // 在稠密中原排第 3 "sparse": 5 // 在稀疏中原排第 5 }, "rank_changes": [ "dense: +2", // 上升 2 位 "sparse: +4" // 上升 4 位 ] } ], "statistics": { "overlap_percentage": 30.0, // 稠密/稀疏一致的程度 "avg_dense_rank_change": 1.5, "avg_sparse_rank_change": 2.1 } }

学习练习

  1. 尝试不同查询

    • 试用同义词 vs 精确词项
    • 测试多语言查询
    • 使用技术代码
  2. 修改参数

    • top_k 检索更多 / 更少候选
    • 跳过重排查看原始结果
    • 尝试不同检索模式
  3. 分析模式

    • 何时混合检索胜过单方法?
    • 稠密与稀疏结果重叠多少?
    • 哪些查询最受益于重排?

故障排查

服务无法启动

  • 检查端口 4240-4242 是否空闲
  • 确认模型已正确下载
  • 检查 Python 版本(3.8+)

内存不足

  • 在 config.py 中减小批大小
  • 用 CPU 代替 MPS/CUDA
  • 启用 FP16 模式

性能缓慢

  • 首次运行会下载模型(请耐心)
  • 后续运行使用缓存模型
  • 如有 GPU 可考虑使用

配置

编辑 config.py 调整:

  • 服务 URL
  • 模型参数
  • 检索设置
  • 重排参数

项目结构

retrieval-pipeline/ ├── config.py # 配置设置(含 fusion_method / rrf_k) ├── document_store.py # 内存文档存储 ├── retrieval_client.py # 稠密/稀疏服务客户端 ├── reranker.py # BGE-Reranker 实现 ├── fusion.py # 结果融合:RRF + 加权分数融合 ├── retrieval_pipeline.py # 主管线编排(使用 fusion.py) ├── evaluate.py # 离线单进程评测 CLI(中文 --help) ├── main.py # FastAPI 服务 ├── test_client.py # 教学测试用例 ├── demo.py # 交互式演示 ├── requirements.txt # Python 依赖 ├── start_all_services.sh # 启动脚本 ├── stop_all_services.sh # 停止脚本 └── README.md # 本文件

性能考量

  • 索引:向两个服务并行索引
  • 检索:并行检索,然后顺序重排
  • 内存:模型约 4GB,外加文档存储
  • 延迟
    • 稠密:每查询约 50-100ms
    • 稀疏:每查询约 10-30ms
    • 重排:20 篇文档约 100-200ms

核心要点

  1. 没有单一最优方法:稠密与稀疏各有所长,互为补充
  2. 混合检索胜出:组合方法通常能改善结果
  3. 重排很重要:神经重排能显著提升相关性
  4. 并行处理:对生产级性能至关重要
  5. 教学价值:理解取舍有助于选择正确方法

延伸阅读

许可证

本项目为学习用途的教学项目。


作者与出处
原作者: bojieli
来源:bojieli
许可证:Apache-2.0
整理: 灏天文库整理
由灏天文库结构化整理,提供目录导航、全文检索与在线阅读,便于系统化学习
发布者: 作者: bojieli 转发
评论区 (0)
U