函数评分查询编辑

function_score 允许您修改查询检索到的文档的评分。例如,如果评分函数计算量很大,并且仅对过滤后的文档集进行评分就足够了,这将非常有用。

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

function_score 可以像这样只使用一个函数

resp = client.search(
    body={
        "query": {
            "function_score": {
                "query": {"match_all": {}},
                "boost": "5",
                "random_score": {},
                "boost_mode": "multiply",
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      function_score: {
        query: {
          match_all: {}
        },
        boost: '5',
        random_score: {},
        boost_mode: 'multiply'
      }
    }
  }
)
puts response
res, err := es.Search(
	es.Search.WithBody(strings.NewReader(`{
	  "query": {
	    "function_score": {
	      "query": {
	        "match_all": {}
	      },
	      "boost": "5",
	      "random_score": {},
	      "boost_mode": "multiply"
	    }
	  }
	}`)),
	es.Search.WithPretty(),
)
fmt.Println(res, err)
GET /_search
{
  "query": {
    "function_score": {
      "query": { "match_all": {} },
      "boost": "5",
      "random_score": {}, 
      "boost_mode": "multiply"
    }
  }
}

有关支持函数的列表,请参见 函数评分

此外,可以组合多个函数。在这种情况下,可以选择仅在文档匹配给定过滤查询时才应用函数

resp = client.search(
    body={
        "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,
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    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
      }
    }
  }
)
puts response
res, err := es.Search(
	es.Search.WithBody(strings.NewReader(`{
	  "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
	    }
	  }
	}`)),
	es.Search.WithPretty(),
)
fmt.Println(res, err)
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
    }
  }
}

整个查询的提升。

有关支持函数的列表,请参见 函数评分

每个函数的过滤查询产生的评分无关紧要。

如果函数没有给出过滤器,则等效于指定 "match_all": {}

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

乘法

评分相乘(默认)

求和

评分相加

平均值

评分取平均值

第一个

应用第一个具有匹配过滤器的函数

最大值

使用最大评分

最小值

使用最小评分

由于评分可能处于不同的尺度(例如,衰减函数的评分在 0 到 1 之间,而 field_value_factor 的评分是任意的),并且有时希望函数对评分的影响不同,因此可以使用用户定义的 weight 调整每个函数的评分。可以在 functions 数组(上面的示例)中为每个函数定义 weight,并将其乘以相应函数计算的评分。如果在没有其他函数声明的情况下给出权重,则 weight 充当一个简单地返回 weight 的函数。

如果 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 定义了

乘法

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

替换

仅使用函数评分,忽略查询评分

求和

查询评分和函数评分相加

平均值

平均值

最大值

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

最小值

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

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

要使 min_score 生效,查询返回的 所有 文档都需要进行评分,然后逐个过滤掉。

function_score 查询提供了几种类型的评分函数。

脚本评分编辑

script_score 函数允许您包装另一个查询,并使用脚本表达式从文档中的其他数值字段值派生的计算结果来可选地自定义其评分。以下是一个简单的示例

resp = client.search(
    body={
        "query": {
            "function_score": {
                "query": {"match": {"message": "elasticsearch"}},
                "script_score": {
                    "script": {
                        "source": "Math.log(2 + doc['my-int'].value)"
                    }
                },
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      function_score: {
        query: {
          match: {
            message: 'elasticsearch'
          }
        },
        script_score: {
          script: {
            source: "Math.log(2 + doc['my-int'].value)"
          }
        }
      }
    }
  }
)
puts response
GET /_search
{
  "query": {
    "function_score": {
      "query": {
        "match": { "message": "elasticsearch" }
      },
      "script_score": {
        "script": {
          "source": "Math.log(2 + doc['my-int'].value)"
        }
      }
    }
  }
}

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

如果 script_score 函数产生的评分具有更高的精度,则将其转换为最接近的 32 位浮点数。

类似地,评分必须是非负的。否则,Elasticsearch 会返回错误。

除了不同的脚本字段值和表达式之外,还可以使用 _score 脚本参数来检索基于包装查询的评分。

脚本编译被缓存以加快执行速度。如果脚本需要考虑参数,最好重用相同的脚本,并为其提供参数

resp = client.search(
    body={
        "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)",
                    }
                },
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    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)"
          }
        }
      }
    }
  }
)
puts response
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)"
        }
      }
    }
  }
}

请注意,与 custom_score 查询不同,查询的评分会乘以脚本评分的结果。如果您希望禁止这种情况,请设置 "boost_mode": "replace"

权重编辑

weight 评分允许您将评分乘以提供的 weight。这在某些情况下可能需要,因为特定查询上设置的提升值会被归一化,而对于此评分函数则不会。数值是浮点类型。

"weight" : number

随机编辑

random_score 生成在 0 到 1 之间(不包括 1)均匀分布的评分。默认情况下,它使用内部 Lucene 文档 ID 作为随机性来源,这非常有效,但不幸的是不可重现,因为文档可能会被合并重新编号。

如果您希望评分可重现,则可以提供 seedfield。最终的评分将基于此种子、所考虑文档的 field 的最小值以及基于索引名称和分片 ID 计算的盐值来计算,这样具有相同值但存储在不同索引中的文档会获得不同的评分。请注意,位于同一分片中且具有相同 field 值的文档将获得相同的评分,因此通常希望使用一个对所有文档都具有唯一值的字段。一个好的默认选择可能是使用 _seq_no 字段,其唯一的缺点是,如果文档被更新,评分会发生变化,因为更新操作也会更新 _seq_no 字段的值。

可以设置种子而不设置字段,但这已被弃用,因为这需要在 _id 字段上加载 fielddata,这会消耗大量内存。

resp = client.search(
    body={
        "query": {
            "function_score": {
                "random_score": {"seed": 10, "field": "_seq_no"}
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      function_score: {
        random_score: {
          seed: 10,
          field: '_seq_no'
        }
      }
    }
  }
)
puts response
res, err := es.Search(
	es.Search.WithBody(strings.NewReader(`{
	  "query": {
	    "function_score": {
	      "random_score": {
	        "seed": 10,
	        "field": "_seq_no"
	      }
	    }
	  }
	}`)),
	es.Search.WithPretty(),
)
fmt.Println(res, err)
GET /_search
{
  "query": {
    "function_score": {
      "random_score": {
        "seed": 10,
        "field": "_seq_no"
      }
    }
  }
}

字段值因子编辑

field_value_factor 函数允许您使用文档中的字段来影响评分。它类似于使用 script_score 函数,但是它避免了脚本的开销。如果在多值字段上使用,则仅使用字段的第一个值进行计算。

例如,假设您有一个用数值 my-int 字段索引的文档,并且希望使用此字段来影响文档的评分,那么执行此操作的示例如下

resp = client.search(
    body={
        "query": {
            "function_score": {
                "field_value_factor": {
                    "field": "my-int",
                    "factor": 1.2,
                    "modifier": "sqrt",
                    "missing": 1,
                }
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      function_score: {
        field_value_factor: {
          field: 'my-int',
          factor: 1.2,
          modifier: 'sqrt',
          missing: 1
        }
      }
    }
  }
)
puts response
GET /_search
{
  "query": {
    "function_score": {
      "field_value_factor": {
        "field": "my-int",
        "factor": 1.2,
        "modifier": "sqrt",
        "missing": 1
      }
    }
  }
}

这将转换为以下评分公式

sqrt(1.2 * doc['my-int'].value)

field_value_factor 函数有许多选项

field

要从文档中提取的字段。

factor

可选的因子,用于将字段值相乘,默认为 1

modifier

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

修饰符 含义

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 函数产生的评分必须是非负的,否则会抛出错误。如果在 0 到 1 之间的数值上使用 logln 修饰符,则会产生负值。请务必使用范围过滤器来限制字段的值以避免这种情况,或者使用 log1pln1p

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

衰减函数编辑

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

要在具有数字字段的查询上使用距离评分,用户必须为每个字段定义一个 origin 和一个 scaleorigin 用于定义计算距离的“中心点”,而 scale 用于定义衰减率。衰减函数指定为

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

DECAY_FUNCTION 应为 linearexpgauss 之一。

指定的字段必须是数字、日期或地理点字段。

在上面的示例中,该字段是 geo_point,并且可以以地理格式提供原点。在这种情况下,必须以单位给出 scaleoffset。如果您的字段是日期字段,您可以将 scaleoffset 设置为天、周等。示例

resp = client.search(
    body={
        "query": {
            "function_score": {
                "gauss": {
                    "@timestamp": {
                        "origin": "2013-09-17",
                        "scale": "10d",
                        "offset": "5d",
                        "decay": 0.5,
                    }
                }
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      function_score: {
        gauss: {
          "@timestamp": {
            origin: '2013-09-17',
            scale: '10d',
            offset: '5d',
            decay: 0.5
          }
        }
      }
    }
  }
)
puts response
GET /_search
{
  "query": {
    "function_score": {
      "gauss": {
        "@timestamp": {
          "origin": "2013-09-17", 
          "scale": "10d",
          "offset": "5d",         
          "decay": 0.5            
        }
      }
    }
  }
}

原点的日期格式取决于映射中定义的 format。如果您未定义原点,则使用当前时间。

offsetdecay 参数是可选的。

origin

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

scale

所有类型都需要。定义从原点 + 偏移量到计算出的分数等于 decay 参数的距离。对于地理字段:可以定义为数字 + 单位(1km、12m 等)。默认单位为米。对于日期字段:可以定义为数字 + 单位(“1h”、“10d”等)。默认单位为毫秒。对于数字字段:任何数字。

offset

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

decay

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

在第一个示例中,您的文档可能代表酒店,并包含一个地理位置字段。您希望根据酒店距离给定位置的距离来计算衰减函数。您可能无法立即看到为高斯函数选择哪个比例,但您可以说:“在距离目标位置 2 公里的距离处,分数应降至三分之一。”然后会自动调整参数“scale”以确保分数函数计算出距离目标位置 2 公里的酒店的分数为 0.33。

在第二个示例中,字段值介于 2013-09-12 和 2013-09-22 之间的文档将获得 1.0 的权重,而距离该日期 15 天的文档将获得 0.5 的权重。

支持的衰减函数编辑

DECAY_FUNCTION 确定衰减的形状

gauss

正常衰减,计算为

Gaussian

其中 sigma 计算以确保分数在距离 scale 处取值 decayorigin+-offset

sigma calc

请参阅 正常衰减,关键字 gauss,以了解演示 gauss 函数生成的曲线的图表。

exp

指数衰减,计算为

Exponential

其中参数 lambda 再次计算以确保分数在距离 scale 处取值 decayorigin+-offset

lambda calc

请参阅 指数衰减,关键字 exp,以了解演示 exp 函数生成的曲线的图表。

linear

线性衰减,计算为

Linear.

其中参数 s 再次计算以确保分数在距离 scale 处取值 decayorigin+-offset

s calc

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

对于单个函数,三个衰减函数及其参数可以这样可视化(此示例中的字段称为“age”)

decay 2d

多值字段编辑

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

最小值

距离是最小距离

最大值

距离是最大距离

平均值

距离是平均距离

求和

距离是所有距离的总和

示例

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

详细示例编辑

假设您正在某个城镇搜索酒店。您的预算有限。此外,您希望酒店靠近市中心,因此酒店距离目标位置越远,您入住的可能性就越小。

您希望与您的标准(例如,“酒店、南希、非吸烟者”)匹配的查询结果根据到市中心的距离和价格进行评分。

直观地说,您希望将市中心定义为原点,也许您愿意从酒店步行 2 公里到市中心。
在这种情况下,您对位置字段的 原点 是市中心,而 比例 约为 2 公里。

如果您的预算很低,您可能更喜欢便宜的东西而不是昂贵的东西。对于价格字段, 原点 将为 0 欧元,而 比例 取决于您愿意支付多少,例如 20 欧元。

在此示例中,字段可能称为“price”(酒店的价格)和“location”(酒店的坐标)。

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

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

此衰减函数也可以是 linearexp

以及 location

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

此衰减函数也可以是 linearexp

假设您想将这两个函数乘以原始分数,则请求将如下所示

resp = client.search(
    body={
        "query": {
            "function_score": {
                "functions": [
                    {"gauss": {"price": {"origin": "0", "scale": "20"}}},
                    {
                        "gauss": {
                            "location": {
                                "origin": "11, 12",
                                "scale": "2km",
                            }
                        }
                    },
                ],
                "query": {"match": {"properties": "balcony"}},
                "score_mode": "multiply",
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      function_score: {
        functions: [
          {
            gauss: {
              price: {
                origin: '0',
                scale: '20'
              }
            }
          },
          {
            gauss: {
              location: {
                origin: '11, 12',
                scale: '2km'
              }
            }
          }
        ],
        query: {
          match: {
            properties: 'balcony'
          }
        },
        score_mode: 'multiply'
      }
    }
  }
)
puts response
res, err := es.Search(
	es.Search.WithBody(strings.NewReader(`{
	  "query": {
	    "function_score": {
	      "functions": [
	        {
	          "gauss": {
	            "price": {
	              "origin": "0",
	              "scale": "20"
	            }
	          }
	        },
	        {
	          "gauss": {
	            "location": {
	              "origin": "11, 12",
	              "scale": "2km"
	            }
	          }
	        }
	      ],
	      "query": {
	        "match": {
	          "properties": "balcony"
	        }
	      },
	      "score_mode": "multiply"
	    }
	  }
	}`)),
	es.Search.WithPretty(),
)
fmt.Println(res, err)
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" 距离您定义的位置很远(接近 2 公里),而且不太便宜(约 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

衰减函数支持的字段编辑

仅支持数字、日期和地理点字段。

如果字段缺失怎么办?编辑

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