ES(ElasticSearch)-相关性&搜索评分策略

相关性&搜索评分策略

相关性概述

什么是相关性(Relevance)

​ 相关性是衡量搜索结果与用户查询意图匹配程度的核心指标。在Elasticsearch中,每个文档都有_score字段表示相关性分数,值越高表示匹配度越好。

核心算法演进:

  • TF-IDF(早期版本):基于词频和逆文档频率
  • BM25(5.X+之后版本默认):改进的文本相似度算法,解决TF-IDF的不足
  • 语义模型(8.x扩展):支持文本嵌入向量等现代技术

TF-IDF算法

image-20250719212747917

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)))
image-20250719212622236

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 // 内容匹配提升2倍
}
}
}

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 // 内容匹配提升2倍
}
},
"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, // 主查询分数权重(默认1.0)
"rescore_query_weight": 0.3, // 二次评分权重(默认1.0)
"query": {
"rescore_query": {
"function_score": {
"script_score": {
"script": "Math.log10(doc['severity'].value + 2)"
}
}
}
}
}
}
  1. window_size 的作用
    • 表示对主查询结果的前 N 条进行二次评分(这里是前50条),而非全局排序。
    • 若主查询返回100条,window_size=50 则只对前50条重新计算分数,后50条保持原分。
  2. 评分合并逻辑
    • 默认情况下,最终分数 = 主查询分 + 二次评分分。
    • 可通过 query_weightrescore_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"], // 标题权重x3
"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_matchbest_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
// multi_match 查询
{
"query": {
"multi_match": {
"query": "quick brown fox",
"fields": ["title", "body"],
"type": "best_fields",
"tie_breaker": 0.3
}
}
}

// 等效的 dis_max 查询
{
"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 子句的基本特性:

  1. 属于 bool 查询的一部分:用于表示”或”逻辑

  2. 匹配越多 should 条件,得分越高:

  3. 默认情况下,文档只需匹配一个 should 条件就会被返回

  4. 通过 minimum_should_match 参数可以控制最少匹配数量

评分特点:

  1. 每个匹配的 should 条件都会贡献到总分

  2. 匹配的条件越多,总分通常越高

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)

multi_match:corss_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"
}
}
}