一.概述
在前几章中,讲到了如何分词,以及分词的种类。 分词后在进行全文检索时,返回结果如何确定用户真正想看到的, 那数据结果如何排序呢?
比如在电商中:搜索一个商品关键词,默认是综合排序,商品如何顺序是经过一定的算法策略,也是为了提高用户的体验。Elasticsearch使用评分算法,对搜索结果进行排序,评分越高的文档,相关度就越高,排序就越在最前面。
下面探讨自定义评分策略的应用场景、搜索结果的相关度以及如何进行自定义评分,以便在搜索时获得最符合用户需求的结果。
Elasticsearch自定义评分的主要作用如下:
1)排序偏好:通过在搜索结果中给每个文档自定义评分,可以更好地满足搜索用户的排序偏好。
2)特殊字段权重:通过给特定字段赋予更高的权重,可以让这些字段对搜索结果的影响更大。
3)业务逻辑需求:根据业务需求,可以定义复杂的评分逻辑,使搜索结果更符合业务需求。
4)自定义用户行为:可以使用用户行为数据(如点击率)作为评分因素,提高用户搜索体验。
搜索引擎本质是一个匹配过程,即从海量的数据中找到匹配用户需求的内容。判定内容与用户查询的相关度一直是搜索引擎领域的核心研究课题之一。如果搜索引擎不能准确地识别用户查询的意图并将相关结果排在前面的位置,那么搜索结果就不能满足用户的需求,从而影响用户对搜索引擎的满意度。
通过如match的全文检索,搜索出来的结果,每个文档有一个分数,分数字段为_score, 文档如下所示:
"hits" : {
"total" : {
"value" : 777935,
"relation" : "eq"
},
"max_score" : 2.70161664E8,
"hits" : [
{
"_index" : "hqbuy_stock",
"_type" : "_doc",
"_id" : "541753335779440",
"_score" : 2.70161664E8,
"_source" : {
"Id" : "541753335779440",
"StockGuid" : "3a123e8a-690c-ec77-9503-0e2004fa47d4",
"PartNo" : "VTTEST11",
"Brand" : "VELLEMAN",
二.评分概述
Lucene(或Elasticsearch)使用布尔模型查找匹配文档,并用一个名为“实用评分函数”的公式来计算相关度。从Elasticsearch 5之后,默认的打分机制改成了Okapi BM25。其中BM是Best Match的缩写,25是指经过25次迭代调整之后得出的算法,它是由TF-IDF机制进化来的。BM25都使用逆向文档频率来区分普通词(不重要)和非普通词(重要),使用词频来衡量某个词在文档中出现的频率。文档里的某个词出现得越频繁,文档与这个词就越相关,得分越高。
2.1影响相关度评分的查询子句
在布尔查询中,每个must、should和must_not元素都称为查询子句。
根据文档满足must或should标准的程度,可以确定文档的相关度评分。分数越高,文档就越符合的搜索条件。
must_not子句中的条件被视为“过滤器"。它会决定文档是否包含在结果中,但不会影响文档的评分方式。
filter 是必须匹配条件,决定包含或排除文档。不会影响文档的评分方式。
总结就是:must和should中的子查询会影响评分,而must_not和filter中的子查询不会影响评分。
下面示例在布尔查询中,filter来过滤不影响评分:
POST /hqbuy_stock/_search
{
"query": {
"bool": {
"filter": {
"term": {
"PartNo": {
"value": "TEST"
}
}
}
}
}
}
查询文档结果中_score字估为0,如下所示:
"hits" : [
{
"_index" : "hqbuy_stock",
"_type" : "_doc",
"_id" : "541592065478725",
"_score" : 0.0,
"_source" : {
"Id" : "541592065478725",
"StockGuid" : "3a123bc5-b1e3-718f-3bde-3420aae666b5",
"PartNo" : "TEST",
"Brand" : "TEXAS INSTRUMENTS/德州仪器",
"Encapsulation" : "",
"DateCode" : "2024+",
"DateCodes" : [
{
"Qty" : 1000,
"DateCodeNo" : "2024+"
}
],
三.自定义评分示例
自定义评分的核心是通过修改评分来修改文档相关度,在最前面的位置返回用户最期望的结果。然而,如何实现这样的自定义评分策略,以确保搜索结果能够最大限度地满足用户需求呢?我们可以从多个层面,包括索引层面、查询层面以及后处理阶段着手。
3.1 Index Boost:在索引层面修改相关度
Index Boost这种方式能在跨多个索引搜索时为每个索引配置不同的级别。所以它适用于索引级别调整评分。
例如:一批数据里有不同的标签,数据结构一致,要将不同的标签存储到不同的索引(A、B、C),并严格按照标签来分类展示(先展示A类,然后展示B类,最后展示C类),应该用什么方式查询呢?
借助indices_boost提升索引的权重,让A排在最前,其次是B,最后是C。
PUT my_index_a/_doc/1
{
"subject": "subject 1"
}
PUT my_index_b/_doc/2
{
"subject": "subject 2"
}
PUT my_index_c/_doc/3
{
"subject": "subject 3"
}
检查索引,在索引级别中设置索引boost的相关度值,这里my_index_a的相关度值为最高,搜索的结果得分也会最高。
get my_index_*/_search
{
"indices_boost":[
{
"my_index_a":15
},{
"my_index_b":5
},{
"my_index_c":3
}
],
"query":{
"match_phrase_prefix": {
"subject": {
"query": "subject"
}
}
}
}
搜索文档结果得分如下所示:
"hits" : [
{
"_index" : "my_index_a",
"_type" : "_doc",
"_id" : "1",
"_score" : 2.002971,
"_source" : {
"subject" : "subject 1"
}
},
{
"_index" : "my_index_b",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.66765696,
"_source" : {
"subject" : "subject 2"
}
},
{
"_index" : "my_index_c",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.5469647,
"_source" : {
"subject" : "subject 3"
}
}
]
3.2 boosting:修改文档相关度
boosting可在查询时修改文档的相关度。boosting值所在范围不同,含义也不同。若boosting值为0~1,如0.2,代表降低评分;若boosting值>1,如1.5,则代表提升评分。适用于某些特定的查询场景,用户可以自定义修改满足某个查询条件的结果评分。
POST /hqbuy_stock/_search
{
"query": {
"match_phrase": {
"PartNo.Like": {
"query": "TEST2",
"boost":10
}
}
}
}
3.3 function_score:自定义评分
下面介绍终级杀招 function_score的使用。对于negative_boost---降低相关度,这里不在介绍,用function_score来替代实现更加灵活。
function_score方式支持用户自定义一个或多个查询语句及脚本,达到精细化控制评分的目的,以对搜索结果进行高度个性化的排序设置。适用于需进行复杂查询的自定义评分业务场景。
先看一个简单示例,根据销量和浏览人数进行相关度提升。评分公式: 评分=原始评分*(销量+销售人数)
创建索引并批量加入数据,如下所示:
put my_index_1006/_bulk
{"index":{"_id":1}}
{"name":"A","sales":10,"visitors":10}
{"index":{"_id":2}}
{"name":"B","sales":20,"visitors":20}
{"index":{"_id":3}}
{"name":"C","sales":30,"visitors":30}
检索数据,使用function_score设置自定义评分规则
post my_index_1006/_search
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"script_score": {
"script": "_score * (doc['sales'].value+doc['visitors'].value)"
}
}
}
}
查看文档的评分如下所示:
"hits" : [
{
"_index" : "my_index_1006",
"_type" : "_doc",
"_id" : "3",
"_score" : 60.0,
"_source" : {
"name" : "C",
"sales" : 30,
"visitors" : 30
}
},
{
"_index" : "my_index_1006",
"_type" : "_doc",
"_id" : "2",
"_score" : 40.0,
"_source" : {
"name" : "B",
"sales" : 20,
"visitors" : 20
}
},
{
"_index" : "my_index_1006",
"_type" : "_doc",
"_id" : "1",
"_score" : 20.0,
"_source" : {
"name" : "A",
"sales" : 10,
"visitors" : 10
}
}
]
四.自定义评分实战
搜索背景:元器件商城关键词搜索,自定义评分规则如下:
1)关键词完全匹配型号,显示优先级最高。
2)关键词完全匹配品牌,显示优先级最高。
3)关键词前缀匹配型号,显示优先级往后。
4)关键词前缀匹配品牌,显示优先级往后。
6)关键词模糊匹配型号,显示优先级再往后。
6)关键词模糊匹配品牌,显示优先级再往后。
7)关键词模糊匹配描述、分类,显示优先级再往后。
8)过期的物料--显示往后排。
9)自营的比非自营优先显示。
10) 有库存的比没有库存的优先显示。
自定义的评分搜索脚本如下所示:
POST /stock/_search
{
"track_total_hits": true,
"from": 0,
"highlight": {
"fields": {
"PartNo.Like": {},
"Brand.Like": {},
"SearchKeyword.Like": {}
}
},
"query": {
"function_score": {
"functions": [
{
"script_score": {
"script": {
"source": "def defScore=1; def _expirationDateStamp = doc['ExpirationDateStamp'].value;
if (params.currTime > _expirationDateStamp ) { defScore = defScore+0.1; }else { defScore = defScore+0.2; }
return defScore * _score;",
"params": {
"currTime": 1715334246
}
}
}
},
{
"script_score": {
"script": {
"source": "def defScore=1; def _stockType = doc['StockType'].value;if (_stockType == 1 ) { defScore = defScore+0.5; }
else { defScore = defScore+0.1; } return defScore * _score;"
}
}
},
{
"script_score": {
"script": {
"source": "def defScore=1; def _stockQty = doc['StockQty'].value;if (_stockQty > 0 ) { defScore = defScore+0.3; }
else { defScore = defScore+0.1; } return defScore * _score;"
}
}
}
],
"query": {
"bool": {
"filter": [
{
"term": {
"StockStatus": {
"value": 1
}
}
}
],
"minimum_should_match": 1,
"should": [
{
"term": {
"PartNo": {
"value": "TEST",
"boost": 1000
}
}
},
{
"term": {
"Brand": {
"value": "TEST",
"boost": 1000
}
}
},
{
"match_phrase_prefix": {
"PartNo.Like": {
"query": "TEST",
"boost": 2
}
}
},
{
"match": {
"PartNo.Like": {
"query": "TEST",
"boost": 2
}
}
},
{
"match_phrase_prefix": {
"Brand.Like": {
"query": "TEST",
"boost": 2
}
}
},
{
"match": {
"Brand.Like": {
"query": "TEST",
"boost": 2
}
}
},
{
"match": {
"SearchKeyword.Like": {
"query": "TEST",
"boost": 0.6
}
}
}
]
}
},
"score_mode": "sum"
}
},
"size": 20,
"sort": [
{
"_score": {
"order": "desc"
}
}
],
"_source": {
"excludes": [
"BatchNumber",
"UniqueId",
"SearchKeyword"
]
}
}
参考文献资料:一本书讲透Elasticsearch