function_score (函数评分) 查询

function_score 允许你修改查询检索到的文档的分数。 例如,如果一个评分函数的计算开销很大,并且它足以计算经过筛选的一组文档的分数,那么这就很有用了。

要使用 function_score,用户必须定义一个查询和一个或多个函数,这些函数为查询返回的每个文档计算一个新分数。

function_score 只能用于一个函数,如下所示:

GET /_search
{
    "query": {
        "function_score": {
            "query": { "match_all": {} },
            "boost": "5",
            "random_score": {}, 
            "boost_mode":"multiply"
        }
    }
}

受支持的函数列表请参考 函数评分

此外,几个功能可以组合。在这种情况下,我们可以选择只在文档匹配指定的 filter 查询时应用该函数。

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)。

受支持的函数列表请参考 函数评分

每个函数的 filter 查询产生的分数并不重要。

如果函数中没有给出任何 filter,这相当于指定 "match_all": {}

首先,每个文档由定义的函数评分。 参数 score_mode 指定如何组合计算出的分数:

multiply

分数相乘(默认)

sum

分数相加,求和

avg

分数的平均值

first

应用第一个具有匹配 filter 的函数

max

取分数最大值

min

取分数最小值

因为分数可以在不同的范围内(例如,衰减函数(decay function)在0到1之间,而 field_value_factor 是任意的),而且有时函数对分数的不同影响是可取的,所以可以使用用户定义的 weight(权重) 来调整每个函数的分数。 weight可以在 functions数组(上面的例子)中按每个函数定义,并与相应函数计算的分数相乘。 如果给定 weight 时没有任何其他函数声明,weight 就会作为一个函数返回weight

如果 score_mode 被设置为 avg,则单个分数将被 weighted(加权) 平均。 例如,如果两个函数返回 1 和 2 分,它们各自的权重分别为 3 和 4,那么它们的得分将合并为 (1*3+2*4)/(3+4)不是 (1*3+2*4)/2

通过设置 max_boost 参数,可以限制新的分数不超过某个限制。 max_boost 的默认值是 FLT_MAX。

新计算的分数与查询的分数相结合。 参数 boost_mode 定义了如何操作:

multiply

查询分数和函数分数相乘(默认)

replace

只使用函数分数,而忽略查询分数

sum

查询分数和函数分数相加

avg

平均值

max

查询分数和函数分数的最大值

min

查询分数和函数分数的最小值

默认情况下,修改分数不会改变匹配的文档。 要排除不满足某个分数阈值的文档,可以将参数 min_score 设置为所需的分数阈值。

要让 min_score 起作用,需要对查询返回的所有文档进行评分,然后逐一过滤掉。

function_score 查询提供了以下几种评分函数。

脚本评分 (Script score)

script_score 函数允许包裹另一个查询,并使用脚本表达式使用从文档中的其他 数值(numeric) 字段值派生的计算定制该查询的评分。 这里是一个简单的例子:

GET /_search
{
    "query": {
        "function_score": {
            "query": {
                "match": { "message": "elasticsearch" }
            },
            "script_score" : {
                "script" : {
                  "source": "Math.log(2 + doc['likes'].value)"
                }
            }
        }
    }
}

在 Elasticsearch 中,所有文档的评分都是一个 32 位的浮点数。

如果 script_score 函数生成了一个精度更高的分数,那么它将被转换为最接近的 32 位浮点数。

同样,分数必须是非负的。 否则,Elasticsearch 返回一个错误。

在不同的脚本字段值和表达式之上,可以使用 _score 脚本参数根据包裹的查询来检索分数。

脚本编译被缓存以更快地执行。 如果脚本有需要考虑的参数,最好重用同一个脚本,并为其提供 params(参数):

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)

weight 分数允许你将分数乘以指定的 weight 值。 这有时是需要的,因为在特定查询上设置的 boost(提升)值 被归一化了,而对于这个评分函数却没有。 number 值的类型是 float。

"weight" : number

随机 (random)

random_score 生成从 0 到 1 (不包括1) 均匀分布的分数。 默认情况下,它使用内部 Lucene 文档 id 作为随机数种子(seed),这非常有效,但不幸的是不可再现的,因为文档可能会被合并而重新编号。

如果你希望分数是可再现的,可以提供一个 seed(种子)field(字段)。 然后,将基于该种子、所考虑文档的 field(字段) 的最小值以及基于索引名称和分片id计算的 盐(salt) 来计算最终得分,以便具有相同值但存储在不同索引中的文档获得不同的得分。 请注意,在同一个分片中并且具有相同 field 值的文档将获得相同的分数,因此通常希望对所有文档使用具有唯一值的字段。 一个好的默认选择可能是使用 _seq_no 字段,其唯一的缺点是如果文档被更新,分数将会改变,因为更新操作也会更新 _seq_no 字段的值。

可以在不设置字段的情况下设置种子,但这已被废弃,因为这需要在 _id 字段上加载 fielddata(字段的数据),这会消耗大量内存。

GET /_search
{
    "query": {
        "function_score": {
            "random_score": {
                "seed": 10,
                "field": "_seq_no"
            }
        }
    }
}

字段值因子 (field value factor)

field_value_factor 函数允许你使用文档中的字段来影响分数。 它类似于使用 script_score 函数,但是它避免了脚本的开销。 如果用于 多值(multi-valued) 字段,则计算中仅使用字段的第一个值。

例如,假设你有一个用数字 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

应用于字段值的修饰符,可以是下列值之一:noneloglog1plog2plnln1pln2psquaresqrtreciprocal。 默认值为 none

修饰符 意义

none

不要对字段值应用任何乘数

log

取字段值的常用对数(common logarithm) 。 由于该函数用于 0 到 1 之间的值时会返回负值,这会导致错误,所以建议使用 log1p 以代替之。

log1p

将字段值加 1,取常用对数

log2p

将字段值加 2,取常用对数

ln

取字段值的自然对数(natural logarithm)。 由于该函数用于 0 到 1 之间的值时会返回负值,这会导致错误,因此建议使用 ln1p 以代替之。

ln1p

将字段值加 1,取自然对数

ln2p

将字段值加 2,取自然对数

square

字段值的平方(乘以它本身)

sqrt

取字段值的 平方根(square root)

reciprocal

字段值的倒数(reciprocate) ,与 1/x 相同,其中 x 是字段的值

missing
文档没有该字段时使用的值。 修饰符和因子仍然应用于它,就好像它是从文档中读取的一样。

field_value_score 函数生成的分数值必须是非负的,否则将会引起错误。 如果 logln 修饰符用于 0 到 1 之间的值,将产生负值。 确保使用范围过滤器限制字段的值以避免这种情况,或者使用 log1pln1p

请记住,取 0 的 log() 或 负数的平方根是非法操作,将会引发异常。 确保使用范围过滤器限制字段的值以避免这种情况,或者使用 log1pln1p

衰减函数 (decay functions)

衰减函数(decay functions) 使用根据文档的数值字段值与用户给定原点的距离衰减的函数对文档进行评分。 这类似于 范围查询(range query),但是使用平滑的边缘而不是方框。

要对具有数值字段的查询使用距离评分,用户必须为每个字段定义 origin (原点)scale(范围)。 需要使用 origin (原点) 来定义计算距离的“中心点”,使用 scale(范围) 来定义衰减率。 衰减函数可以这样定义:

"DECAY_FUNCTION": { 
    "FIELD_NAME": { 
          "origin": "11, 12",
          "scale": "2km",
          "offset": "0km",
          "decay": 0.33
    }
}

DECAY_FUNCTION 应为 linearexpgauss

指定的字段必须是 numeric、date 或 geo-point 类型的。

在上面的例子中,字段是 geo_point 类型,origin 可以以 geo 格式提供。 在这种情况下,scaleoffset 必须使用上例中的单位。 如果你的字段是 date 类型,可以将 scaleoffset 设置为天(d)、周(w)等。 示例:

GET /_search
{
    "query": {
        "function_score": {
            "gauss": {
                "date": {
                      "origin": "2013-09-17", 
                      "scale": "10d",
                      "offset": "5d", 
                      "decay" : 0.5 
                }
            }
        }
    }
}

origin 的日期格式取决于 mapping 中定义的format(格式)。 如果没有定义origin,则使用当前时间。

参数 offsetdecay 是可选的。

origin

用于计算距离的原点。 对于 numeric 字段,必须以数字形式给出;对于 date 字段,必须以日期形式给出;对于 geo 段,必须以地理点形式给出。 对于 geo 和 numeric 字段是必需的。 对于 date 字段,默认值为 now(现在)。 原点支持日期计算(例如 now-1h)。

scale

所有类型都需要。 定义从 origin + offset 的距离,在该距离处计算的分数将等于参数 decay
对于 geo 字段:可以定义为 数字 + 单位(如:"1km","12m",...),默认单位是 m (米)。
对于 date 字段:可以定义为 数字 + 单位(如:"1h","10d"...​),默认单位是 ms (毫秒)。
对于 numeric 字段: 可以是任意数值。

offset

如果定义了 offset,衰减函数将只计算距离大于定义的 offset 的文档衰减。 默认值为 0

decay

参数 decay 定义了如何在 scale 给定的距离上对文档进行评分。 如果没有定义 decay,则在 scale 距离上的文档将被评分为 0.5

在第一个例子中,文档可能表示酒店并包含 geo(地理位置) 字段。 你希望根据酒店离给定位置的距离来计算衰减函数。 你可能不会立即看到为 guass(高斯)函数 选择哪一个 scale,但是你可以这样说:“在距离期望位置 2km 处,分数应减少到三分之一。” 然后将自动调整参数"scale",以确保评分函数计算出距离期望位置 2km 的酒店的分数为0.33。

在第二个例子中,字段值在 2013-09-12 和 2013-09-22 之间的文档的权重(weight)为 1.0,距离该日期超过 15 天的文档的权重为 0.5。

支持的衰减函数

DECAY_FUNCTION 决定衰减的形状:

gauss

正态衰减,计算如下:

Gaussian

其中,要计算sigma以确保分数在距离 origin+-offsetscale 距离处取值为 decay

sigma calc

有关 gauss 函数生成的曲线的图形的演示,请参考 正态衰减,关键字 gauss

exp

指数衰减,计算如下:

Exponential

其中,参数lambda再次被计算以确保分数在距离 origin+-offsetscale 距离处取值为 decay

lambda calc

有关 exp 函数生成的曲线的图形的演示,请参考 指数衰减,关键字 exp

linear

线性衰减,计算如下:

Linear.

其中,参数 s 再次被计算以确保分数在距离 origin+-offsetscale 距离处取值为 decay

s calc

与正态和指数衰减相反,如果字段值超过用户给定 scale 值的两倍,此函数实际上将分数设置为0。

对于单个函数,三个衰减函数以及它们的参数可以像下面这样可视化(在这个例子中称为 “age”(年龄) 的字段):

decay 2d

多值字段 (multi-values fields)

如果用于计算衰减的字段包含多个值,默认情况下会选择最接近原点(origin)的值来确定距离。 这可以通过设置 multi_value_mode 来改变。

min

取最小距离作为距离值

max

取最大距离作为距离值

avg

取平均距离作为距离值

sum

取所有距离的和作为距离值

示例:

    "DECAY_FUNCTION": {
        "FIELD_NAME": {
              "origin": ...,
              "scale": ...
        },
        "multi_value_mode": "avg"
    }

详细示例

假如你正在某个城市寻找一家酒店,而你的预算有限。 此外,你希望酒店靠近市中心,因此酒店离理想位置越远,入住的可能性就越小。

你希望匹配你的标准(例如,“hotel, Nancy, non-smoker”)的查询结果根据到市中心的距离以及价格进行评分。

直觉上,你可能希望将市中心定义为起点,并且可能愿意从酒店步行 2km 到市中心。
在这种情况下,位置字段的 origin 是城镇中心,scale 为 2km。

如果你的预算很低,你可能更喜欢便宜的东西而不是贵的东西。 对于 price(价格) 字段, origin 为 0 欧元,scale 取决于你愿意支付的金额,例如 20 欧元。

在本例中,对于酒店的价格,字段可能被称为 "price",而对于该酒店的坐标,字段可能被称为 "location"。

在这种情况下, price 的函数是:

"gauss": { 
    "price": {
          "origin": "0",
          "scale": "20"
    }
}

这个衰减函数也可以是 linearexp

对于 location

"gauss": { 
    "location": {
          "origin": "11, 12",
          "scale": "2km"
    }
}

这个衰减函数也可以是 linearexp

假设你想将这两个函数在原始分数上相乘,则请求如下所示:

GET /_search
{
    "query": {
        "function_score": {
          "functions": [
            {
              "gauss": {
                "price": {
                  "origin": "0",
                  "scale": "20"
                }
              }
            },
            {
              "gauss": {
                "location": {
                  "origin": "11, 12",
                  "scale": "2km"
                }
              }
            }
          ],
          "query": {
            "match": {
              "properties": "balcony"
            }
          },
          "score_mode": "multiply"
        }
    }
}

接下来,我们展示了三种可能的衰减函数的计算分数。

正态衰减,关键字 gauss

在上面的示例中,当选择 gauss 作为衰减函数时,乘数的等高线和曲面图如下所示:

cd0e18a6 e898 11e2 9b3c f0145078bd6f
ec43c928 e898 11e2 8e0d f3c4519dbd89

假设原始搜索结果匹配三家酒店:

  • "Backback Nap"
  • "Drink n Drive"
  • "BnB Bellevue"

"Drink n Drive" 离你定义的位置相当远(将近 2km),而且不太便宜(约 13 欧元),所以它的因子很低,为 0.56。 "BnB Bellevue"和"Backback Nap"都非常接近定义的位置,但"BnB Bellevue"更便宜,因此它的因子为 0.86,而"Backpack Nap"的因子值为 0.66。

指数衰减,关键字 exp

在上面的示例中,当选择 exp 作为衰减函数时,乘数的等高线和曲面图如下所示:

082975c0 e899 11e2 86f7 174c3a729d64
0b606884 e899 11e2 907b aefc77eefef6

线性衰减,关键字 linear

在上面的示例中,当选择 linear 作为衰减函数时,乘数的等高线和曲面图如下所示:

1775b0ca e899 11e2 9f4a 776b406305c6
19d8b1aa e899 11e2 91bc 6b0553e8d722

衰减函数支持的字段

只支持 numeric、date 和 geo-point 字段。

如果一个字段缺失,怎么办?

如果文档中缺少 numeric 字段,该函数将返回 1。