文章目录
-
- [1. 知识导图:Lucene 评分体系概览](#1. 知识导图:Lucene 评分体系概览)
- [2. 传统霸主:TF-IDF 算法原理](#2. 传统霸主:TF-IDF 算法原理)
-
- [2.1 核心公式拆解](#2.1 核心公式拆解)
- [2.2 TF-IDF 的致命缺陷](#2.2 TF-IDF 的致命缺陷)
- [3. 现代标准:BM25 (Best Matching 25)](#3. 现代标准:BM25 (Best Matching 25))
-
- [3.1 BM25 vs TF-IDF 曲线对比](#3.1 BM25 vs TF-IDF 曲线对比)
- [3.2 BM25 核心公式](#3.2 BM25 核心公式)
- [3.3 关键参数详解 ( k 1 k_1 k1 与 b b b)](#3.3 关键参数详解 ( k 1 k_1 k1 与 b b b))
-
- [流程图:BM25 计算逻辑](#流程图:BM25 计算逻辑)
- [4 实战:如何在 Elasticsearch 中掌控评分](#4 实战:如何在 Elasticsearch 中掌控评分)
-
- [4.1 Explain API:调试神器](#4.1 Explain API:调试神器)
- [4.2 自定义参数配置](#4.2 自定义参数配置)
- 总结
引言
Elasticsearch (ES) 之所以能在海量数据中实现毫秒级的搜索响应,归功于其底层坚实的基石------Apache Lucene。如果说倒排索引(Inverted Index)是 Lucene 的骨架,那么**相关性打分(Scoring)**就是它的灵魂。
为什么搜索 "Apple" 时,展示的是苹果手机而不是苹果水果?为什么有的文档排在第一位?本文将从原理到源码,深度剖析 Lucene 的评分进化史:从经典的 TF-IDF 到现代的 BM25。
1. 知识导图:Lucene 评分体系概览
在深入算法细节之前,我们先通过思维导图宏观了解 Lucene 的评分上下文。
Lucene Scoring
核心目标
找出最符合用户意图的文档
Ranking
基础理论
Boolean Model
VSM
经典算法 TF-IDF
词频
逆文档频率
协调因子
字段长度归一化
缺陷: 词频无限饱和
现代算法 BM25
Best Matching 25
ES 5.0+ 默认算法
k1
b
2. 传统霸主:TF-IDF 算法原理
在 Elasticsearch 5.0 之前,默认的评分算法基于 TF-IDF (Term Frequency - Inverse Document Frequency),并结合了向量空间模型(Vector Space Model, VSM)。
2.1 核心公式拆解
Lucene 的经典 TF-IDF 评分公式并非简单的 T F × I D F TF \times IDF TF×IDF,而是经过了工程化改良:
s c o r e ( q , d ) = c o o r d ( q , d ) ⋅ q u e r y N o r m ( q ) ⋅ ∑ t ∈ q ( t f ( t ∈ d ) ⋅ i d f ( t ) 2 ⋅ t . g e t B o o s t ( ) ⋅ n o r m ( t , d ) ) score(q, d) = coord(q, d) \cdot queryNorm(q) \cdot \sum_{t \in q} ( tf(t \in d) \cdot idf(t)^2 \cdot t.getBoost() \cdot norm(t, d) ) score(q,d)=coord(q,d)⋅queryNorm(q)⋅t∈q∑(tf(t∈d)⋅idf(t)2⋅t.getBoost()⋅norm(t,d))
我们重点关注核心三要素:
-
TF (Term Frequency) - 词频
- 含义:检索词在文档中出现的次数越多,相关性越高。
- Lucene 实现 : t f ( t ∈ d ) = f r e q u e n c y tf(t \in d) = \sqrt{frequency} tf(t∈d)=frequency
- 直觉:一篇文章出现 5 次 "Elasticsearch" 比出现 1 次的更相关。
-
IDF (Inverse Document Frequency) - 逆文档频率
- 含义:检索词在所有文档中出现的越少(越稀有),权重越高。
- Lucene 实现 : i d f ( t ) = 1 + log ( d o c C o u n t d o c F r e q + 1 ) idf(t) = 1 + \log(\frac{docCount}{docFreq + 1}) idf(t)=1+log(docFreq+1docCount)
- 直觉:"的"、"是" 这种词到处都有,权重极低;"Lucene" 这种专业词汇权重极高。
-
Field Norm - 字段长度归一化
- 含义:字段越短,匹配权值越高。
- 直觉:在"标题"中匹配到关键词,通常比在长篇大论的"正文"中匹配到更重要。
2.2 TF-IDF 的致命缺陷
TF-IDF 最大的问题在于 TF 的线性(或平方根)增长。
场景假设:
- 文档 A:出现 "AI" 这个词 1 次。
- 文档 B:出现 "AI" 这个词 10 次。
- 文档 C:出现 "AI" 这个词 100 次。
在 TF-IDF 看来,文档 C 的相关性会极高。但在人类认知中,一篇文章提到 10 次 "AI" 和 100 次 "AI" 可能都仅仅代表它是关于 AI 的文章,相关性并没有 10 倍的差距。这就是**"词频饱和度"**问题。
3. 现代标准:BM25 (Best Matching 25)
为了解决 TF-IDF 的缺陷,Okapi BM25 算法应运而生,并成为 Elasticsearch 5.x 之后的默认算法。BM25 基于概率相关模型(Probabilistic Relevance Model)。
3.1 BM25 vs TF-IDF 曲线对比
BM25 引入了**饱和(Saturation)**机制。当词频增加到一定程度时,分数的增长会趋于平缓。
词频对得分的影响 (TF-IDF vs BM25) 0 5 10 15 20 25 30 Term Frequency (词频) 10 9 8 7 6 5 4 3 2 1 0 Score (得分)
注:上图为示意图,展示了 TF-IDF 随词频持续增长,而 BM25 快速达到渐进极限。
3.2 BM25 核心公式
BM25 的公式看起来复杂,但本质是对 TF-IDF 的优化:
s c o r e ( D , Q ) = ∑ i = 1 n I D F ( q i ) ⋅ f ( q i , D ) ⋅ ( k 1 + 1 ) f ( q i , D ) + k 1 ⋅ ( 1 − b + b ⋅ ∣ D ∣ a v g d l ) score(D, Q) = \sum_{i=1}^{n} IDF(q_i) \cdot \frac{f(q_i, D) \cdot (k_1 + 1)}{f(q_i, D) + k_1 \cdot (1 - b + b \cdot \frac{|D|}{avgdl})} score(D,Q)=i=1∑nIDF(qi)⋅f(qi,D)+k1⋅(1−b+b⋅avgdl∣D∣)f(qi,D)⋅(k1+1)
3.3 关键参数详解 ( k 1 k_1 k1 与 b b b)
BM25 的强大之处在于它是可调优的。
| 参数 | 默认值 | 作用描述 | 图解说明 |
|---|---|---|---|
| k 1 k_1 k1 | 1.2 |
控制词频饱和度 。 值越小,饱和越快(分数越早停止增长); 值越大,越接近 TF-IDF 的线性增长。 | 决定曲线弯曲的"快慢"。 |
| b b b | 0.75 |
控制字段长度归一化 。 b=1: 完全归一化(短字段优势巨大)。 b=0: 禁用归一化(长短字段一视同仁)。 |
决定长文档是否被"惩罚"。 |
流程图:BM25 计算逻辑
输入: 查询词 q, 文档 D
计算 IDF
计算词频 f
计算文档长度 |D|
应用 k1 饱和度
应用 b 长度归一化
组合公式
最终得分
4 实战:如何在 Elasticsearch 中掌控评分
4.1 Explain API:调试神器
要理解为什么文档 A 排在文档 B 前面,必须使用 _explain API。
json
GET /my_index/_explain/1
{
"query": {
"match": {
"content": "elasticsearch"
}
}
}
返回结果解读:
- value: 最终得分。
- description : 描述计算过程,你会看到
idf、tf、boost等具体数值。 - details : 递归展示每个部分的计算细节,比如 BM25 的 k 1 k_1 k1 和 b b b 实际应用情况。
4.2 自定义参数配置
如果你处理的是短文本 (如商品标题),可能希望 b b b 值大一些(完全归一化);如果你处理的是长篇技术文档 ,可能希望降低 b b b 的影响。
在 Mapping 中修改默认算法:
json
PUT /my_custom_index
{
"settings": {
"index": {
"similarity": {
"my_bm25": {
"type": "BM25",
"k1": 1.5,
"b": 0.6
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"similarity": "my_bm25"
}
}
}
}
总结
| 特性 | TF-IDF (Classic) | BM25 (Modern) |
|---|---|---|
| 词频增长 | 线性/平方根 (无上限) | 渐进饱和 (更符合人类直觉) |
| 长度归一化 | 简单倒数 | 可调参数 b b b 控制 |
| 参数调优 | 较少 | k 1 , b k_1, b k1,b 灵活可调 |
| 适用场景 | 短文本、简单场景 | 通用场景,尤其是长短文本混合 |
Apache Lucene 通过从 TF-IDF 到 BM25 的演进,解决了长文档"作弊"和词频无限堆砌的问题。理解这些底层原理,不仅能帮助我们写出更好的 Query DSL,还能在遇到排序异常(Bad Ranking)时,迅速定位是 IDF 偏差、字段长度干扰还是词频饱和度设置不当。