ES自定义评分函数评分查询ES会为查询的每篇文档计算一个相关度评分score,默认按照评分从高到低的顺序返回搜索结果。在很多场景下,我们不仅需要搜索匹配的结果,还需要能够对搜索结果进行一定的重新排序。例如:搜索某个关键字的文档,并考虑文档的时效性进行综合排名。搜索旅游景点附近的酒店,并根据距离和价格等因素对酒店进行综合排序。搜索标题包含elasticsearch的文章,并根据浏览量和点赞量进行综合排序。功能分数查询允许我们实现最终分数的自定义评分。score自定义评分过程为了写作方便,本文将ES对匹配query的文档进行评分得到的score记录为query_score,最终搜索结果的score记录为result_score。显然,在正常情况下(即不使用自定义评分时),result_score就是query_score。那么当我们使用自定义评分时会发生什么?最终结果的得分即result_score的计算过程如下:和之前一样执行query,得到原始的query_score。执行setcustomscoringfunction,为每个文档得到一个新的分数,在本文中记为func_score。最终结果的分数result_score等于query_score和func_score以某种方式计算(默认乘法)。比如搜索标题包含elasticsearch的文档。如果不使用自定义评分,搜索格式如下:GET/_search{"query":{"match":{"title":"elasticsearch"}}}假设我们最终得到三个搜索结果,分数分别为0.3、0.2、0.1。使用自定义评分,function_score,语法如下:GET/_search{"query":{"function_score":{"query":{"match":{"title":"elasticsearch"}}"boost_mode":"multiply"}}}最终搜索结果分数的计算过程是:执行查询得到原始分数,即对应上面的假设,即query_score分别为0.3、0.2、0.1。执行一个自定义的评分函数,这一步会为每个文档得到一个新的分数,假设新的分数是func_score分别为1、3、5。最终结果的得分为result_score=query_score*func_score,三个假设搜索结果的最终得分分别为0.3*1=0.3、0.2*3=0.6、0.1*5=0.5,至此我们完成了新的打分process,搜索结果也会按照最终得分降序排列。最终得分result_score通过query_score和func_score计算,计算方式由参数boost_mode定义:multiply:multiply(默认),result_score=query_score*function_scorereplace:replace,result_score=function_scoresum:add,result_score=query_score+function:_scoreavg取两者的平均值,result_score=Avg(query_score,function_score)max:取两者的最大值,result_score=Max(query_score,function_score)min:取两者的最小值,result_score=Min(query_score,function_score)后阅读本文,您应该对自定义评分流程(查询原始评分、自定义函数评分、最终结果评分)有一个基本印象。但是我们还有一个关键点没有讲到,那就是自定义评分函数怎么设置?function_score打分函数function_score提供以下打分函数:weight:加权。random_score:随机分数。field_value_factor:使用字段的值参与分数的计算。decay_function:衰减函数gauss、linear、exp等。script_score:自定义脚本。weightweight加权,即给每个文档一个权重值。示例:{"query":{"function_score":{"query":{"match":{"message":"elasticsearch"}},"weight":5}}}示例中权重为5,即即,由于定义函数scorefunc_score=5,最终结果的score等于query_score*5。当然,这个例子对所有匹配项进行加权,并没有改变搜索结果的顺序。让我们看另一个例子:":{"title":"elasticsearch"}},"weight":5}]}}}我们可以通过filter限制权重的范围,另外我们可以在函数中同时使用多个评分函数。random_scorerandom_score随机打分,产生一个均匀分布在[0,1)之间的随机分数值。例子:GET/_search{"query":{"function_score":{"random_score":{}}}}虽然是随机值,但是有时候我们需要随机值保持一致。例如,所有用户随机生成搜索结果,但同一用户的随机结果是一致的。在这种情况下,您只需为同一用户指定相同的种子即可。示例:{"query":{"function_score":{"random_score":{"seed":10,"field":"_seq_no"}}}}默认情况下,当没有设置字段时,将使用Lucenedocidas随机源用于生成随机值,但是这样会消耗大量内存。官方建议将该字段设置为_seq_no。主要需要注意的是,即使指定了相同的seed,在某些情况下随机值也会发生变化,因为一旦更新了字段,_seq_no也随之更新,导致随机源发生变化。组合多个函数的示例:GET/_search{"query":{"function_score":{"query":{"match_all":{}},"boost":"5","functions":[{"filter":{“匹配”:{“测试”:“酒吧”}},“random_score”:{},“重量”:23},{“过滤器”:{“匹配”:{“测试”:“猫”}},"weight":42}],"max_boost":42,"score_mode":"max","boost_mode":"multiply","min_score":42}}}在上面的示例函数中,两个评分函数是set:一个是random_score随机打分,权重为23,另一个只有权重42通过这个函数是将权重42score_mode的值设置为max,意思是取两个评分函数的最大值作为func_score,对应上面的假设,就是2.3和42的最大值,即func_score=42boost_mode设置为multiply,即把原来的query_score和func_score相乘得到最终的score。参数score_mode指定多个评分函数如何组合计算一个新的分数:multiply:乘以分数(默认)sum:addavg:加权平均first:使用第一个过滤函数的分数max:取最大值min:取最小值是为了避免新分数的值过高,上限可以通过max_boost参数设置。需要注意的是,无论我们如何自定义评分,都不会改变原始查询的匹配行为。当我们自定义评分时,我们会在原始查询完成后重新计算每个匹配文档的分数。<\b>为了排除一些分数过低的结果,我们可以通过min_score参数设置最低分数阈值。field_value_factorfield_value_factor使用字段的值参与分数的计算。例如,使用likes字段进行综合搜索:{"query":{"function_score":{"query":{"match":{"message":"elasticsearch"}},"field_value_factor":{"field":"likes","factor":1.2,"missing":1,"modifier":"log1p"}}}}说明:field:参与计算的字段。factor:倍增因子,默认为1,会与field的字段值相乘。missing:如果字段字段不存在,则使用missing指定的默认值。modifier:计算函数,为了避免分数差异过大,用于平滑分数,可以是以下之一:none:不处理,默认log:log(factor*field_value)log1p:log(1+factor*field_value)log2p:log(2+factor*field_value)ln:ln(factor*field_value)ln1p:ln(1+factor*field_value)ln2p:ln(2+factor*field_value)square:square,(factor*field_value)^2sqrt:squareroot,sqrt(factor*field_value)reciprocal:求倒数,1/(factor*field_value)假设匹配文档的点赞数为1000,则例子中打分函数生成的分数为log(1+1.2*1000),最终得分是原始查询得分与打分函数得分之差。decay_functiondecay_function衰减函数,例如:以某个值为中心点,从一定距离开始逐渐衰减(缩小分数),以某个日期为中心点,从一定地理距离开始逐渐衰减(缩小分数)以位置点为中心点,超出距离逐渐衰减(缩小分数)。例子:"DECAY_FUNCTION":{"FIELD_NAME":{"origin":"30,120","scale":"2km","offset":"0km","decay":0.33}}上面的例子意味着在距离中心点2公里的半径范围外,分数减少到三分之一(乘以衰减值0.33)。DECAY_FUNCTION可以是以下任意一个函数:linear:线性函数exp:指数函数gauss:高斯函数origin:中心点,只能是一个值,日期,geo-pointscale:定义到中心点的距离offset:offset,default0decay:衰减指数,默认0.5例子:GET/_search{"query":{"function_score":{"gauss":{"@timestamp":{"origin":"2013-09-17","scale":"10d","offset":"5d","decay":0.5}}}}}中心点为2013-09-17日期,scale为10d,表示日期范围为从2013-09-12到2013-09score为-22的文档的权重为1,dateoutsidescale+offset=15d的文档的权重为0.5。如果参与计算的字段有多个值,则默认选择距离中心点最近的值,即距离中心点最短的值,可以通过multi_value_mode设置:min:最近的距离max:最远的distanceavg:平均距离sum:所有距离累计例子:GET/_search{"query":{"function_score":{"query":{"match":{"properties":"bigbalcony"}},"functions“:[{“高斯”:{“价格”:{“原点”:“0”,“比例”:“2000”}}},{“高斯”:{“位置”:{“原点”:“30,120","scale":"2km"}}],"score_mode":"multiply"}}}假设这是一个大阳台的房子搜索。上例中price字段的中心点设置为0,范围在2000以内,location字段的中心点设置为“30,120”,半径2km内,得分为超出这个范围的匹配结果会进行高斯衰减,即分数会降低。script_scorescript_score自定义脚本打分,如果以上打分功能还不够你也可以直接写脚本打分。示例:GET/_search{“query”:{“function_score”:{“query”:{“match”:{“message”:“elasticsearch”}},“script_score”:{“script”:{“source”:"Math.log(2+doc['my-int'].value)"}}}}}在脚本中,字段以doc['field']和doc['field']的形式被引用.value是使用的字段值。您还可以将额外参数与脚本内容分开: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['my-int'].value)"}}}}}结语通过了解Elasticsearch的自定义评分,相信您可以更好地完成适合您业务的综合搜索。
