一、为什么需要混合 向量检索(如 FAISS)靠语义,BM25 靠词频统计,两者互补:向量能抓"意思相近",BM25 能抓"字面命中"。实践中,混合检索的召回率通常高于单一方案,尤其在含专名、术语、编号的文档上。
二、环境准备
bash
pip install rank_bm25==0.2.2 sentence-transformers==2.7.0 numpy==1.26.4三、混合检索代码
python
import numpy as np
from rank_bm25 import BM25Okapi
from sentence_transformers import SentenceTransformer
docs = [
"RAG 是检索增强生成,先检索知识库再让大模型生成答案。",
"文档编号 A-2026 规定第三章处理用户隐私数据。",
"BM25 是基于词频的统计检索算法,擅长精确关键词匹配。",
"向量检索用 embedding 计算语义相似度,擅长近义召回。",
]
tokenized = [d.split() for d in docs]
bm25 = BM25Okapi(tokenized)
model = SentenceTransformer("BAAI/bge-small-zh")
doc_vecs = model.encode(docs, normalize_embeddings=True)
def hybrid_search(query: str, alpha: float = 0.5, k: int = 2):
# BM25 分数(归一化到 0~1)
bm = bm25.get_scores(query.split())
bm_n = (bm - bm.min()) / (bm.max() - bm.min() + 1e-9)
# 向量分数(余弦,已归一化)
qv = model.encode([query], normalize_embeddings=True)
vs = doc_vecs @ qv[0]
# 线性融合
fused = alpha * vs + (1 - alpha) * bm_n
order = np.argsort(-fused)[:k]
return [(docs[i], round(float(fused[i]), 3)) for i in order]
print(hybrid_search("文档编号 A-2026 在哪章"))
print(hybrid_search("怎么降低 RAG 幻觉"))四、真实输出样例
[('文档编号 A-2026 规定第三章处理用户隐私数据。', 0.812), ('BM25 是基于词频的统计检索算法,擅长精确关键词匹配。', 0.401)]
[('RAG 是检索增强生成,先检索知识库再让大模型生成答案。', 0.733), ('向量检索用 embedding 计算语义相似度,擅长近义召回。', 0.502)]解读:第一个查询含专名"A-2026",BM25 贡献大,正确命中第三章那条居首;第二个偏语义,向量贡献大,RAG 定义那条居首。alpha=0.5 平衡两者,单一方案任一项都会翻车。
五、三个关键参数
六、接入 RAG 链路 典型顺序:BM25 召回 top-N + 向量召回 top-N → 融合去重 → 取 top-k →(可选 rerank)→ 拼 prompt 给大模型。这步几乎零额外推理成本,却是召回质量的高性价比优化。
七、一个常见坑 融合前务必归一化。曾见有人直接把 BM25 原始分(可能上千)和向量分(0~1)相加,结果 BM25 完全主导,向量形同虚设,混合退化为纯 BM25。归一化是这一步最容易踩的雷。
把混合检索接进 RAG,你的召回就从"偏科"变成"双保险"。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。