3.4 复合查询
3.4.2 加权查询...................................... 93
5.4 使用查询加权影响得分 .........................150
5.4.1 加权............................................150
5.4.2 为查询添加加权.........................150
5.4.3 修改得分 ....................................153
5.5 索引时加权何时有意义 .........................160
5.5.1 在输入数据中定义字段加权......160
加权查询封装了两个查询,并且降低其中一个查询返回文档的得分。加权查询中有三个节点需要定义:
positive部分,包含所返回文档得分不会被改变的查询;
negative部分,返回的文档得分将被降低;
negative_boost部分,包含用来降低negative部分查询得分的加权值。
加权查询的优点是,positive部分和negative部分包含的查询结果都会出现在搜索结果中,而某些查询的得分将被降低。如果使用布尔查询的must_not节点,将得不到这样的结果。
假设我们想要一个简单的词条查询,查询title字段中含有crime词条,希望这样的文档得分不被改变,同时要year字段在1800~1900内的文档,但这样文档的得分要有一个0.5的加权。这样的查询如下所示:
{
"query" : {
"boosting" : {
"positive" : {
"term" : {
"title" : "crime"
}
},
"negative" : {
"range" : {
"year" : {
"from" : 1800,
"to" : 1900}
}},
"negative_boost" : 0.5
}}
}
上一章介绍了什么是得分以及Elasticsearch如何计算它。随着应用程序的增长,提高搜索质量的需求也进一步增大。我们把它叫做搜索体验。我们需要知道什么对用户更重要,关注用户如何使用搜索功能。这导致不同的结论,例如,有些文档比其他的更重要,或特定查询需强调一个字段而弱化其他字段。这就是可以用到加权的地方。
加权是一个评分过程中额外使用的值。我们已经知道它适用于下列地方。
query:这可以通知搜索引擎,给定查询是复杂查询的一部分,而且比其他部分更重要。
field:有几个文档字段对用户非常重要。例如,以Bill搜索电子邮件,应该首先列出那些发送自Bill的邮件,紧随其后列出主题中含有Bill的邮件,然后是内容中提到Bill
的邮件。
指定给查询或字段的加权值只是计算分数时的众多因素之一,我们都应意识到这一点。现在,看几个查询的例子。
假设索引有两个文档,第一个文档如下所示:
{ "id" : 1,
"to" : "John Smith",
"from" : "David Jones",
"subject" : "Top secret!"
}
第二个文档如下所示:
{
"id" : 2,
"to" : "David Jones",
"from" : "John Smith",
"subject" : "John, read this document"
}
数据很简单,但它应该很好地描述了我们的问题。现在,假设有以下查询:
{
"query" : {
"query_string" : {
"query" : "john",
"use_dis_max" : false
}}
}
在这个例子中,Elasticsearch将为_all字段创建一个查询,并将查找包含所需文字的文档。通过把use_dis_max参数设为false,告诉Elasticsearch不希望使用disjunction查询(如果你不记得disjunction查询,请参阅3.3节中的最大分查询和字符串查询部分)。很容易猜到,我们的两条记录都将返回,标识符等于2的那个将第一个返回。这是由于John分别出现在from字段和subject字段。检查一下结果:
"hits" : {
"total": 2,
"max_score": 0.13561106,
"hits": [{
"_index": "messages",
"_type": "email",
"_id": "2",
"_score": 0.13561106,"_source":
{"id": 1,"to": "David Jones","from":
"John Smith","subject": "John, read this document"}
},{
"_index": "messages",
"_type": "email",
"_id": "1","_score": 0.11506981,"_source": {"id": 2,"to": "John Smith","from": "David Jones","subject": "Top secret!"}
}]}
一切都正常吗?技术上来说,是的。但我认为第二个文档应该出现在结果列表的第一位,因为搜索时,在许多情况下,最重要的因素是匹配人,而不是消息主题。你可能不同意,但这正好解释了为什么全文搜索的相关性是一个困难的课题:有时,很难判断在特定情况下哪个排序更好。我们能做什么?首先,重写查询,间接告知Elasticsearch应使用哪些字段搜索,如下所示:
{
"query" : {
"query_string" : {
"fields" : ["from", "to", "subject"],
"query" : "john",
"use_dis_max" : false
}}
}
这和上一个示例查询不完全一样。运行它,将得到同样的结果,但如果仔细观察,你会发现在得分上的差异。在上一个示例中,Elasticsearch只使用一个字段,_all。现在我们在3个字段上搜索。这意味着有些因素改变了,如字段长度等。不过,这在我们的例子中不是那么重要。在底层,Elasticsearch生成由3个查询组成的复杂查询:每个字段一个查询。当然,每个查询的贡献得分取决于这个字段上找到的词条数以及这个字段的长度。我们介绍一些字段之间的差异。将下面的查询同前面的查询相比较:
{
"query" : {
"query_string" : {
"fields" : ["from^5", "to^10", "subject"],"query" : "john",
"use_dis_max" : false
}}
}
看看高亮显示的部分(^5和^10)。通过这种方式,可以告诉Elasticsearch给定字段的重要程度。我们看最重要的字段是to,其次是from。subject字段的boost为默认值,即1.0。永远记住,这个值只是各种因素之一。你可能想知道为什么选择5,而不是1000或1.23。嗯,这取决于我们想要达到的效果,有什么样的查询,更重要的是在索引中有什么样的数据。通常,当数据有意义的部分发生变化时,也许我们应该再次检查和调整相关性。最后,看一个类似的例子,但这次使用bool查询,如下所示:
{
"query" : {
"bool" : {
"should" : [
{ "term" : { "from": { "value" : "john", "boost" : 5 }}},
{ "term" : { "to": { "value" : "john", "boost" : 10 }}},
{ "term" : { "subject": { "value" : "john" }}}
]
}}
}
前面的示例演示了如何通过加权特定查询组件来影响结果列表。另一种方法是运行一个查询来影响匹配文档的得分。接下来几节将总结Elasticsearch提供的几种可能性。我们将在例子中使用第3章中用过的数据。
constant_score查询允许我们对任何过滤器或查询明确设置一个被用作得分的值,它将通
过加权参数赋给每个匹配文档。
乍看起来,此查询并不实际。但考虑到建立复杂查询的情况,这种查询允许我们设置多少个匹配查询的文档可以影响总分。看看下面的示例:
{
"query" : {
"constant_score" : {
"query": {
"query_string" : {
"query" : "available:false author:heller"
}}
}}
}
在我们的数据中,有两个文档的available字段为false,其中一个在author字段上有值。
但由于constant_score查询,Elasticsearch将在评分时忽略此信息,两个文档的得分都是
下一个与加权相关的查询是加权查询。它允许我们定义一个查询的额外部分,用于降低匹配文档的得分。下面的例子列出所有图书,但E.M.Remarque写的书得分将低10倍:
{
"query" : {
"term" : {
"available" : true
}},
"negative" : {
"match" : {
"author" : "remarque"
}
},
"negative_boost" : 0.1
}}
}
3. function_score查询
我们已经看过两个允许改变查询返回文档得分的例子。第三个例子,我们想谈谈function_score查询,它比之前的查询更复杂。这个查询在得分计算成本高昂时非常有用,因为它将计算过滤后文档的得分。
(1) 函数查询的结构函数查询的结果很简单,看上去如下所示:
{
"query" : {
"function_score" : {
"query" : { ... },
"filter" : { ... },
"functions" : [
{
"filter" : { ... },
"FUNCTION" : { ... }
}],
"boost_mode" : " ... ",
"score_mode" : " ... ",
"max_boost" : " ... ",
"boost" : " ... "
}}
}一般来说,function_score查询中可以使用查询、过滤、函数和附加参数。每个函数可以
有一个过滤器定义要应用的过滤结果。如果一个函数没有定义过滤器,它将应用到所有文档。function_score查询背后的逻辑很简单。首先,函数匹配文档并基于score_mode参数计
算得分。然后,文档的查询得分由函数计算所得分数结合而成,结合时基于boost_mode参数。我们现在来讨论以下参数。
boost_mode:boost_mode参数允许定义如何将函数查询所计算分数与查询分数结合起来。可以将它设置成下列值。
multiply:这是默认行为,查询得分将与函数计算所得分相乘。
replace:导致查询得分全部被忽略,文档得分等于函数计算所得分。 sum:文档得分等于查询得分和函数得分的总和。
avg:文档得分等于查询得分和函数得分的平均值。 max:文档得分等于查询得分和函数得分的最大值。 min:文档得分等于查询得分和函数得分的最小值。
score_mode:该参数定义了函数计算所得分是如何结合在一起的。以下是该参数可以设置的值。
multiply:这是默认行为,结果是查询得分乘以函数的得分。 sum:把所定义的函数的得分相加。
avg:函数得分等于所有匹配函数得分的平均值。
first:把第一个拥有匹配文档过滤器的函数的得分返回。 max:返回所有函数得分的最大值。 min:返回所有函数得分的最小值。
要记住,可以通过使用function_score查询中的max_boost参数来限制最大计算得分。默认情况下,这个参数的值被设为Float.MAX_VALUE,意思是最大浮点值。
boost参数允许我们为文档设置一个查询范围的加权值。我们还没谈到可以包含到查询的functions节点中的函数,下面是目前可用的函数。
boost_factor函数:这个函数允许我们把文档分数乘以一个给定值。boost_factor参数的值不会被范式化,而是原原本本的值。下面是使用boost_factor参数的一个例子:
{
"query" : {
"function_score" : {
"query" : {
"term" : {
"available" : true
}},
"functions" : [
{ "boost_factor" : 20 }
]}
}}
script_score函数:这个函数允许我们使用一个脚本来计算得分,用于函数返回的得分(然后将落到boost_mode参数定义的行为中去)。使用script_score函数的例子如下所示:
{
"query" : {
"function_score" : {
"query" : {
"term" : {
"available" : true
}},
"functions" : [
{
"script_score" : {
"script" : "_score * _source.copies *
parameter1",
"params" : {
"parameter1" : 12
}
}}
]}
}}
random_score函数:使用这个函数,可以通过指定seed值来生成一个伪随机分数。为了模拟随机性,每次都应指定一个新的seed。一个使用此功能的例子如下所示:
{
"query" : {
"function_score" : {
"query" : {
"term" : {
"available" : true
}},
"functions" : [
{
"random_score" : {
"seed" : 12345
}}
]}
}}
decay函数:除了前面提到的评分函数以外,Elasticsearch包含了额外的decay函数。前述函数给出的分数随着距离变大而变低,该函数则不同。距离基于一个单值的数值型字段(比如日期、地理位置点或标准的数值型字段)计算而来。最容易想到的例子是基于与一个给定点的距离来加权文档。
假设有一个point字段,存储着位置信息,我们希望文档的得分受与用户所在位置的距离影响(比如,用户用手机设备发送请求),用户在52,21这个位置,我们可以发送如下查询:
{
"query" : {
"function_score" : {
"query" : {
"term" : {
"available" : true
}},
"functions" : [
{
"linear" : {
"point" : {
"origin" : "52, 21",
"scale" : "1km",
"offset" : 0,
"decay" : 0.2
}}
}]
}}
}
在前面的示例中,linear是decay函数的名称。使用它时,值将线性衰退。其他可能的值是gauss和exp。我们选择了linear衰减函数,因为当字段值两次超过给定值时,它把分数设置为0。当你想把较低的值赋予太远的文档时,这是非常有用的。
在此给出相关方程,让你了解得分是如何通过给定函数计算的。linear衰减函数使用以下公式来计算得分文档:
score max 0, scale | field value origin |
scale gauss衰减函数使用以下公式来计算得分文档:
scoreexp(fieldvalueorigin)2 2scale2
exp衰减函数使用以下公式来计算得分文档:
scoreexp| fieldvalueorigin|
当然,你不需要每次都使用纸笔计算文档,但偶尔需要时,这些函数很方便。现在,让我们讨论一下查询结构的其余部分。我们想用point字段作得分计算。如果文档没有该字段的值,在计算时会得到一个值为1。
此外,我们提供了额外的参数。origin和scale参数是必需的。origin是中心点,计算从此点开始。scale是衰变率。默认情况下,offset参数设置为0;如果定义了该参数,decay函数将只计算文档值比此参数的值大的文档得分。decay参数告诉Elasticsearch应该降低多少分数,默认设置为0.5。在我们的例子中,我们要求,在1公里的距离,得分应该会减少20%(0.2)。
我们期望新版的Elasticsearch会扩展可用函数的数量,建议跟进官方文档,function_score 查 询 的 专 属 页 面 可 访 问 这 里 : http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl- function-score-query.html。
4. 弃用查询
在介绍function_score查询之后,custom_boost、custom_score和custom_filters_score查询被弃用。以下部分演示如何使用function_score查询实现与这些查询相同的结果。本节为想从Elasticsearch旧版本迁移并通过修改来移除废弃查询的人提供一个参考。
(1) 更换custom_boost_factor查询假设我们有如下的custom_boost_factor查询:
{
"query" : {
"custom_boost_factor" : {
"query": {
"term" : { "author" : "heller" }
},
"boost_factor": 5.0
}
}}
为使用function_score查询来取代上述查询,可以使用下列查询:{
"query" : {
"function_score" : {
"query": {
"term" : { "author" : "heller" }
},
"functions" : [
{ "boost_factor": 5.0 }
]
}}
}
(2) 更换custom_score查询第二种废弃查询是custom_score,假设有如下的custom_score查询:
{
"query" : {
"custom_score" : {
"query" : { "match_all" : {} },
"script" : "_source.copies * 0.5"
}}
}如果想用function_score查询取代它,将如下所示:
{"query" : {
"function_score" : {
"boost_mode" : "replace",
"query" : { "match_all" : {} },
"functions" : [
{
}}
]}
}}
"script_score" : { "script" : "_source.copies * 0.5"
(3) 更换custom_filters_score查询最后要讨论的是custom_filters_score查询,假设有如下查询:
{
"query" : {
"custom_filters_score" : {
"query" : { "match_all" : {} },
"filters" : [
{
"filter" : { "term" : { "available" : true }},
"boost" : 10
}],
"score_mode" : "first"
}
}}
如果想用function_score查询取代它,将如下所示:
{
"query" : {
"function_score" : {
"query" : { "match_all" : {} },
"functions" : [
{
"filter" : { "term" : { "available" : true }},
"boost_factor" : 10
}],
"score_mode" : "first"
}
}}
前一节讨论了对查询进行加权。这种类型的加权非常方便和强大,在大多数情况下满足需求。
然而,如果当我们在建立索引时就知道哪些文档重要,更方便的方法是使用索引时加权。
我们获得独立于查询的一个加权,成本是重建索引(在加权值变化时,我们需要重建索引)。此外,由于加权过程中已经在索引时计算,性能会稍好一些。Elasticsearch把加权的信息存储为规范化信息的一部分。很重要的是,如果把omit_norms设置为true,就不能使用索引时加权。
我们看一个典型的文档定义,如下所示:
{
"title" : "The Complete Sherlock Holmes",
"author" : "Arthur Conan Doyle",
"year" : 1936
}如果想为这个特定文档的author字段加权,结构应该会略有变化,文档看起来应该如下所示:
{
"title" : "The Complete Sherlock Holmes",
"author" : {
"_value" : "Arthur Conan Doyle",
"_boost" : 10.0,
},
"year": 1936
}
就是这些。在上述文档被编制到索引后,我们会让Elasticsearch知道author字段的重要性大于其他字段。
图灵社区会员 打顺顺([email protected]) 专享 尊重版权
在旧版本Elasticsearch中,设置文档范围的加权是可能的。然而,从4.0开始, 1Lucene不支持文档范围的加权,Elasticsearch靠加权所有字段来模拟文档的加权。Elasticsearch 1.0废弃了文档提升,我们决定不写它,因为它将被删除。
值得一提的是可以直接在映射文件中定义字段的加权。下面的示例演示了这一点:
{
"mappings" : {
"book" : {
"properties" : {
"title" : { "type" : "string" },
"author" : { "type" : "string", "boost" : 10.0 }
}
}}
}因为上述加权,所有查询将对以author命名的所有字段加权。这也适用于使用_all字段的
查询。