作者:晨星 (CJX0712) · v0.1.0
用顶级开源搭一套现代搜索引擎 / RAG 检索栈的两段式架构:
BM25 + FAISS(LSI) 双路召回 → RRF 融合 → LightGBM LambdaMART 学习重排(旗舰 RankFuse),
在 3 seeds 上 nDCG@10 相对最强基线 +11.73%(0.9086 vs 0.8132,统计显著)。
本系统不从零自研 SOTA。BM25、ANN、LambdaMART 全部直接复用久经工业验证的开源实现, 工程重心放在正确组合、可复现评测、可降级交付上。见 docs/architecture.md §1。
git clone https://github.com/CJX0712/searchforge.git
cd searchforge
python -m venv .venv && .venv/Scripts/python -m pip install -r requirements.lock.txt # Linux: .venv/bin/python
python examples/run_demo.py # 端到端:基准 + 确定性 + 消融 + 失败案例(约 37s,CPU)
python -m searchforge.cli bench --seeds 42,43,44 --out benchmark.json
python -m searchforge.cli run --seed 42
python -m searchforge.cli tune --trials 16 # Optuna 调参(独立验证种子)所有依赖均有 win_amd64 / manylinux 预编译 wheel,无需源码编译、无需下载任何模型权重。
| 方法 | nDCG@10 | nDCG@5 | MAP@10 | Recall@100 |
|---|---|---|---|---|
| random(对照) | 0.0246 ± 0.0021 | 0.0245 ± 0.0043 | 0.0025 ± 0.0002 | 0.0839 ± 0.0009 |
| lexical(BM25, rank_bm25) | 0.8121 ± 0.0159 | 0.8187 ± 0.0183 | 0.1384 ± 0.0061 | 0.7267 ± 0.0199 |
| dense(FAISS + LSI) | 0.6347 ± 0.0168 | 0.6206 ± 0.0167 | 0.1493 ± 0.0067 | 0.9418 ± 0.0200 |
| hybrid(RRF 融合) | 0.8042 ± 0.0164 | 0.7998 ± 0.0121 | 0.1493 ± 0.0061 | 0.9118 ± 0.0220 |
| rankfuse(旗舰) | 0.9086 ± 0.0175 | 0.9080 ± 0.0205 | 0.1493 ± 0.0061 | 0.9118 ± 0.0220 |
门禁:旗舰 0.9086 vs 最强基线 0.8132 → Δ = +0.0954(+11.73%),门槛 +5%, 显著性判据(均值差 > ½(σ₁+σ₂) = 0.019)通过 → PASS。
数字全部来自真实运行输出(benchmark.json),无手填。
- MAP@10 无区分度:dense/hybrid/rankfuse 三者的 MAP@10 完全相同(0.1493)。
原因是三者都把约 10 篇相关文档放进 top-10,AP@10 触及
10/|rel|天花板后 对排序顺序不敏感。因此以分级的 nDCG@10 作为主指标,MAP 仅作参考。 - 旗舰 Recall@100 = hybrid(0.9118),低于 dense(0.9418): 重排只重排 top-N 候选窗口、不扩大召回;而 LSI 稠密通路召回面更广。 这是本架构的已知限制(也是下一步方向,见"已知限制")。
| 配置 | nDCG@10 | MAP@10 | 结论 |
|---|---|---|---|
| all(全部特征) | 0.9021 | 0.1455 | 基准 |
| no_dense(去掉稠密特征) | 0.8912 | 0.1421 | −0.0109,稠密信号有正贡献 |
| no_bm25(去掉词法特征) | 0.8950 | 0.1448 | −0.0071,词法信号有正贡献 |
| no_rrf(去掉融合特征) | 0.8901 | 0.1449 | −0.0120,融合特征贡献最大 |
| no_rerank(不做重排,纯 RRF) | 0.7935 | 0.1442 | −0.1086,重排阶段是主要增益来源 |
每个组件贡献为正且被如实保留;不存在"只报正增益"的筛选。
| qid | achieved | 初排 | 天花板 | 窗口内相关 | 首篇相关位次 | 归因 |
|---|---|---|---|---|---|---|
| 0 | 0.0000 | 0.0000 | 0.8074 | 10/64 | 16 | 召回失败:相关文档在窗口内但初排即被跨主题干扰词压到第 11 位之后 |
| 201 | 0.0000 | 0.0000 | 0.4331 | 3/61 | 28 | 同上 |
| 266 | 0.0000 | 0.0245 | 0.6183 | 6/57 | 6 | 重排劣于初排:该查询上学习到的组合信号失效(负面结果如实保留) |
┌─────────────── 离线建索引 ───────────────┐
语料 ──┬──→ 词法通路:BM25 (rank_bm25) │
└──→ 稠密通路:TF-IDF → TruncatedSVD(LSI) → 归一化 ──→ FAISS IndexFlatIP
│
query ──→ 双路召回 top-K ──→ RRF 融合初排 ──→ top-N 候选 ──→ LambdaMART 重排 ──→ 最终排序
searchforge/
core/ types · errors(E100~E500) · config(ENV 覆盖) · interfaces(Protocol) · seed
data/ 合成语料/查询 + 分级相关性标注(确定性 DGP)
retrieval/ lexical(BM25/TF-IDF 兜底) · dense(FAISS-LSI/numpy 兜底) · hybrid(RRF)
rerank/ features(LTR 特征) · lambdamart(LightGBM/sklearn 兜底)
hpo/ Optuna(独立验证种子,防调参泄漏)
eval/ nDCG@K / MAP@K / MRR@K / Recall@K
pipeline/ SearchPipeline.run() + benchmark()
cli.py run / bench / tune
调用方向单向无环:cli → pipeline → {data, retrieval, rerank, hpo, eval} → core。
| 环节 | 项目 | 权威依据 |
|---|---|---|
| 词法召回 | rank_bm25 |
Robertson & Zaragoza (2009),BM25 至今不可绕过的强基线 |
| 稠密召回 | faiss-cpu |
Johnson/Douze/Jégou (arXiv 1702.08734),Meta ANN 事实标准 |
| 稠密表示 | TruncatedSVD(LSI) |
Deerwester et al. (1990) |
| 多路融合 | Reciprocal Rank Fusion | Cormack, Clarke & Büttcher, SIGIR 2009 |
| 学习重排 | lightgbm LambdaMART |
Burges (MSR-TR-2010-82),工业级 LTR 主力 |
| 超参优化 | optuna |
Akiba et al., KDD 2019 |
| 缺失依赖 | 降级路径 |
|---|---|
rank_bm25 |
词法改用 TF-IDF 余弦 |
faiss |
稠密改用 numpy 精确余弦(同一 LSI 嵌入,结果逐位一致,有单测保证) |
lightgbm |
重排改用 sklearn.LogisticRegression |
| 全部缺失 | 仍端到端跑通(有单测覆盖) |
| 项 | 状态 |
|---|---|
| 单测 | 51 passed |
| 依赖锁定 | requirements.lock.txt(校验无本机绝对路径) |
| 确定性 | 同 seed 两次运行核心指标差 = 0.00e+00 |
| 性能预算 | demo 37.4s(≤60s)、峰值 187MB(≤2GB) |
| lint / format | ruff check + ruff format --check 全绿 |
| CI | GitHub Actions,矩阵 Python 3.12 / 3.13 |
| 密钥自查 | git grep -nE "(sk-|api[_-]?key|token|secret|password)" 无命中 |
| 许可证 | MIT,与全部依赖许可证兼容 |
- 重排不扩大召回:旗舰 Recall@100 受初排窗口约束(0.9118 < dense 0.9418)。 下一步可引入更大候选窗或召回导向的第二阶段。
- 评测基于合成语料:相关性与干扰模式由 DGP 显式控制(便于可复现与难度体检), 未使用 TREC/BEIR 等真实数据集——因为环境不允许下载外部权重/数据。 结论可迁移性应在此前提下理解。
- 稠密表示是 LSI 而非预训练神经编码器:同样受"不下载外部权重"约束。 真实场景中替换为稠密编码器后,双路信号的互补性只会更强。
- 极难查询上重排可能劣于初排(见失败案例 q=266),即学习到的组合信号在部分查询上失效。
- docs/architecture.md —— 架构、DGP 与泄漏控制、难度甜点扫描、基线调参、门禁
- docs/model_card.md —— 模型卡(用途、特征、限制、公平性)
MIT © 晨星 (CJX0712)