elasticsearch 评分算法BM25 作者:马育民 • 2026-05-21 20:56 • 阅读:10000 # 介绍 BM25(Okapi BM25)是工业界最主流的 **全文检索相关性打分算法**,核心是对 TF‑IDF 做**词频饱和+长度归一化**修正,让短文档更占优、高频词不过度权重、长文档不天然占便宜。下面从原理、公式、参数、流程、优缺点、与 TF‑IDF 对比、工程实现、调优与应用场景,一次性讲透。 # BM25 是什么 - **全称**:Okapi BM25(Best Matching 25),1970s 由 Robertson 等人提出,因是第25版且效果最佳而得名。 - **定位**:**概率检索模型**下的**词袋(Bag‑of‑Words)排序算法**,只看词是否出现、不看语序。 - **用途**:计算**查询 Q 与文档 D 的相关性分数**,分数越高排越前;是 Elasticsearch、Solr、OpenSearch 的**默认相关性算法**。 # 应用场景 1. **传统搜索引擎**:ES、Solr、OpenSearch 默认排序 2. **RAG 检索**:大模型知识库检索(先 BM25 召回,再重排) 3. **站内搜索**:电商商品、新闻、博客、知识库搜索 4. **短文本匹配**:标题、摘要、关键词检索 5. **混合检索**:BM25(关键词)+ 向量检索(语义),兼顾精准与语义 --- # 公式 [](https://www.malaoshi.top/upload/0/0/1GW3LxGlIGTG.png) [](https://www.malaoshi.top/upload/0/0/1GW3LxHuWPaO.png) --- # 全文检索完整流程 1. **建索引阶段** - 文档分词 → 词项列表 - 统计:总文档数 $$N$$、各词 $$n(q_i)$$、文档长度 $$|D|$$、平均长度 $${avgdl}$$ - 计算每个词的 IDF 并存入索引 2. **检索阶段** - 查询分词 → $$(q1 \ldots qn)$$ - 对每个查询词: - 查倒排索引,拿到包含该词的文档列表 - 计算该词在文档中的 TF - 代入公式计算单词语分 - 所有词分数求和 → 文档最终 BM25 分 - 按分数降序排序,返回 Top‑K --- # BM25 vs TF‑IDF | 维度 | TF‑IDF | BM25 | |---|---|---| | 词频 TF | 线性增长,无上限 | 饱和增长,有天花板(\(k_1+1\)) | | 文档长度 | 长文档天然占优 | 归一化,短文档更占优 | | IDF 计算 | 无平滑,易无穷大 | +0.5 平滑,稳定有界 | | 排序效果 | 易被关键词堆砌作弊 | 抗作弊,相关性更准 | | 工业应用 | 早期搜索引擎 | ES/Solr 默认,RAG 检索首选 | --- # 优缺点总结 ### 优点 1. **相关性精准**:解决 TF‑IDF 两大缺陷,排序更符合人类直觉 2. **轻量高效**:纯统计、无训练、可解释、速度快(适合海量数据) 3. **鲁棒性强**:IDF 平滑、词频饱和,抗噪声、抗作弊 4. **易调优**:仅 2 个核心参数,场景适配简单 ### 缺点 1. **词袋假设**:不考虑语序、语义、上下文(如“我吃苹果”≠“苹果吃我”) 2. **无法理解语义**:同义词、近义词、多义词处理弱(需结合词向量/大模型) 3. **依赖分词质量**:中文需精准分词,否则效果差 --- # 工程实现 Python 极简版 ```python import math from collections import defaultdict # 1. 计算 IDF def compute_idf(documents): N = len(documents) idf = defaultdict(int) for doc in documents: unique_words = set(doc) for word in unique_words: idf[word] += 1 # BM25 平滑 IDF for word in idf: idf[word] = math.log((N - idf[word] + 0.5) / (idf[word] + 0.5)) return idf # 2. 计算单文档 BM25 分数 def bm25_score(query, doc, idf, k1=1.2, b=0.75, avgdl=100): doc_len = len(doc) score = 0.0 for word in query: if word not in doc: continue tf = doc.count(word) idf_val = idf[word] # BM25 公式 numerator = tf * (k1 + 1) denominator = tf + k1 * (1 - b + b * doc_len / avgdl) score += idf_val * numerator / denominator return score # 3. 批量检索排序 def bm25_search(query, documents, top_k=3): idf = compute_idf(documents) avgdl = sum(len(doc) for doc in documents) / len(documents) # 计算所有文档分数 scores = [] for i, doc in enumerate(documents): s = bm25_score(query, doc, idf, avgdl=avgdl) scores.append((i, s)) # 排序取 Top-K scores.sort(key=lambda x: x[1], reverse=True) return [documents[i] for i, s in scores[:top_k]] ``` --- # 进阶:BM25 与向量检索结合(RAG 最佳实践) - **BM25 强项**:关键词精准匹配、速度快、可解释 - **向量检索强项**:语义理解、同义词、上下文 - **最佳组合**:**BM25 召回 Top‑50 + 向量重排 Top‑10**,兼顾速度、精准、语义 原文出处:http://malaoshi.top/show_1GW3LxPO7Q2z.html