相关性&搜索评分策略
相关性概述
什么是相关性(Relevance)
相关性是衡量搜索结果与用户查询意图匹配程度的核心指标。在Elasticsearch中,每个文档都有_score字段表示相关性分数,值越高表示匹配度越好。
核心算法演进:
- TF-IDF(早期版本):基于词频和逆文档频率
- BM25(5.X+之后版本默认):改进的文本相似度算法,解决TF-IDF的不足
- 语义模型(8.x扩展):支持文本嵌入向量等现代技术
TF-IDF算法
-%E7%9B%B8%E5%85%B3%E6%80%A7&%E6%90%9C%E7%B4%A2%E8%AF%84%E5%88%86%E7%AD%96%E7%95%A5/image-20250719212747917.png)
BM25算法核心要素
- 词频(TF-term frequency):词项在该文档文档中出现的次数
- 逆文档频率(IDF): 词项在整个索引中的稀有程度
- 字段长度归一化:较短的字段(如标题)中包含的关键词比在较长字段(如正文)中的相同关键词更重要
- 可调参数:
k1(控制词频饱和度),b(控制字段长度影响)
1
| score(D, Q) = ∑(i=1 to n) IDF(qi) * (f(qi, D) * (k1 + 1)) / (f(qi, D) + k1 * (1 - b + b * (|D| / avgdl)))
|
BM25与TF-IDF对比优势:
| 特性 |
TF-IDF |
BM25 |
| 词频饱和度 |
线性增长 |
渐进饱和 |
| 字段长度影响 |
固定权重 |
可调参数(b) |
| 长尾词项处理 |
普通 |
更优 |
| 短文档匹配 |
可能过度匹配 |
更平衡 |
查看ES打分明细
explain:true
1 2 3 4 5 6 7 8 9
| GET /products/_search { "explain": true, "query": { "match": { "description": "wireless headphones" } } }
|
自定义评分
自定义评分的核心是通过修改评分来修改文档的相关性,在最前面的位置返回用户最期望的结果
自定义评分策略
1、index boost 索引级加权
1 2 3 4 5 6 7 8 9 10
| GET /index_*/_search { "query":{ "match_all"{} } "indices_boost": [ { "index_1": 1.5 }, // 提升index_1的权重 { "index_2": 0.8 } ] }
|
2、boosting/negative_boost 文档级加权/减权
boost加权
1 2 3 4 5 6 7 8 9
| GET /news/_search { "query": { "match": { "content": "AI", "boost": 2.0 } } }
|
negative_boost降权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| GET /news/_search { "query": { "boosting": { "positive": { "match": { "content": "AI", "boost": 2.0 } }, "negative": { "match": { "title": "AI" } }, "negative_boost": 0.2 } } }
|
3、function_score 完全自定义评分
场景:评分 = 原始评分 * (销量+浏览人数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| POST my_index_products/_search { "query": { "function_score": { "query": { "match_all": {} }, "script_score": { "script": { "source": "_score * (doc['sales'].value + doc['visitors'].value)" } } } } }
|
4、rescore_query 二次评分
对一定数量的查询结果,二次评分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| GET /logs/_search { "from": 0, "size":10, "query": {"match": {"message": "error"}}, "rescore": { "window_size": 50, "query_weight": 0.7, "rescore_query_weight": 0.3, "query": { "rescore_query": { "function_score": { "script_score": { "script": "Math.log10(doc['severity'].value + 2)" } } } } } }
|
window_size 的作用
- 表示对主查询结果的前 N 条进行二次评分(这里是前50条),而非全局排序。
- 若主查询返回100条,
window_size=50 则只对前50条重新计算分数,后50条保持原分。
- 评分合并逻辑
- 默认情况下,最终分数 = 主查询分 + 二次评分分。
- 可通过
query_weight 和 rescore_query_weight 控制权重(如示例中的 70% 主查询分 + 30% 二次评分分)。
5、function_score 和 rescore_query的差异比对
| 特性 |
function_score |
rescore_query |
| 作用阶段 |
主查询阶段(直接影响原始评分) |
后处理阶段(对初步结果二次调整) |
| 执行顺序 |
在原始查询匹配时立即生效 |
在主查询完成后,对部分结果重新计算评分 |
| 性能影响 |
对所有匹配文档计算,可能成本较高 |
仅对窗口内(window_size)的文档计算,更轻量 |
| 灵活性 |
支持多函数组合(权重、衰减、脚本等) |
仅支持单一评分逻辑(通常用 function_score) |
| 典型场景 |
需要全局性、复杂的业务评分逻辑 |
对 Top-N 结果微调,或避免全局计算的高成本 |
多字段搜索场景优化
最佳字段策略(Best Fields)
某个文档多个匹配字段都匹配命中检索条件时,返回其中评分最高的作为该文档得分
最终评分 = 最佳字段评分 + (tie_breaker * 其他匹配字段评分)
tie_breaker 用于指定非最佳匹配的得分系数,默认是0
multi_match:best_fields
当你使用 multi_match 查询而不指定type参数时,会默认使用best_fields类型
场景:商品搜索(标题/描述分开匹配)
1 2 3 4 5 6 7 8 9 10 11
| GET /products/_search { "query": { "multi_match": { "query": "bluetooth speaker", "type": "best_fields", "fields": ["title^3", "description"], "tie_breaker": 0.3 } } }
|
dis max query
1 2 3 4 5 6 7 8 9 10 11 12
| GET /products/_search { "query": { "dis_max": { "queries": [ {"match": {"title": "phone"}}, {"match": {"features": "waterproof"}} ], "tie_breaker": 0.7 } } }
|
两者关系
实际上,multi_match 的 best_fields 类型在内部会被转换成 dis_max 查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| { "query": { "multi_match": { "query": "quick brown fox", "fields": ["title", "body"], "type": "best_fields", "tie_breaker": 0.3 } } }
{ "query": { "dis_max": { "queries": [ { "match": { "title": "quick brown fox" }}, { "match": { "body": "quick brown fox" }} ], "tie_breaker": 0.3 } } }
|
多数字段统计(Most Field)
某个文档多个匹配字段都匹配命中检索条件时,返回各个字段评分之和
multi_match:most_fields
使用multi_mach 且指定 type = Most Fields
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| PUT /articles { "mappings": { "properties": { "text": { "type": "text" }, "text_english": { "type": "text", "analyzer": "english" } } } }
POST /articles/_search { "multi_match": { "query": "running", "type": "most_fields", "fields": ["text", "text_english"] } }
|
should
should 子句的基本特性:
属于 bool 查询的一部分:用于表示”或”逻辑
匹配越多 should 条件,得分越高:
默认情况下,文档只需匹配一个 should 条件就会被返回
通过 minimum_should_match 参数可以控制最少匹配数量
评分特点:
每个匹配的 should 条件都会贡献到总分
匹配的条件越多,总分通常越高
1 2 3 4 5 6 7 8 9 10 11 12
| { "query": { "bool": { "should": [ { "match": { "title": "quick brown fox" }}, { "match": { "body": "quick brown fox" }}, { "match": { "description": "quick brown fox" }} ], "minimum_should_match": 1 } } }
|
两者关系
| 特性 |
should 多字段实现 |
multi_match 查询 |
| 实现方式 |
手动组合多个 match 查询 |
专用多字段查询语法 |
| 评分策略 |
累加所有匹配字段的评分 |
取决于 type 参数(best/most/cross_fields) |
| 控制精度 |
更灵活,可单独调整每个字段权重 |
统一控制 |
| 使用复杂度 |
更冗长 |
更简洁 |
跨字段搜索(Cross Fields)
某个文档多个字段分别匹配匹配命中检索条件
适用场景:姓名/地址等分散字段
1 2 3 4 5 6 7 8 9 10 11
| GET /users/_search { "query": { "multi_match": { "query": "John Smith", "type": "cross_fields", "fields": ["first_name", "last_name"], "operator": "and" } } }
|