Elasticsearch function_score使用

在使用Elasticsearch进行全文搜索的时候,默认是使用BM25计算的_score字段进行降序排序的。当我们需要用其他字段进行降序或者升序排序的时候,可以使用sort字段,传入我们想要的排序字段和方式。
当简单的使用几个字段升降序排列组合无法满足我们的需求的时候,我们就需要自定义排序的特性,Elasticsearch提供了function_score的DSL来自定义打分,这样就可以根据自定义的_score来进行排序。

1.第一个例子

首先新建一个索引test_ratings。

{
  "test_ratings_v1" : {
    "aliases" : {
      "test_ratings" : { }
    },
    "mappings" : {
      "doc" : {
        "properties" : {
          "comment" : {
            "type" : "text",
            "analyzer" : "ik_smart"
          },
          "create" : {
            "type" : "date"
          },
          "id" : {
            "type" : "integer"
          },
          "productId" : {
            "type" : "integer"
          },
          "rating" : {
            "type" : "integer"
          },
          "test" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "title" : {
            "type" : "text",
            "analyzer" : "ik_max_word"
          },
          "userId" : {
            "type" : "integer"
          }
        }
      }
    },
    "settings" : {
      "index" : {
        "refresh_interval" : "1s",
        "number_of_shards" : "3",
        "provided_name" : "test_ratings_v1",
        "max_result_window" : "10000000",
        "creation_date" : "1569227588095",
        "number_of_replicas" : "0",
        "uuid" : "cx7XVhITQnaBlqKtauZZow",
        "version" : {
          "created" : "6080199"
        }
      }
    }
  }
}

然后导入一些数据,一个基本的请求如下所示:

  • function_score包裹的query内的DSL,就像我们平常使用的查询条件一样使用。
  • field_value_factor表示query查询计算的_score再乘以rating字段的值
  • missing 表示如果rating的值确实,用1作为默认值
    用这样的请求,我们可以找出那些评论标题中带有喜欢,而且评分也很高的评论,最终 _score = _score * rating(default=1)
GET /test_ratings/_search
{
  "query": {
    "function_score": {
    "query": {"match": {
      "title": "喜欢"
    }},
    "field_value_factor": {
      "field": "rating",
      "missing": 1
    }
  }}
}

2.field_value_factor

上面的例子中已经简单的介绍了field_value_factor的作用了,现在再来看看field_value_factor内的一些参数

2.1 field

相乘的字段,该字段必须是数字类型。

2.2 factor

相乘的系数,可以自己调节相乘的系数

2.3 missing

定义字段缺省值

2.4 modifier

修正相乘值
有时候将字段值,直接和_score相乘效果不好,需要修正字段值,可选的值有:

选项 含义
none 默认值
log log(x)
log1p log(1+x)
log2p log(2+x)
ln ln(x)
ln1p ln(1+x)
ln2p ln(2+x)
square x^x
sqrt x的平方根
reciprocal x的倒数

当factor和modifier同时使用的时候,factor优先于modifier
以log为例,最终分数会变为 _score = _score * log(factor * x)
当我们觉得评分0分和1分的区别大于4分和5分的区别的时候,就可以使用log来修正_score。

GET /test_ratings/_search
{
  "query": {
    "function_score": {
    "query": {"match": {
      "title": "喜欢"
    }},
    "field_value_factor": {
      "field": "rating",
      "missing": 0,
      "modifier": "log1p"
    }
  }}
}

当默认值为0的时候,使用log会报错,因此可以使用log1p来修正。

3.script_score

当我们使用field_value_factor和functions内简单的weight无法满足业务的时候,可以使用Elasticsearch提供的Painless脚本来自定义排序函数。
painless语法参考:https://www.elastic.co/guide/en/elasticsearch/painless/6.8/painless-examples.html
一个简单的用法如下:

GET /test_ratings/_search
{
  "query": {"function_score": {
    "query": {"match": {
      "title": "喜欢"
    }},
    "boost_mode": "replace",
    "script_score": {
      "script":{
        "params": {
          "a":1,
          "b":2
        },
        "source": "_score + params.b * doc['rating'].value"
      }
    }
  }}
}

3.1 params

自定义参数

3.2 source

指定脚本,可以使用doc['f']的方式访问原始字段值,也可以用_score访问计算后的score值

4.random_score

使用random_score可以让不同的人请求得到不同的排序结果,而同一个人请求可以得到相同的结果,使用如下:

GET /test_ratings/_search
{
  "query": {"function_score": {
    "query": {"match": {
      "title": "喜欢"
    }},
    
    "random_score": {
        "seed": 1,
        "field": "userId"
    },
    "boost_mode": "replace"
  }}
}

4.1 seed

指定随机的种子,相同的种子返回相同排序,每个种子会为每个文档生成一个0-1的随机数,改随机数就是random_score的返回值,可以和其他filter或者外部打分一起使用。

4.2 field

对于相同shard的相同field的值,产生的随机数一样,因此在使用的时候,尽量选择值不一样的field。

5.functions

上面的例子中,每一个doc都会乘以相同的系数,有时候我们需要对不同的doc采用不同的权重。这时,使用functions是一种不错的选择。基本的用法如下:

GET /test_ratings/_search
{
    "query": {
        "function_score": {
            "query": {
                "match": {
                    "title": "喜欢"
                }
            },
            "functions": [{
                    "filter": {
                        "term": {
                            "userId": 209983
                        }
                    },
                    "weight": 5
                },
                {
                    "filter": {
                        "term": {
                            "rating": 5
                        }
                    },
                    "weight": 2
                }
            ],
            "boost": 2,
            "score_mode": "max",
            "boost_mode": "multiply",
            "min_score": 18,
            "max_boost": 4
        }
    }
}

5.1 filter

表示每一个不同分值的过滤器。

5.2 weight

表示对应的权重

上诉例子表示:对搜索召回的结果,userid=209983的doc,weight为5,rating=5的doc的weight为2

5.3 functions内其他内部参数

functions内支持script_score和field_value_factor等参数

6. function_score其他外部参数

还是以4中的例子来分析其他的外层参数

6.1 score_mode

表示functions内的每一项的计算方式,可选的计算方式有:

选项 含义
multiply functions内每一项weight相乘
sum functions内每一项weight相加
avg functions内的weight求平均值
first functions内的第一个weight值
max functions内的最大weight
min functions内的最小min

6.2 max_boost

对functions计算的score的限制,表示functions返回的最值。适用field_value_factor等其他场景

6.3 boost

对functions计算出来的结果,再做相乘的系数。适用field_value_factor等其他场景

6.4 boost_mode

对boost和functions结果的乘积 和原始_score之间的计算方式。适用field_value_factor等其他场景,可选值为:

选项 含义
multiply 结果和原始_score相乘
replace 用计算结果替换原始_score
sum 结果和原始_score相加
avg 结果和原始_score求平均值
max 求结果和原始_score最大值
min 求结果和原始_score最小值

6.5 min_score

限制最后召回的最低得分

6.6 weight

在外部也可以用weight,对每个文档乘一个系数。

7.衰减函数

有些时候我们需要某个字段等于一个特定值时分数最高,然后往两边或者一边递减,比如地理位置或者价格区间。这个时候可以使用衰减函数。基本使用如下:

{
    "query": {
        "function_score": {
            "query": {
                "match": {
                    "title": "喜欢"
                }
            },
            "gauss": {
                "rating": {
                    "origin": "2",
                    "offset": "1",
                    "scale": "0.5",
                    "decay": "0.5"
                }
            },
            "boost_mode": "replace"
        }
    }
}

其中rating是field名,gauss是衰减函数名

7.1 fiend内部参数

选项 含义
origin 最佳值的位置,这个位置的score为1
offset 最佳值两边的范围,在(origin-offset, origin+offset)范围内都是最大score:1
scale 衰减距离,从offset再往外scale衰减到指定decay值
decay 指定的衰减值,到达scal的距离,衰减到decay

7.2 衰减函数

目前支持三种衰减函数:gauss、exp、linear
衰减函数和参数见下图,参考https://www.elastic.co/guide/en/elasticsearch/reference/6.8/query-dsl-function-score-query.html#function-random

衰减函数示意图

7.3 multi-values fields

在计算field的值到origin的距离时,如果field是一个多值的字段,则可以通multi_value_mode字段,来设置参与距离计算的值,可选值为:

选项 含义
min field内到origin的最小距离
max field内到origin的最大距离
avg field内数组到origin的平均距离
sum field内数组到origin的距离之和

8.总结

利用Elasticsearch的function_score功能,可以灵活的对搜索结果进行排序。但是在使用过程中尽量少使用script_score,script_score性能会有一些影响。

更多精彩内容,请关注公众号


公众号二维码.jpg

更多精彩内容,请关注公众号


公众号二维码.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容