Elasticsearch如何构建基于向量召回 + rerank 的双阶段检索系统
本文将深入解析现代搜索系统中的“双阶段检索架构”,结合向量检索(ANN)与精排模型(rerank),帮助你从零构建高性能、高相关度的语义搜索系统,适用于问答系统、RAG、多轮检索、企业知识库等场景。
目录
- 双阶段检索系统背景与价值
- 系统架构图解
- 向量召回阶段详解
- 精排(rerank)阶段详解
- 全流程代码实战(Elasticsearch + BGE + rerank)
- 多文档样例效果展示
- 性能优化与工程部署建议
- 总结与延伸方向
一、双阶段检索系统背景与价值
为什么要双阶段?
单一方法 | 局限性 |
---|---|
BM25 | 精度低,无法理解语义 |
向量检索 | 速度快但相关性不稳定,特别是前几位 |
rerank | 高精度,但计算代价大 |
→ 所以常用组合是:
向量召回(粗排)+ rerank(精排)
先快速筛出相关文档,再用强模型精确重排序。
二、系统架构图解(文字图)
+-----------------------------+
| 用户查询 Query |
+-----------------------------+
|
v
+-----------------------------+
| 向量嵌入模型(BGE) |
+-----------------------------+
|
v
+-----------------------------+
| 向量召回(Elasticsearch/HNSW)|
| - 取 Top-k 相关文档 |
+-----------------------------+
|
v
+-----------------------------+
| rerank 精排(cross-encoder) |
| - 针对每个候选文档打分 |
| - 得到最终排序结果 |
+-----------------------------+
|
v
+-----------------------------+
| 返回最终结果 |
+-----------------------------+
三、向量召回阶段详解
3.1 嵌入模型选择
推荐使用:BAAI/bge-base-zh
安装:
pip install sentence-transformers
使用:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-base-zh")
query_embedding = model.encode("请问2024年社保缴费标准是多少?")
3.2 向量入库(Elasticsearch)
假设文档段落已分段 + 向量化:
es.index(index="docs", document={
"text": "2024年北京社保缴费基数上限为...",
"embedding": embedding.tolist(),
"doc_id": "doc_001"
})
3.3 向量召回查询
query_vector = model.encode(query)
results = es.search(index="docs", knn={
"field": "embedding",
"query_vector": query_vector.tolist(),
"k": 20,
"num_candidates": 100
})
四、rerank 阶段详解
4.1 精排模型介绍
精排模型通常使用 cross-encoder,能联合输入 query + 文档,更好建模语义相关性。
推荐模型:
cross-encoder/ms-marco-MiniLM-L-6-v2
(英文)bce-reranker-base_v1
(中文)
4.2 安装并使用
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
tokenizer = AutoTokenizer.from_pretrained("shibing624/bce-reranker-base_v1")
model = AutoModelForSequenceClassification.from_pretrained("shibing624/bce-reranker-base_v1")
model.eval()
4.3 精排打分代码
def rerank(query, passages):
scores = []
for passage in passages:
inputs = tokenizer(
query, passage["text"],
return_tensors="pt", padding=True, truncation=True
)
with torch.no_grad():
output = model(**inputs)
score = torch.sigmoid(output.logits)[0].item()
scores.append((passage["text"], score))
return sorted(scores, key=lambda x: x[1], reverse=True)
五、完整流程代码实战(简化版)
from sentence_transformers import SentenceTransformer
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from elasticsearch import Elasticsearch
import torch
# 初始化
es = Elasticsearch()
retriever = SentenceTransformer("BAAI/bge-base-zh")
tokenizer = AutoTokenizer.from_pretrained("shibing624/bce-reranker-base_v1")
rerank_model = AutoModelForSequenceClassification.from_pretrained("shibing624/bce-reranker-base_v1")
rerank_model.eval()
query = "2024年企业职工社保缴费政策"
# Step 1:向量检索召回
query_vec = retriever.encode(query)
resp = es.search(index="docs", knn={
"field": "embedding",
"query_vector": query_vec.tolist(),
"k": 20,
"num_candidates": 100
})
candidates = [hit["_source"] for hit in resp["hits"]["hits"]]
# Step 2:精排
results = []
for c in candidates:
inputs = tokenizer(query, c["text"], return_tensors="pt", truncation=True, padding=True)
with torch.no_grad():
logits = rerank_model(**inputs).logits
score = torch.sigmoid(logits)[0].item()
results.append((c["text"], score))
# 排序
results = sorted(results, key=lambda x: x[1], reverse=True)
# 输出结果
for text, score in results[:5]:
print(f"得分:{score:.3f} 文档:{text}")
六、多文档样例效果展示(示意)
查询:
“北京2024年社保缴费基数变化”
向量召回前5段(示意):
- “2024年社保缴费基数上限为29200元”
- “社保缴纳截止日为每月15日”
- “医保缴费基数为此前年度平均工资”
- “养老保险与社保的区别...”
- “2023年社保标准是...”
rerank 之后结果重排序:
- “2024年社保缴费基数上限为29200元”
- “医保缴费基数为此前年度平均工资”
- “2023年社保标准是...”
- “社保缴纳截止日为每月15日”
- “养老保险与社保的区别...”
→ 前排结果更加聚焦“基数变化”,而不是关键词相似性。
七、性能优化与工程部署建议
模块 | 建议 |
---|---|
向量召回 | 使用 HNSW + num\_candidates ≥ 100 |
精排模型 | 小模型部署 FastAPI / ONNX 加速 |
批量 rerank | tokenizer + model 支持批量输入 |
数据更新 | 向量可离线生成,每天批量入库 |
多语言支持 | 使用 M3E/BGE-m3/LaBSE 等通用模型 |
八、总结与延伸方向
阶段 | 技术方案 | 优点 |
---|---|---|
粗排(召回) | 向量搜索(ANN) | 快速语义定位 |
精排 | cross-encoder rerank | 精准相关性建模 |
合作使用 | 双阶段 | 精度与效率兼得 |
延伸:
- 第三阶段:rerank 后再进行摘要生成(如 RAG)
- 多模态检索:将图像/PDF嵌入纳入同一向量索引
- 向量压缩:使用 Faiss/ScaNN + 向量量化提升性能
评论已关闭