一、BM25初排+向量/深度模型精排 系统架构图(Mermaid可直接渲染)
flowchart TD
A[用户输入查询Q] --> B[查询预处理]
B --> B1[分词/去停用词/归一化]
B1 --> C[多路召回层]
%% 召回层(千万级→千级)
C --> C1[关键词召回:倒排索引]
C --> C2[向量召回:FAISS/HNSW]
C1 & C2 --> D[召回结果合并去重]
D --> D1[候选集:500~1000条]
%% BM25初排(千级→百级)
D1 --> E[BM25初排]
E --> E1[计算Score(Q,D)排序]
E1 --> E2[初排结果:Top 50~200条]
%% 精排层(百级→十级)
E2 --> F[精排层]
F --> F1[轻量版:向量余弦相似度]
F --> F2[效果版:Cross-Encoder深度模型]
F1 & F2 --> G[精排打分排序]
%% 最终输出
G --> H[结果后处理]
H --> H1[去重/过滤低质结果]
H1 --> I[返回最终Top10~50]
I --> J[用户展示]
%% 辅助模块
subgraph 离线预处理
K[文档库] --> K1[分词/特征提取]
K1 --> K2[构建倒排索引(供BM25)]
K1 --> K3[文档Embedding(供向量召回)]
K2 --> C1
K3 --> C2
end
二、Java/Elasticsearch 落地方案
Elasticsearch(ES)是工业界最主流的检索引擎,天然支持BM25,结合Java客户端+向量插件可快速落地整套架构:
1. 环境准备
- ES版本:7.x/8.x(推荐8.x,原生支持向量检索)
- 插件:
# 向量检索插件(ES 8.x已内置,7.x需安装) bin/elasticsearch-plugin install ml-commons - Java客户端:Elasticsearch High Level REST Client(对应ES版本)
2. 核心步骤(Java代码实现)
步骤1:创建索引(包含文本字段+向量字段)
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
public class ESIndexCreator {
// 创建混合索引(BM25文本+向量)
public void createMixedIndex(RestHighLevelClient client, String indexName) throws Exception {
CreateIndexRequest request = new CreateIndexRequest(indexName);
// 构建索引映射:text字段(BM25) + dense_vector字段(向量)
XContentBuilder mapping = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
// 文本字段:用BM25打分
.startObject("content")
.field("type", "text")
.field("analyzer", "ik_max_word") // 中文分词器(需安装ik插件)
.field("similarity", "BM25") // 核心:指定BM25相似度算法
.endObject()
// 标题字段:权重更高(BM25多字段加权)
.startObject("title")
.field("type", "text")
.field("analyzer", "ik_max_word")
.field("similarity", "BM25")
.endObject()
// 向量字段:用于精排
.startObject("content_vector")
.field("type", "dense_vector")
.field("dims", 768) // 匹配BERT-base输出维度
.field("index", true) // 开启向量索引
.field("similarity", "cosine") // 余弦相似度
.endObject()
.endObject()
.endObject();
request.mapping(mapping);
client.indices().create(request, RequestOptions.DEFAULT);
}
}
步骤2:BM25初排(Java实现)
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
public class BM25FirstRank {
// BM25初排:多字段加权,返回Top200
public SearchResponse bm25Rank(RestHighLevelClient client, String indexName, String query) throws Exception {
SearchRequest searchRequest = new SearchRequest(indexName);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 1. 构建多字段BM25查询(标题权重3倍,正文1倍)
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.should(QueryBuilders.matchQuery("title", query).boost(3.0f));
boolQuery.should(QueryBuilders.matchQuery("content", query).boost(1.0f));
// 2. 设置BM25排序(默认按BM25得分降序)
sourceBuilder.query(boolQuery);
sourceBuilder.sort(SortBuilders.scoreSort().order(SortOrder.DESC));
// 3. 初排只返回Top200,减少后续精排压力
sourceBuilder.size(200);
searchRequest.source(sourceBuilder);
return client.search(searchRequest, RequestOptions.DEFAULT);
}
}
步骤3:向量精排(Java实现)
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
public class VectorReRank {
// 向量精排:基于BM25初排结果,用向量余弦相似度重排
public SearchResponse vectorReRank(RestHighLevelClient client, String indexName, String query, float[] queryVector) throws Exception {
SearchRequest searchRequest = new SearchRequest(indexName);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 1. 先通过BM25初排拿到候选集(复用上面的BM25查询)
BM25FirstRank bm25Rank = new BM25FirstRank();
SearchResponse bm25Response = bm25Rank.bm25Rank(client, indexName, query);
// 2. 提取BM25初排的文档ID,限定精排范围
String[] docIds = extractDocIds(bm25Response); // 自定义方法:提取Top200文档ID
sourceBuilder.query(QueryBuilders.idsQuery().addIds(docIds));
// 3. 向量精排:按余弦相似度排序
sourceBuilder.sort(
SortBuilders.scriptSort(
"cosineSimilarity(params.queryVector, doc['content_vector']) + 1.0", // 余弦相似度+1避免负数
org.elasticsearch.script.ScriptType.INLINE
)
.param("queryVector", queryVector)
.order(SortOrder.DESC)
);
// 4. 精排后返回Top10
sourceBuilder.size(10);
searchRequest.source(sourceBuilder);
return client.search(searchRequest, RequestOptions.DEFAULT);
}
// 辅助方法:提取BM25结果的文档ID
private String[] extractDocIds(SearchResponse response) {
return response.getHits().getHits().stream()
.map(hit -> hit.getId())
.toArray(String[]::new);
}
}
步骤4:完整链路封装(Java)
public class SearchPipeline {
private RestHighLevelClient esClient;
private EmbeddingModel embeddingModel; // 自定义Embedding模型(如BERT)
// 完整检索链路:BM25初排 → 向量精排
public SearchResponse search(String indexName, String query) throws Exception {
// 1. 查询预处理
String processedQuery = preprocessQuery(query); // 分词、去停用词
// 2. 生成查询向量(调用BERT等模型)
float[] queryVector = embeddingModel.getEmbedding(processedQuery);
// 3. BM25初排
BM25FirstRank bm25Rank = new BM25FirstRank();
SearchResponse bm25Response = bm25Rank.bm25Rank(esClient, indexName, processedQuery);
// 4. 向量精排
VectorReRank vectorReRank = new VectorReRank();
return vectorReRank.vectorReRank(esClient, indexName, processedQuery, queryVector);
}
// 预处理:分词、去停用词(对接IK分词器)
private String preprocessQuery(String query) {
// 实现自定义预处理逻辑
return query;
}
}
3. 关键配置与优化(ES落地必看)
- BM25参数调优(elasticsearch.yml):
index.similarity.bm25.k1: 1.2 # 词频饱和参数 index.similarity.bm25.b: 0.75 # 长度归一化参数 - 向量索引优化:
- 维度选择:768(BERT-base)/ 384(MiniLM)
- 索引类型:
hnsw(高效近似近邻) - 参数:
m: 16, ef_construction: 100(平衡速度/精度)
- 性能优化:
- 初排数量:建议50~200(太多耗性能,太少丢相关文档)
- 缓存:缓存热门查询的BM25结果和向量
- 分片:按数据量合理分片(建议每分片10~30GB)
总结
- 架构核心:先通过BM25从千万级文档快速筛选出200条左右候选集,再用向量/深度模型对小候选集做语义精排,兼顾速度与效果。
- ES落地关键:文本字段指定BM25相似度算法,新增dense_vector字段存储文档向量,通过ID限定精排范围,最后按余弦相似度重排。
- 参数原则:BM25初排数量控制在50~200,向量维度选384/768,BM25默认k1=1.2、b=0.75即可满足大部分场景。
注意:本文归作者所有,未经作者允许,不得转载