10. 检索(2)

174 阅读10分钟

2. 高亮、排序和分页

2.1 高亮

1. 高亮定义

以给定的高亮语法格式,在一个或者多个字段中使待检索的字段形成高亮或者突出显示的片段。

2. 高亮语法

  • fragment_size:每个高亮片段的字符数。默认为100。
  • number_of_fragments:高亮最大片段数。如果片段数设置为0,则不返回任何高亮片段,而是将整个字段内容突出显示并返回,同时fragment_size将被忽略。默认值为5。
  • fields:待高亮字段。假设指定comment_*,则以comment_开头的所有text、keyword类型的字段都会被高亮显示。

3. 高亮实战

使用了Kibana自带的电子商务数据集

POST kibana_sample_data_ecommerce/_search
{
  "_source": "products.product_name",
  "query": {
    "match": {
      "products.product_name": "dress"
    }
  },
  "highlight": {
    "number_of_fragments": 0,
    "fragment_size": 150,
    "fields": {
      "products.product_name":{
        "pre_tags":   "<em>",
        "post_tags": "</em>"
         
      }
    }
  }
}

从结果中可以看出我们检索的关键字dress被高亮显示,在该字符之前是<em>,在该字符之后是</em>。

2.2 排序

1. 排序定义

es支持在查询时自定义排序规则,这使得用户可以根据实际需求对查询结果进行排序。排序可以基于文档评分、字段值或者自定义脚本实现。在编写查询语句时,通过在请求体中添加sort字段并指定排序规则,即可实现对查询结果的排序。

在一个或多个特定字段上,可以添加一种或多种排序方式。排序是在字段级别上定义的,其中特殊字段名如下。

  • _score:按分数排序。
  • _doc:按索引顺序排序(通常用在scroll遍历上)。

能排序的字段都具备正排索引,单text类型字段是不可以排序的,

# 上一章的那个乌兰索引
POST lwy_index/_search
{
  "sort": [
    {
      "title": {
        "order": "desc"
      }
    }
  ]
}

如果要使text字段支持排序、聚合,则需要开启fielddata。

2. 排序语法

sort后跟数组,表示支持一个或多个字段组合排序。

"sort": [
    {
      "title": {
        "order": "desc"
      }
    },
    {
      "age": {
        "order": "asc"
      }
    }
  ]

注意:sort是和query平级的,并不会被query包含。

3. 排序实战

在如下示例中使用了两种排序方式,这些字段首先基于popular_degree降序排列,在popular_degree相同的情况下再基于_score进行升序排列。

POST lwy_index/_search
{
  "query": {
    "match": {
      "title": "乌兰"
    }
  },
  "sort": [
    {
      "popular_degree": {
        "order": "desc"
      }
    },
    {
      "_score":{
        "order":"asc"
      }
    }
  ]
}

2.3 分页

1. 分页介绍 es支持对查询结果进行分页处理,允许用户逐步获取和浏览大量数据。在编写查询语句时,可通过在请求体中添加from和size字段实现分页。from表示结果集的起始位置,size表示每页返回的文档数量。这两个字段的默认值分别为0和10,即默认返回查询结果的前10条记录

主流的搜索引擎都是分页展示结果数据的。

对于海量数据来说,用户关注的往往是自己最关心的TOP N条数据。搜索引擎又是怎么知道哪些结果数据是用户最关注的呢?最原始的办法就是利用评分机制(如TF-IDF机制)实现:当评分越高时,该条数据返回的位置越靠前。

2. 分页语法

POST lwy_index/_search
{
  "from": 0,
  "size": 20,
  "query": {
    "match_all": {}
  }
}

如果将from设置为11、size设置为10,则返回的是第10~19条数据(默认从第0条开始)。以此类推,就能实现翻页。

3. 自定义评分

信息检索时,返回结果的相关度是一个至关重要的因素。es使用评分算法,根据查询条件与索引文档的匹配程度来确定每个文档的相关度。为了满足各种特定的业务需求,也允许用户自定义评分。

自定义评分的主要作用如下。

  1. 排序偏好:通过在搜索结果中给每个文档自定义评分,可以更好地满足搜索用户的排序偏好。
  2. 特殊字段权重:通过给特定字段赋予更高的权重,可以让这些字段对搜索结果的影响更大。
  3. 业务逻辑需求:根据业务需求,可以定义复杂的评分逻辑,使搜索结果更符合业务需求。
  4. 自定义用户行为:可以使用用户行为数据(如点击率)作为评分因素,提高用户搜索体验。

总的来说,自定义评分是用来优化es默认评分算法的一种有效方法,可以更好地满足特定应用场景的需求。

3.1 搜索结果相关度与自定义评分的关系

搜索引擎本质是一个匹配过程,即从海量的数据中找到匹配用户需求的内容。判定内容与用户查询的相关度一直是搜索引擎领域的核心研究课题之一。如果搜索引擎不能准确地识别用户查询的意图并将相关结果排在前面的位置,那么搜索结果就不能满足用户的需求,从而影响用户对搜索引擎的满意度。 image.png

3.2 控制es相关度

在结构化数据库(如MySQL)中只能查询结果与数据库中的行(row)是否匹配,而此类查询所返回的结果往往是二元的,即“是”或“否

而通过全文搜索引擎Elasticsearch,不仅能找到匹配的文档,还可以按照相关度的高低对它们进行排序。

实现返回结果按相关度排序的核心是评分。

在下面示例中,_score就是es检索返回的评分,其值可以衡量每个文档与查询的匹配程度,即相关度。每个文档都有对应的评分,该得分由正浮点数表示。文档评分越高,则该文档的相关度越高。

image.png

3.3 影响相关度评分的查询子句

在布尔查询中,每个must、should和must_not元素都称为查询子句。

根据文档满足must或should标准的程度,可以确定文档的相关度评分。分数越高,文档就越符合的搜索条件。

must_not子句中的条件被视为“过滤器”。它会决定文档是否包含在结果中,但不会影响文档的评分方式。在must_not里可以显式指定任意过滤器,以基于结构化数据来决定包含或排除文档。

filter表示“必须匹配”,但它不以评分,而以过滤模式来实现匹配。filter内部语句对评分没有贡献,只是根据过滤标准来决定包含或排除文档。

简单来说,filter、must_not不影响评分,其他查询子句会影响评分。

3.4 自定义评分定义

自定义评分的核心是通过修改评分来修改文档相关度,返回用户最期望的结果。

以下是几种主要的自定义评分策略。

3.4.1 index boost:在索引层面修改相关度

这种方式能在跨多个索引搜索时为每个索引配置不同的级别。所以它适用于索引级别调整评分。

实战举例:一批数据里有不同的标签,数据结构一致,要将不同的标签存储到不同的索引(A、B、C),并严格按照标签来分类展示(先展示A类,然后展示B类,最后展示C类),应该用什么方式查询呢?

具体实现如下。借助indices_boost提升索引的权重,让A排在最前,其次是B,最后是C。

 PUT index_100a/_doc/1
 {
   "subject":"subject 1"
 }

 PUT index_100b/_doc/1
 {
   "subject":"subject 1"
 }
 PUT index_100c/_doc/1
 {
   "subject":"subject 1"
 }
 
 POST index_100*/_search
 {
   "indices_boost": [
     {
       "index_100a": 1.5
     },
     {
       "index_100b": 1.2
     },
     {
       "index_100c": 1
     }
   ],
   "query": {
     "term": {
       "subject.keyword": {
         "value": "subject 1"
       }
     }
   }
 }
3.4.2 boosting:修改文档相关度

boosting可在查询时修改文档的相关度。

boosting值所在范围不同,含义也不同。若boosting值为0~1,如0.2,代表降低评分;若boosting值>1,如1.5,则代表提升评分。

适用于某些特定的查询场景,用户可以自定义修改满足某个查询条件的结果评分。

POST lwy_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title":  {
              "query": "乌兰新",
              "boost": 20
            }
          }
        }
      ]
    }
  }
}
3.4.3 negative_boost:降低相关度

negative_boost仅对查询中定义为negative的部分生效。

计算评分时,不修改boosting部分评分,而negative部分的评分则乘以negative_boost的值。

negative_boost取值为0~1.0,如0.3。

若对某些返回结果不满意,但又不想将其排除(must_not),则可以考虑采用negative_boost的方式。

实战如下。搜索title字段,返回包含“乌兰”(positive部分)但是不包含“新闻”(negative部分)的文档。对于那些既包含“乌兰”又包含“新闻”的文档,其评分将被降低为原评分的1/10(由negative_boost参数决定)

POST lwy_index/_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "title": "乌兰"
        }
      },
      "negative": {
        "match": {
          "title": "新闻"
        }
      },
      "negative_boost": 0.1
    }
  }
}
3.4.4 function_score 自定义评分

该方式支持用户自定义一个或多个查询语句及脚本,达到精细化控制评分的目的,以对搜索结果进行高度个性化的排序设置。适用于需进行复杂查询的自定义评分业务场景。

实战举例1:若商品信息如表所示,如何同时根据销量和浏览人数进行相关度提升?

商品销量/件浏览人数/人
A1010
B2020

可以设计:评分 = 原始评分 x (销量+浏览人数)

PUT lwy_index1/_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}

POST lwy_index1/_search
{
  "query": {
    "function_score": {
      "query": {
        "match_all": {}
      },
      "script_score": {
        "script": {
          "source": "_score * doc['sales'].value+doc['visitors'].value"
        }
      }
    }
  }
}

实战举例2:针对乌兰索引,基于popular_degree字段计算评分,使评分曲线相对平滑,没有大的波动

POST lwy_index/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": {
          "title": "乌兰"
        }
      },
     "field_value_factor": {
       "field": "popular_degree",
       "modifier": "log1p",
       "factor": 0.1,
       "missing":1
     },
     "boost_mode": "sum"
    }
  }
}

评分公式:

new_score = old_score+log(1+0.1 x popular_degree)

使用field_value_factor时要注意,有的文档可能会缺少这个字段,则需以missing充当缺失字段的默认值。

实战举例3:在同一个索引里以固定的查询方式返回结果,其相关度评分能否保持在一定范围之内,比如0~100分?这样当对某些词语或文档进行搜索时,就可以知道在索引里面是否存在满足相关度需求的文档了。

回答如下。

  • 利用"modifier":"log1p",通过对原始评分的对数变换,评分结果会表现得更加“平滑
  • 通过设置max_boost参数,可以使新评分不超过特定范围。例如,将该参数的值设置为100,则所有文档的评分都不会超过100。max_boost参数的默认值为FLT_MAX,即浮点数的最大值
3.4.5 rescore_query:查询后二次打分

指重新计算查询所返回的结果文档中指定文档的得分。es会截取查询返回的前N条结果,并使用预定义的二次评分方法来重新计算其得分。

但对全部有序的结果集进行重新排序的话,开销势必很大,使用rescore_query可以只对结果集的子集进行处理。该方式适用于对查询语句的结果不满意,需要重新打分的场景。

POST lwy_index/_search
{
 "query": {
   "match": {
     "title": "乌兰"
   }
 },
 "rescore": {
   "window_size": 50,
   "query": {
     "rescore_query":{
       "function_score":{
         "script_score":{
           "script":{
             "source": "doc['popular_degree'].value"
           }
         }
       }
     }
   }
 }
}

其中,query rescorer仅对query和post_filter阶段返回的前K个结果执行第二次查询。

query rescorer是一种工具,用于对搜索结果进行重新评分和排序。而K是一个用户设定的参数,用于控制重新评分阶段处理的文档数量。

在上述查询示例中,window_size参数的值设为50,这意味着系统将对每个分片上前50个(即K=50)返回结果执行二次查询。这些结果称为“窗口”,因此该参数命名为“window_size”。