Elasticsearch:使用 rescore 来为过滤后的搜索结果重新打分

2,700 阅读8分钟

Rescore 可以帮助提高精度,方法是仅对 querypost_filter 阶段返回的顶部(例如 100 - 500)文档进行重新排序,使用辅助(通常成本更高)算法,而不是将成本算法应用于索引中的所有文档。Rescore 将是一个新查询,它将根据你定义的条件对结果重新排序。 这里的重点是 rescore 仅应用于你的查询首先返回的结果。

在每个分片返回其结果以由处理整个搜索请求的节点排序之前,在每个分片上执行 rescore 请求。

如果你想了解整个搜索流是如何工作的,请详细阅读之前的文章 “Elasticsearch:彻底理解 Elasticsearch 数据操作”。

目前,rescore API 只有一种实现:query rescorer,它使用 query 来调整评分。 将来,可能会提供替代的 rescorer,例如,成对的 rescorer。

注意:如果 rescore 查询提供了显式排序 sort(降序的 _score 除外),则会抛出错误。

注意:当向用户展示分页时,你不应在浏览每个页面时更改 window_size(通过传递不同 from 的值),因为这会改变 top hits 值,导致结果在用户浏览页面时发生混乱的变化。

Query rescorer

Query rescorer 仅对 querypost_filter 阶段返回的 Top-K 结果执行第二个查询。 将在每个分片上检查的文档数量可以通过 window_size 参数控制,默认为 10。

默认情况下,原始查询和 rescore 查询的分数线性组合以生成每个文档的最终 _score。 原始查询和 rescore 查询的相对重要性可以分别用 query_weight 和 rescore_query_weight 控制。 两者都默认为 1。比如:



1.  POST /_search
2.  {
3.     "query" : {
4.        "match" : {
5.           "message" : {
6.              "operator" : "or",
7.              "query" : "the quick brown"
8.           }
9.        }
10.     },
11.     "rescore" : {
12.        "window_size" : 50,
13.        "query" : {
14.           "rescore_query" : {
15.              "match_phrase" : {
16.                 "message" : {
17.                    "query" : "the quick brown",
18.                    "slop" : 2
19.                 }
20.              }
21.           },
22.           "query_weight" : 0.7,
23.           "rescore_query_weight" : 1.2
24.        }
25.     }
26.  }


分数的组合方式可以通过 score_mode 来控制:

Score mode描述
total添加原始分数和重新分数查询分数。 默认值。
multiply将原始分数乘以 rescore 查询分数。 对 function query 重新评分很有用。
avg平均原始分数和 rescore 查询分数。
max取原始分数和 rescore 查询分数的最大值。
min取原始分数和 rescore 查询分数的最小值。

多个 rescore

也可以按顺序执行多个重新评分:



1.  POST /_search
2.  {
3.     "query" : {
4.        "match" : {
5.           "message" : {
6.              "operator" : "or",
7.              "query" : "the quick brown"
8.           }
9.        }
10.     },
11.     "rescore" : [ {
12.        "window_size" : 100,
13.        "query" : {
14.           "rescore_query" : {
15.              "match_phrase" : {
16.                 "message" : {
17.                    "query" : "the quick brown",
18.                    "slop" : 2
19.                 }
20.              }
21.           },
22.           "query_weight" : 0.7,
23.           "rescore_query_weight" : 1.2
24.        }
25.     }, {
26.        "window_size" : 10,
27.        "query" : {
28.           "score_mode": "multiply",
29.           "rescore_query" : {
30.              "function_score" : {
31.                 "script_score": {
32.                    "script": {
33.                      "source": "Math.log10(doc.count.value + 2)"
34.                    }
35.                 }
36.              }
37.           }
38.        }
39.     } ]
40.  }


第一个获取查询结果,然后第二个获取第一个的结果,依此类推。 第二个分数将 “看到” 第一个分数完成的排序,因此可以在第一个分数上使用大窗口将文档拉入第二个分数的较小窗口。

例子

我们首先来说使用如下命令来创建一个叫做 movies 的索引:



1.  PUT movies
2.  {
3.    "settings": {
4.      "analysis": {
5.        "analyzer": {
6.          "en_analyzer": {
7.            "tokenizer": "standard",
8.            "filter": [
9.              "lowercase",
10.              "stop"
11.            ]
12.          },
13.          "shingle_analyzer": {
14.            "type": "custom",
15.            "tokenizer": "standard",
16.            "filter": [
17.              "lowercase",
18.              "shingle_filter"
19.            ]
20.          }
21.        },
22.        "filter": {
23.          "shingle_filter": {
24.            "type": "shingle",
25.            "min_shingle_size": 2,
26.            "max_shingle_size": 3
27.          }
28.        }
29.      }
30.    },
31.    "mappings": {
32.      "properties": {
33.        "title": {
34.          "type": "text",
35.          "analyzer": "en_analyzer",
36.          "fields": {
37.            "suggest": {
38.              "type": "text",
39.              "analyzer": "shingle_analyzer"
40.            }
41.          }
42.        },
43.        "actors": {
44.          "type": "text",
45.          "analyzer": "en_analyzer",
46.          "fields": {
47.            "keyword": {
48.              "type": "keyword",
49.              "ignore_above": 256
50.            }
51.          }
52.        },
53.        "description": {
54.          "type": "text",
55.          "analyzer": "en_analyzer",
56.          "fields": {
57.            "keyword": {
58.              "type": "keyword",
59.              "ignore_above": 256
60.            }
61.          }
62.        },
63.        "director": {
64.          "type": "text",
65.          "fields": {
66.            "keyword": {
67.              "type": "keyword",
68.              "ignore_above": 256
69.            }
70.          }
71.        },
72.        "genre": {
73.          "type": "text",
74.          "fields": {
75.            "keyword": {
76.              "type": "keyword",
77.              "ignore_above": 256
78.            }
79.          }
80.        },
81.        "metascore": {
82.          "type": "long"
83.        },
84.        "rating": {
85.          "type": "float"
86.        },
87.        "revenue": {
88.          "type": "float"
89.        },
90.        "runtime": {
91.          "type": "long"
92.        },
93.        "votes": {
94.          "type": "long"
95.        },
96.        "year": {
97.          "type": "long"
98.        },
99.        "title_suggest": {
100.          "type": "completion",
101.          "analyzer": "simple",
102.          "preserve_separators": true,
103.          "preserve_position_increments": true,
104.          "max_input_length": 50
105.        }
106.      }
107.    }
108.  }


我们接下来使用 _bulk 命令来写入一些文档到这个索引中去。我们使用这个链接中的内容。我们使用如下的方法:



1.  POST movies/_bulk
2.  {"index": {}}
3.  {"title": "Guardians of the Galaxy", "genre": "Action,Adventure,Sci-Fi", "director": "James Gunn", "actors": "Chris Pratt, Vin Diesel, Bradley Cooper, Zoe Saldana", "description": "A group of intergalactic criminals are forced to work together to stop a fanatical warrior from taking control of the universe.", "year": 2014, "runtime": 121, "rating": 8.1, "votes": 757074, "revenue": 333.13, "metascore": 76}
4.  {"index": {}}
5.  {"title": "Prometheus", "genre": "Adventure,Mystery,Sci-Fi", "director": "Ridley Scott", "actors": "Noomi Rapace, Logan Marshall-Green, Michael Fassbender, Charlize Theron", "description": "Following clues to the origin of mankind, a team finds a structure on a distant moon, but they soon realize they are not alone.", "year": 2012, "runtime": 124, "rating": 7, "votes": 485820, "revenue": 126.46, "metascore": 65}

7.  ....


在上面,为了说明的方便,我省去了其它的文档。你需要把整个 movies.txt 的文件拷贝过来,并全部写入到 Elasticsearch 中。它共有1000 个文档。

现在让我们运行一些测试。 我的第一个查询将没有 rescore,让我们分析结果。



1.  GET movies/_search
2.  {
3.    "_source": ["title","year"], 
4.    "query": {
5.      "match": {
6.        "title": "hunger games"
7.      }
8.    }
9.  }


上面命令运行的结果为:



1.  {
2.    "took": 3,
3.    "timed_out": false,
4.    "_shards": {
5.      "total": 1,
6.      "successful": 1,
7.      "skipped": 0,
8.      "failed": 0
9.    },
10.    "hits": {
11.      "total": {
12.        "value": 6,
13.        "relation": "eq"
14.      },
15.      "max_score": 10.52117,
16.      "hits": [
17.        {
18.          "_index": "movies",
19.          "_id": "sJBkU4YBE_j_JbcsK5n0",
20.          "_score": 10.52117,
21.          "_source": {
22.            "title": "The Hunger Games",
23.            "year": 2012
24.          }
25.        },
26.        {
27.          "_index": "movies",
28.          "_id": "W5BkU4YBE_j_JbcsK5v0",
29.          "_score": 7.5008345,
30.          "_source": {
31.            "title": "The Hunger Games: Catching Fire",
32.            "year": 2013
33.          }
34.        },
35.        {
36.          "_index": "movies",
37.          "_id": "x5BkU4YBE_j_JbcsK5v0",
38.          "_score": 6.5867085,
39.          "_source": {
40.            "title": "Hunger",
41.            "year": 2008
42.          }
43.        },
44.        {
45.          "_index": "movies",
46.          "_id": "sZBkU4YBE_j_JbcsK5r0",
47.          "_score": 6.559334,
48.          "_source": {
49.            "title": "The Hunger Games: Mockingjay - Part 2",
50.            "year": 2015
51.          }
52.        },
53.        {
54.          "_index": "movies",
55.          "_id": "wZBkU4YBE_j_JbcsK5v0",
56.          "_score": 6.559334,
57.          "_source": {
58.            "title": "The Hunger Games: Mockingjay - Part 1",
59.            "year": 2014
60.          }
61.        },
62.        {
63.          "_index": "movies",
64.          "_id": "1ZBkU4YBE_j_JbcsK5v0",
65.          "_score": 5.260585,
66.          "_source": {
67.            "title": "Funny Games",
68.            "year": 2007
69.          }
70.        }
71.      ]
72.    }
73.  }


请注意,我们只有默认的 Elasticsearch 排序。 看到对于我们的搜索条件,“ hunger games” 的结果是较旧的电影。得分最高的电影是 2012 年发布的 “The Hunger Games”。我们希望对于这组结果,较新的电影有一个提升,使它们变得比旧电影更相关。为此,我们将使用 rescorer 查询并添加一个标准,即对于 2013 年以上的任何电影都会添加提升值。 注意 score_query_weight = 5  代表我们想要的。为了降低主要查询文档的分数,让我们将提升更改为 0.5 (query_weight = 0.5)。



1.  GET movies/_search
2.  {
3.    "_source": [
4.      "title",
5.      "year"
6.    ],
7.    "query": {
8.      "match": {
9.        "title": "hunger games"
10.      }
11.    },
12.    "rescore": {
13.      "query": {
14.        "rescore_query": {
15.          "range": {
16.            "year": {
17.              "gte": 2013
18.            }
19.          }
20.        },
21.        "score_mode": "total",
22.        "query_weight": 0.5,
23.        "rescore_query_weight": 5
24.      },
25.      "window_size": 50
26.    }
27.  }


上面命令运行的结果为:



1.  {
2.    "took": 3,
3.    "timed_out": false,
4.    "_shards": {
5.      "total": 1,
6.      "successful": 1,
7.      "skipped": 0,
8.      "failed": 0
9.    },
10.    "hits": {
11.      "total": {
12.        "value": 6,
13.        "relation": "eq"
14.      },
15.      "max_score": 8.750418,
16.      "hits": [
17.        {
18.          "_index": "movies",
19.          "_id": "W5BkU4YBE_j_JbcsK5v0",
20.          "_score": 8.750418,
21.          "_source": {
22.            "title": "The Hunger Games: Catching Fire",
23.            "year": 2013
24.          }
25.        },
26.        {
27.          "_index": "movies",
28.          "_id": "sZBkU4YBE_j_JbcsK5r0",
29.          "_score": 8.279667,
30.          "_source": {
31.            "title": "The Hunger Games: Mockingjay - Part 2",
32.            "year": 2015
33.          }
34.        },
35.        {
36.          "_index": "movies",
37.          "_id": "wZBkU4YBE_j_JbcsK5v0",
38.          "_score": 8.279667,
39.          "_source": {
40.            "title": "The Hunger Games: Mockingjay - Part 1",
41.            "year": 2014
42.          }
43.        },
44.        {
45.          "_index": "movies",
46.          "_id": "sJBkU4YBE_j_JbcsK5n0",
47.          "_score": 5.260585,
48.          "_source": {
49.            "title": "The Hunger Games",
50.            "year": 2012
51.          }
52.        },
53.        {
54.          "_index": "movies",
55.          "_id": "x5BkU4YBE_j_JbcsK5v0",
56.          "_score": 3.2933543,
57.          "_source": {
58.            "title": "Hunger",
59.            "year": 2008
60.          }
61.        },
62.        {
63.          "_index": "movies",
64.          "_id": "1ZBkU4YBE_j_JbcsK5v0",
65.          "_score": 2.6302924,
66.          "_source": {
67.            "title": "Funny Games",
68.            "year": 2007
69.          }
70.        }
71.      ]
72.    }
73.  }


从上面的搜索结果我们可以看出来 2013 的电影 “The Hunger Games: Catching Fire” 现在排在第一名的位置,而之前的那个 2012 年的电影现在排名在第四的位置。也就是说我们对 2013 后的电影的搜索结果进行了加分。

Rescore 是一个很好的重新排序资源,可以根据你定义的最佳排序标准帮助你提高结果列表的效率。