ES中的二次打分

本文介绍了ElasticSearch的QueryScore功能,用于在原始查询打分后对Top-N文档进行二次打分,提高排序性能。通过设置窗口大小和权重调整原始和二次打分的贡献,以及在Java客户端中的使用示例。

二次打分

之前文章写的Script Score的搜索打分是针对整个匹配结果集的,如果一个搜索匹配了几十万个文档,对着文档使用过Function Score或者Script Score查询进行打分是非常耗时的,整个排序性能大打折扣。针对这种情况,ElasticSearch提供了Query Score功能作为折中方案,它支持只针对返回文档的一部分文档进行打分。

1.1 二次打分简介

Query Score工作的阶段是在原始查询打分之后,它支持对打分后Top-N的文档集合执行第二次查询和打分。通过设置window_size参数可以控制在每个分片上进行二次打分查询的文档数量,在默认情况下window_size的值为10。在默认情况下,文档的最终得分等于原查询和rescore查询的分数之和。当然,还可以使用参数对这两部分的权重进行控制。

1.2 使用示例

索引结构和数据如下:


json

复制代码

 PUT /hotel_painless  {    "mappings": {      "properties": {        "title":{          "type": "text"       },        "price":{          "type": "double"       },        "create_time":{          "type": "date"       },        "full_room":{          "type": "boolean"       },        "location":{          "type": "geo_point"       },        "doc_weight":{          "type": "integer"       },        "tags":{          "type": "keyword"       },        "comment_info":{ //定义comment_info字段类型为object          "properties": {            "favourable_comment":{ //定义favourable_comment字段类型为integer              "type":"integer"           },            "negative_comment":{              "type":"integer"           }         }       },        "hotel_vector":{ //定义hotel_vector字段类型为dense_vector          "type": "dense_vector",          "dims":5       }     }   }  }


bash

复制代码

 POST /_bulk  {"index":{"_index":"hotel_painless","_id":"001"}}  {"title":"文雅假日酒店","price":556.00,"create_time":"20200418120000","full_room":false,"location":{"lat":36.083078,"lon":120.37566},"doc_weight":30,"tags":["wifi","小型电影院"],"comment_info":{"favourable_comment":20,"negative_comment":10},"hotel_vector":[0,3.2,5.8,1.2,0]}  {"index":{"_index":"hotel_painless","_id":"002"}}  {"title":"金都嘉怡假日酒店","price":337.00,"create_time":"20210315200000","full_room":false,"location":{"lat":39.915153,"lon":116.4030},"doc_weight":10,"tags":["wifi","免费早餐"],"comment_info":{"favourable_comment":20,"negative_comment":10},"hotel_vector":[0.7,9.2,5.3,1.2,12.3]}  {"index":{"_index":"hotel_painless","_id":"003"}}  {"title":"金都欣欣酒店","price":200.00,"create_time":"20210509160000","full_room":true,"location":{"lat":39.186555,"lon":117.162007},"doc_weight":10,"tags":["会议厅","免费车位"],"comment_info":{"favourable_comment":20,"negative_comment":10},"hotel_vector":[6,3.2,0.4,9.3,0]}  {"index":{"_index":"hotel_painless","_id":"004"}}  {"title":"金都家至酒店","price":500.00,"create_time":"20210218080000","full_room":true,"location":{"lat":39.915343,"lon":116.422011},"doc_weight":50,"tags":["wifi","免费车位"],"comment_info":{"favourable_comment":20,"negative_comment":10},"hotel_vector":[0.7,3.2,5.1,2.9,0.1]}  {"index":{"_index":"hotel_painless","_id":"005"}}  {"title":"文雅精选酒店","price":800.00,"create_time":"20210101080000","full_room":true,"location":{"lat":39.918229,"lon":116.422011},"doc_weight":70,"tags":["wifi","充电车位"],"comment_info":{"favourable_comment":20,"negative_comment":10},"hotel_vector":[12.1,5.2,5.1,9.2,4.5]}

现在有一个比较简单的查询:查询价格大于300元的酒店,DSL如下:


bash

复制代码

 GET /hotel_painless/_search  {    "query": {      "range": {        "price": {          "gte": 300       }     }   }  }

image-20240414182615236

从结果中可看出,索引中有5个文档,匹配的文档书为4。因为使用的是范围查询,所以匹配的文档得分都为1.如果想提升在上述排序中前两个名称包含“金都”的酒店文档排名。而这两个目标酒店的位置分别为2和3,当前的索引主分片数为1,那么应该设置window_size=3,使用二次打分对查询进行扩展的DSL如下:


json

复制代码

 GET /hotel_painless/_search  {    "query": {      "range": {        "price": { //使用range查询          "gte": 300       }     }   },    "rescore": {      "query": { //对返回的文档进行二次打分        "rescore_query":{            "match":{            "title":"金都"         }       }     },      "window_size": 3 //对每个分片的前3个文档进行二次打分   }  }

在上面的DSL中,二次打分使用rescore进行封装,在rescore中可以设置二次打分的查询query和window_size,window_size设置为3意味着对每个分片的前3个文档进行二次打分,执行上述DSL的结果如下:


json

复制代码

 {   ...    "hits" : {      "total" : {        "value" : 4,        "relation" : "eq"     },      "max_score" : 2.1062784,      "hits" : [       {          "_index" : "hotel_painless",          "_type" : "_doc",          "_id" : "004",          "_score" : 2.1062784,          "_source" : {            "title" : "金都家至酒店",            "price" : 500.0,           ...         }       },       {          "_index" : "hotel_painless",          "_type" : "_doc",          "_id" : "002",          "_score" : 1.977973,          "_source" : {            "title" : "金都嘉怡假日酒店",            "price" : 337.0,         ...         }       },       {          "_index" : "hotel_painless",          "_type" : "_doc",          "_id" : "001",          "_score" : 1.0,          "_source" : {            "title" : "文雅假日酒店",            "price" : 556.0,         }       },       {          "_index" : "hotel_painless",          "_type" : "_doc",          "_id" : "005",          "_score" : 1.0,          "_source" : {            "title" : "文雅精选酒店",   ...         }       }     ]   }  }  ​

image-20240414183426238

通过对比rescore前后的结果可以看到,原有文档的002和004分别排在第二位和第三位,并且得分都是1。在rescore的查询中对TOP3且标题含有“金都”的文档进行了加分操作,因此文档002和文档004的得分得到了提升。因为文档004的标题更短,所以它的分数相对更高一些,处在第一个位置,文档002处在第二个位置。

在默认情况下,当存在二次打分时,文档得分=原始查询分数+二次打分分数文档得分=原始查询分数+二次打分分数

而用户额可以为这两部分的分数设置权重,所以文档得分=原始查询分数×原始查询权重+二次打分分数×二次打分权重文档得分=原始查询分数×原始查询权重+二次打分分数×二次打分权重

可以分别设置query_weightrescore_query_weight,为原始查询权重和二次打分权重赋值,例如下面的DSL设置原始查询权重为0.6,二次打分权重为1.7:


bash

复制代码

 GET /hotel_painless/_search  {    "query": {      "range": {        "price": {          "gte": 300       }     }   },    "rescore": {      "query": {        "rescore_query": {          "match": {            "title": "金都"         }       },        "query_weight": 0.6,        "rescore_query_weight": 1.7     },      "window_size": 3   }  }

image-20240414184057178

1.3 在Java客户端中使用二次打分


scss

复制代码

 @Test  public void getRescoreQuery(){      //构建原始的range查询      RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price")             .gte(300);      SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();      searchSourceBuilder.query(rangeQueryBuilder);//添加原始查询Builder      //构建二次打分的查询      MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "金都");      //构建二次打分Builder      QueryRescorerBuilder queryRescorerBuilder = new QueryRescorerBuilder(matchQueryBuilder);      queryRescorerBuilder.setQueryWeight(0.6f);  //设置原始打分权重      queryRescorerBuilder.setRescoreQueryWeight(1.7f);   //设置二次打分权重      queryRescorerBuilder.windowSize(3); //设置每个分片参加二次打分文档的个数      //添加二次打分Builder      searchSourceBuilder.addRescorer(queryRescorerBuilder);      //创建搜索请求      SearchRequest searchRequest = new SearchRequest("hotel_painless");      searchRequest.source(searchSourceBuilder);//设置查询请求      printResult(searchRequest); //打印搜索结果  }  //打印方法封装,方便查看结果  public void printResult(SearchRequest searchRequest) {      try {          //执行搜索          SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);          //获取搜索结果集          SearchHits searchHits = searchResponse.getHits();          for (SearchHit searchHit : searchHits) {              String index=searchHit.getIndex();  //获取索引名称              String id=searchHit.getId();        //获取文档_id              float score = searchHit.getScore(); //获取得分              String source = searchHit.getSourceAsString();//获取文档内容              System.out.println("index="+index+",id="+id+",score="+score+",source="+source);         }     } catch (IOException e) {          throw new RuntimeException(e);     }  }

image-20240414183959499

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值