Function_query
function_score允许你修改检索文档的分数。例如,如果score函数的计算开销非常大,并且它计算经过筛选的一组文档上的分数便足够了,那么这将非常有用。
function_score 查询 是用来控制评分过程的终极武器,它允许为每个与主查询匹配的文档应用一个函数, 以达到改变甚至完全替换原始查询评分 _score 的目的。
实际上,也能用过滤器对结果的 子集 应用不同的函数,这样一箭双雕:既能高效评分,又能利用过滤器缓存。
要使用function_score,用户必须定义查询一个或多个函数,这些函数计算查询返回的每个文档的新分数。
如果没有 function_score 查询,就不能将全文查询与最新发生这种因子结合在一起评分,而不得不根据评分 _score 或时间 date 进行排序;这会相互影响抵消两种排序各自的效果。这个查询可以使两个效果融合:可以仍然根据全文相关度进行排序,但也会同时考虑最新发布文档、流行文档、或接近用户希望价格的产品。正如所设想的,查询要考虑所有这些因素会非常复杂,让我们先从简单的例子开始,然后顺着梯子慢慢向上爬,增加复杂度。
function_score 仅使用一个函数如下示例:
GET /_search
{
"query": {
"function_score": {
"query": { "match_all": {} },
"boost": "5",
"random_score": {},
"boost_mode":"multiply"
}
}
}
此外,可以组合几个功能。在这种情况下,只有当文档匹配给定的筛选查询时,才可以选择应用该函数。
GET /_search{
"query": {
"function_score": {
"query": { "match_all": {} },
"boost": "5",
"functions": [
{
"filter": { "match": { "test": "bar" } },
"random_score": {},
"weight": 23
},
{
"filter": { "match": { "test": "cat" } },
"weight": 42
}
],
"max_boost": 42,
"score_mode": "max",
"boost_mode": "multiply",
"min_score" : 42
}
}}
Boost :提升整个查询。
有关受支持函数的列表,请参阅函数得分查询。
注意:每个函数的过滤查询产生的分数无关紧要。
如果没有给出函数的过滤器,这相当于指定 "match_all": {}
首先,每个文档都由定义的函数评分。该参数score_mode指定计算得分的组合方式:
multiply:得分乘以(默认)
sum:得分总和
avg:得分是平均的
first:应用具有匹配过滤器的第一个函数
max:使用最高分
min:使用最低分数
因为得分可以在不同的尺度上(例如,衰减函数的得分在0到1之间,但是field_value_factor的得分是任意的),而且有时函数对得分的影响不同,因此每个函数的得分可以根据用户定义的权重进行调整。权重可以在函数数组(上面的例子)中为每个函数定义,并与各自函数计算的得分相乘。如果在没有任何其他函数声明的情况下给出权值,权值就充当一个函数,该函数只返回权值。
如果score_mode被设置为avg,则各个分数将通过加权平均值进行组合。例如,如果两个函数返回得分1和2,并且它们各自的权重分别为3和4,那么它们的得分将组合为(1*3+2*4)/(3+4)而不是(1*3+2*4)/2。
通过设置max_boost参数,可以将新分数限制为不超过某个限制。max_boost的默认值是FLT_MAX。
新计算的分数与查询的分数组合。该参数boost_mode
新分数为functions内的,查询分数为query内的。
boost_mode参数说明:
multiply:查询得分和功能得分相乘(默认)
replace:仅使用函数得分,忽略查询得分
sum:查询得分和函数得分被添加
avg:平均
max:查询分数与函数分数最大值
min:查询分数与函数分数最小值
默认情况下,修改分数不会更改匹配的文档。要排除不满足某个分数阈值的文档,可以将min_score参数设置为所需的分数阈值。
要使min_score工作,需要对查询返回的所有文档进行评分,然后逐一过滤。
function_score函数分类
weight
为每个文档应用一个简单而不被规范化的权重提升值:当weight 为 2 时,最终结果为 2 * _score 。
field_value_factor
使用这个值来修改_score ,如将 popularity 或 votes (受欢迎或赞)作为考虑因素。
random_score
为每个用户都使用一个不同的随机评分对结果排序,但对某一具体用户来说,看到的顺序始终是一致的。
衰减函数 (decay functions)—— linear 、 exp 、 gauss
将浮动值结合到评分_score 中,例如结合 publish_date 获得最近发布的文档,结合 geo_location 获得更接近某个具体经纬度(lat/lon)地点的文档,结合 price 获得更接近某个特定价格的文档。
script_score
如果需求超出以上范围时,用自定义脚本可以完全控制评分计算,实现所需逻辑。
Script_score
该script_score函数允许您使用脚本表达式包装另一个查询并自定义其评分,可选地使用从doc中的其他数字字段值派生的计算。这是一个简单的示例:
GET /_search{
"query": {
"function_score": {
"query": {
"match": { "message": "elasticsearch" }
},
"script_score" : {
"script" : {
"source": "Math.log(2 + doc['likes'].value)"
}
}
}
}}
script_score函数产生的分数必须是非负数,否则将引发错误。
在不同的脚本字段值和表达式之上,可以使用_score脚本参数根据包装好的查询检索分数。
脚本编译被缓存,以便更快地执行。如果脚本有需要考虑的参数,最好重用相同的脚本,并为其提供参数:
GET /_search{
"query": {
"function_score": {
"query": {
"match": { "message": "elasticsearch" }
},
"script_score" : {
"script" : {
"params": {
"a": 5,
"b": 1.2
},
"source": "params.a / Math.pow(params.b, doc['likes'].value)"
}
}
}
}}
注意,与custom_score查询不同,查询的得分与脚本得分的结果相乘。如果您想要禁止这个,请设置“boost_mode”:“replace”
Weight(权重查询)
权重分数允许您将分数乘以提供的权重。这有时是需要的,因为针对特定查询设置的boost值会被规范化,而对于这个score函数则不会。number值的类型为float。
"weight" : number
Random
random_score生成从0到但不包括1的均匀分布的分数。默认情况下,它采用了内部Lucene的文档ID作为随机源,这是非常有效的,但不幸的是不可复制,因为文档可能被合并(merge)重新编号。
如果您希望分数可以重现,可以提供seed 和field。最后的分数将被计算在此基础上种子(seed),字段的最小值计算的考虑文档和盐基于索引名称和碎片id这样的文档有相同的值,但存储在不同的索引得到不同的分数。注意,相同切分内的文档和字段值相同的文档将获得相同的分数,因此通常希望使用对所有文档都具有惟一值的字段。一个好的默认选择可能是使用_seq_no字段,它唯一的缺点是,如果文档被更新,那么分数会发生变化,因为update操作也会更新_seq_no字段的值。
可以在不设置字段的情况下设置种子,但这已被弃用,因为这需要在_id字段上加载fielddata,这会消耗大量内存。
GET /_search{
"query": {
"function_score": {
"random_score": {
"seed": 10,
"field": "_seq_no"
}
}
}}
Field_value_factor(接受欢迎度提升权重)
field_value_factor功能允许您使用文档中的字段来影响分数。它与使
用该script_score函数类似,但它避免了脚本的开销。它类似于使用script_score函数,但是,它避免了脚本开销。如果用于多值字段,则仅在计算中使用该字段的第一个值。
参数配置详细说明:
例如,假设您有一个使用数字likes 字段索引的文档,并希望使用此字段影响文档的分数,这样做的示例如下所示:
GET /_search{
"query": {
"function_score": {
"field_value_factor": {
"field": "likes",
"factor": 1.2,
"modifier": "sqrt",
"missing": 1
}
}
}
这将转化为以下评分公式:
sqrt(1.2 * doc['likes'].value)
field_value_factor函数有许多选项:
field要从文档中提取的字段。
factor将字段值乘以的可选因子,默认为1。
modifier修改适用于该字段的值,可以是一个:none,log, log1p,log2p,ln,ln1p,ln2p,square,sqrt,或reciprocal。默认为none。
修改(modifier)含义
none不要对字段值应用任何乘数
log取字段值的常用对数。因为如果在0和1之间的值上使用此函数将返回负值并导致错误,建议改为使用log1p。
log1p将1添加到字段值并采用常用对数
log2p将2添加到字段值并采用常用对数
ln取字段值的自然对数。因为如果在0和1之间的值上使用此函数将返回负值并导致错误,建议改为使用ln1p。
ln1p将1添加到字段值并采用自然对数
ln2p将2添加到字段值并采用自然对数
square平方字段值(乘以它自己)
sqrt取字段值的平方根
reciprocal报答字段值,同1/x那里x是该字段的值
missing
如果文档没有该字段,则使用的值。修饰符和因子仍然应用于它,就像从文档中读取一样。
field_value_score函数产生的分数必须是非负数,否则将引发错误。的log和ln如果0和1一定要限制的字段的值与一系列过滤器,以避免这种情况,或者使用之间对值的修饰符会产生负值log1p和ln1p。
请记住,将log()设为0或负数的平方根是非法操作,并抛出异常。请务必使用范围过滤器限制字段的值以避免这种情况,或使用log1p和ln1p。
案例详解:
设想有个网站供用户发布博客并且可以让他们为自己喜欢的博客点赞,我们希望将更受欢迎的博客放在搜索结果列表中相对较上的位置,同时全文搜索的评分仍然作为相关度的主要排序依据,可以简单的通过存储每个博客的点赞数来实现它:
PUT /blogposts/post/1{
"title": "About popularity",
"content": "In this post we will talk about...",
"votes": 6
}
在搜索时,可以将function_score 查询与 field_value_factor 结合使用,即将点赞数与全文相关度评分结合:
GET /blogposts/post/_search{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes"
}
}
}}
然而这并不会带来出人意料的好结果,全文评分_score 通常处于 0 到 10 之间,如下图“受欢迎度的线性关系基于 _score 的原始值 2.0” 中,有 10 个赞的博客会掩盖掉全文评分,而 0 个赞的博客的评分会被置为 0 。
受欢迎度的线性关系基于 _score 的原始值 2.0
一种融入受欢迎度更好方式是用modifier 平滑 votes 的值。换句话说,我们希望最开始的一些赞更重要,但是其重要性会随着数字的增加而降低。0 个赞与 1 个赞的区别应该比 10 个赞与 11 个赞的区别大很多。
对于上述情况,典型的modifier 应用是使用 log1p 参数值,公式如下:
new_score = old_score * log(1 + number_of_votes)
log 对数函数使 votes 赞字段的评分曲线更平滑,如图 “受欢迎度的对数关系基于 _score 的原始值 2.0” :
受欢迎度的对数关系基于 _score 的原始值 2.0
带 modifier 参数的请求如下:
GET /blogposts/post/_search{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p"
}
}
}}
修饰语modifier 的值可以为:none (默认状态)、 log 、 log1p 、 log2p 、 ln 、 ln1p 、 ln2p 、 square 、 sqrt 以及 reciprocal 。想要了解更多信息请参照: field_value_factor 文档.
Factor
可以通过将votes 字段与 factor 的积来调节受欢迎程度效果的高低:
GET /blogposts/post/_search{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p",
"factor": 2
}
}
}}
双倍效果
添加了factor 会使公式变成这样:
new_score = old_score * log(1 + factor * number_of_votes)
factor 值大于1 会提升效果, factor 值小于 1 会降低效果,如图 “受欢迎度的对数关系基于多个不同因子” 。
boost_mode编辑
或许将全文评分与field_value_factor 函数值乘积的效果仍然可能太大,我们可以通过参数boost_mode来控制函数与查询评分 _score 合并后的结果,参数接受的值为:
multiply
评分_score 与函数值的积(默认)
sum
评分_score 与函数值的和
min
评分_score 与函数值间的较小值
max
评分_score 与函数值间的较大值
replace
函数值替代评分_score
与使用乘积的方式相比,使用评分_score 与函数值求和的方式可以弱化最终效果,特别是使用一个较小 factor 因子时:
GET /blogposts/post/_search{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p",
"factor": 0.1
},
"boost_mode": "sum"
}
}}
将函数计算结果值累加到评分 _score 。
之前请求的公式现在变成下面这样(参见图32 “使用 sum 结合受欢迎程度” ):
new_score = old_score + log(1 + 0.1 * number_of_votes)
max_boost编辑
最后,可以使用max_boost 参数限制一个函数的最大效果:
GET /blogposts/post/_search{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p",
"factor": 0.1
},
"boost_mode": "sum",
"max_boost": 1.5
}
}}
无论field_value_factor 函数的结果如何,最终结果都不会大于 1.5 。max_boost 只对函数的结果进行限制,不会对最终评分 _score 产生直接影响。
Decay functions(衰变函数)
其他事项:
1、数据平滑处理——log1p()和exmp1()
在数据预处理时首先可以对偏度比较大的数据用log1p函数进行转化,使其更加服从高斯分布,此步处理可能会使我们后续的分类结果得到一个更好的结果;
平滑处理很容易被忽略掉,导致模型的结果总是达不到一定的标准,同样使用逼格更高的log1p能避免复值得问题——复值指一个自变量对应多个因变量;
log1p的使用就像是将一个数据压缩到了一个区间,与数据的标准化类似。下面再说说它的逆运算expm1函数。
由于前面使用过log1p将数据进行了压缩,所以最后需要记得将预测出的平滑数据进行一个还原,而还原过程就是log1p的逆运算expm1。
log1p和expm1的功能:
log1p函数有它存在的意义,即保证了x数据的有效性,当x很小时(如两个数值相减后得到),由于太小超过数值有效性,用计算得到结果为0,换作log1p则计算得到一个很小却不为0的结果,这便是它的意义(好像是用泰勒公式来展开运算的,不确定)。
同样的道理对于expm1,当x特别小,
就会急剧下降出现如上问题,甚至出现错误值。
在最开始看到这样的处理方式的时候,不是很理解包括为什么是逆运算(一下子没有想到),后来慢慢摸索就优点清晰了,比如为什么两这是逆运算(简单处理):
是e为底的对数,
是e为底的指数,根据对数的规则,再进行变换推导可以得到: