Elasticsearch控制相关度

2021-08-11 18:07:34 浏览数 (1)

简介

Elasticsearch 提供了一个最重要的功能就是相关性。它可以帮我们按照我们搜索的条件进行相关性计算。每个文档有一个叫做 _score 的分数。在默认没有 sort 的情况下,返回的文档时按照分数的大小从大到小进行排列的。

Doc相关度评分

代码语言:javascript复制
score(q,d)  =    #score(q,d) 是文档 d 与查询 q 的相关度评分。
            queryNorm(q)    #queryNorm(q) 是 查询归一化 因子 (新)。
          · coord(q,d)      #coord(q,d) 是 协调 因子 (新)。 
          · ∑ (             #查询 q 中每个词 t 对于文档 d 的权重和。
                tf(t in d)  #tf(t in d) 是词 t 在文档 d 中的 词频 。
              · idf(t)²     #idf(t) 是词 t 的 逆向文档频率 。 
              · t.getBoost()#t.getBoost() 是查询中使用的 boost(新)。 
              · norm(t,d)   #norm(t,d) 是 字段长度归一值 ,与 索引时字段层 boost (如果存在)的和(新)。  
            ) (t in q)      #查询 q 中每个词 t 对于文档 d 的权重和。

词频

词在文档中出现的频度是多少? 频度越高,权重 越高 。 5 次提到同一词的字段比只提到 1 次的更相关。词频的计算方式如下:

代码语言:javascript复制
tf(t in d) = √frequency   
#词 t 在文档 d 的词频( tf )是该词在文档中出现次数的平方根。

如果不在意词在某个字段中出现的频次,而只在意是否出现过,则可以在字段映射中禁用词频统计:

代码语言:javascript复制
PUT /my_index
{
  "mappings": {
    "doc": {
      "properties": {
        "text": {
          "type":          "string",
          "index_options": "docs"   #将参数 index_options 设置为 docs 可以禁用词频统计及词频位置,这个映射的字段不会计算词的出现次数,对于短语或近似查询也不可用。要求精确查询的 not_analyzed 字符串字段会默认使用该设置。 
        }
      }
    }
  }
}

TF 的计算永远是100%的精确,这是因为它是一个文档级的计算。

反向文档频

词在集合所有文档里出现的频率是多少?频次越高,权重 越低 。逆向文档频率的计算公式如下:

代码语言:javascript复制
idf(t) = 1   log ( numDocs / (docFreq   1))  
#词 t 的逆向文档频率( idf )是:索引中文档数量除以所有包含该词的文档数,然后求其对数。

IDF 的计算不一定是100%的精确。在默认的 query-then-fetch 计算中,它是在本地针对每个 shard 来计算的。在绝大多数的情况下,这个绝不是一个问题:

  1. 使用本地 IDF 很少出现问题,尤其是对于大型数据集
  2. 如果您的文档在各个分片之间分布良好,则本地分片之间的 IDF 将基本相同

字段长度归一值

字段的长度是多少? 字段越短,字段的权重 越高 。字段长度的归一值公式如下:

代码语言:javascript复制
norm(d) = 1 / √numTerms   #字段长度归一值( norm )是字段中词数平方根的倒数。 

字段长度的归一值对全文搜索非常重要, 许多其他字段不需要有归一值。无论文档是否包括这个字段,索引中每个文档的每个 string 字段都大约占用 1 个 byte 的空间。对于 not_analyzed 字符串字段的归一值默认是禁用的,而对于 analyzed 字段也可以通过修改字段映射禁用归一值:

代码语言:javascript复制
PUT /my_index
{
  "mappings": {
    "doc": {
      "properties": {
        "text": {
          "type": "string",
          "norms": { "enabled": false }   #这个字段不会将字段长度归一值考虑在内,长字段和短字段会以相同长度计算评分。 
        }
      }
    }
  }
}

权重boost

权重的提升会被应用到字段的每个词,而不是字段本身。查询时的权重提升 是可以用来影响相关度的主要工具,任意类型的查询都能接受 boost 参数。

查询归一因子

查询归一因子queryNorm )试图将查询 归一化 , 这样就能将两个不同的查询结果相比较。

代码语言:javascript复制
queryNorm = 1 / √sumOfSquaredWeights   #sumOfSquaredWeights 是查询里每个词的 IDF 的平方和。 

协调因子

协调因子coord ) 可以为那些查询词包含度高的文档提供奖励,文档里出现的查询词越多,它越有机会成为好的匹配结果。

Index相关度评分

当在多个索引中搜索时, 可以使用参数 indices_boost 来提升整个索引的权重。在下面例子中,当要为最近索引的文档分配更高权重时,可以这么做:

代码语言:javascript复制
GET /docs_2014_*/_search   #这个多索引查询涵盖了所有以字符串 docs_2014_ 开始的索引。 
{
  "indices_boost": {   #其中,索引 docs_2014_10 中的所有文件的权重是 3 ,索引 docs_2014_09 中是 2 ,其他所有匹配的索引权重为默认值 1 。 
    "docs_2014_10": 3,
    "docs_2014_09": 2
  },
  "query": {
    "match": {
      "text": "quick brown fox"
    }
  }
}

权重提升不会被应用于它在查询表达式中出现的层,而是会被合并下转至每个词中。

相似度算法

向量空间模型

在向量空间模型里, 向量空间模型里的每个数字都代表一个词的 权重。TF/IDF 是向量空间模型计算词权重的默认方式。

现在,设想我们有三个文档:

  1. I am happy in summer 。
  2. After Christmas I’m a hippopotamus
  3. The happy hippopotamus helped Harry 。

可以为每个文档都创建包括每个查询词—— happyhippopotamus ——权重的向量,然后将这些向量置入同一个坐标系中。

  • 文档 1: (happy,____________) —— [2,0]
  • 文档 2: ( ___ ,hippopotamus) —— [0,5]
  • 文档 3: (happy,hippopotamus) —— [2,5]

向量之间是可以比较的,只要测量查询向量和文档向量之间的角度就可以得到每个文档的相关度,文档 1 与查询之间的角度最大,所以相关度低;文档 2 与查询间的角度较小,所以更相关;文档 3 与查询的角度正好吻合,完全匹配。

在实际中,只有二维向量(两个词的查询)可以在平面上表示,幸运的是, 线性代数 ——作为数学中处理向量的一个分支——为我们提供了计算两个多维向量间角度工具,这意味着可以使用如上同样的方式来解释多个词的查询。

概率相关模型

官方文档相关度评分背后的理论解读如下:

Lucene(或 Elasticsearch)使用 布尔模型查找匹配文档,并用一个名为 实用评分函数的公式来计算相关度。这个公式借鉴了 词频/逆向文档频率和 向量空间模型,同时也加入了一些现代的新特性,如协调因子(coordination factor),字段长度归一化(field length normalization),以及词或查询语句权重提升。

Elasticsearch 5 之前的版本,评分机制或者打分模型基于 TF-IDF实现。从Elasticsearch 5之后, 缺省的打分机制改成了Okapi BM25

BM25 的 BM 是缩写自 Best Match, 25 貌似是经过 25 次迭代调整之后得出的算法,它也是基于 TF/IDF 进化来的。

能与 TF/IDF 和向量空间模型媲美的就是 Okapi BM25 ,它被认为是 当今最先进的 排序函数。 BM25 源自 概率相关模型(probabilistic relevance model) ,而不是向量空间模型,但这个算法也和 Lucene 的实用评分函数有很多共通之处。

BM25 同样使用词频、逆向文档频率以及字段长归一化,但是每个因子的定义都有细微区别。

词频饱和度

TF/IDF 和 BM25 同样使用 逆向文档频率 来区分普通词(不重要)和非普通词(重要), 同样认为(参见 词频 )文档里的某个词出现次数越频繁,文档与这个词就越相关。

另一方面,BM25 有一个上限,文档里出现 5 到 10 次的词会比那些只出现一两次的对相关度有着显著影响。但是如图 TF/IDF 与 BM25 的词频饱和度 所见,文档中出现 20 次的词几乎与那些出现上千次的词有着相同的影响。

字段长度归一化

在 字段长归一化 中,我们提到过 Lucene 会认为较短字段比较长字段更重要:字段某个词的频度所带来的重要性会被这个字段长度抵消,但是实际的评分函数会将所有字段以同等方式对待。它认为所有较短的 title 字段比所有较长的 body 字段更重要。

BM25 当然也认为较短字段应该有更多的权重,但是它会分别考虑每个字段内容的平均长度,这样就能区分短 title 字段和 长title 字段。

BM25调优

不像 TF/IDF ,BM25 有一个比较好的特性就是它提供了两个可调参数:

  • k1这个参数控制着词频结果在词频饱和度中的上升速度。默认值为 1.2 。值越小饱和度变化越快,值越大饱和度变化越慢。
  • b这个参数控制着字段长归一值所起的作用, 0.0 会禁用归一化, 1.0 会启用完全归一化。默认值为 0.75

在实践中,调试 BM25 是另外一回事, k1b 的默认值适用于绝大多数文档集合,但最优值还是会因为文档集不同而有所区别,为了找到文档集合的最优值,就必须对参数进行反复修改验证。

传统的TF值理论上是可以无限大的。而BM25与之不同,它在TF计算方法中增加了一个常量k,用来限制TF值的增长极限。下面是两者的公式:

代码语言:javascript复制
传统 TF Score = sqrt(tf)  # 平方根计算√x
BM25的 TF Score = ((k   1) * tf) / (k   tf)

BM25还引入了平均文档长度的概念,单个文档长度对相关性的影响力与它和平均长度的比值有关系。BM25的TF公式里,除了常量k外,引入另外两个参数:L和b。

(1)L是文档长度与平均长度的比值。如果文档长度是平均长度的2倍,则L=2。

(2)b是一个常数,它的作用是规定L对评分的影响有多大。加了L和b的公式变为:

代码语言:javascript复制
TF Score = ((k   1) * tf) / (k * (1.0 - b   b * L)   tf)

设置BM25

代码语言:javascript复制
PUT /my_index
{
  "mappings": {
    "doc": {
      "properties": {
        "title": {
          "type":       "string",
          "similarity": "BM25"   #title 字段使用 BM25 相似度算法。 
        },
        "body": {
          "type":       "string", 
          "similarity": "default"   #body 字段用默认相似度算法(参见 实用评分函数)。 
        }
      }
  }
}

目前,Elasticsearch 不支持更改已有字段的相似度算法 similarity 映射,只能通过为数据重新建立索引来达到目的。

配置BM25

配置相似度算法和配置分析器很相似, 自定义相似度算法可以在创建索引时指定。例如:

代码语言:javascript复制
PUT /my_index
{
  "settings": {
    "similarity": {
      "my_bm25": {   #创建一个基于内置 BM25 ,名为 my_bm25 的自定义相似度算法。 
        "type": "BM25",
        "b":    0   #禁用字段长度规范化(field-length normalization)。参见 调试 BM25 。 
      }
    }
  },
  "mappings": {
    "doc": {
      "properties": {
        "title": {
          "type":       "string",
          "similarity": "my_bm25"   #title 字段使用自定义相似度算法 my_bm25 。 
        },
        "body": {
          "type":       "string",
          "similarity": "BM25"   #字段 body 使用内置相似度算法 BM25 。
        }
      }
    }
  }
}

相关性评分分析

评分的两大特征TF和IDF

代码语言:javascript复制
POST sphinx-disease/_search
{
  "explain": true, 
  "query": {
    "match": {
      "name": "生殖细胞肿瘤"
    }
  }
}
代码语言:javascript复制
{
    "took":17,
    "timed_out":false,
    "_shards":{
        "total":1,
        "successful":1,
        "skipped":0,
        "failed":0
    },
    "hits":{
        "total":395,
        "max_score":61.857334,
        "hits":[
            {
                "_shard":"[sphinx-disease-20.05.18-143503][0]",
                "_node":"dVzWLgCbRKy-l5a8bwNxAA",
                "_index":"sphinx-disease-20.05.18-143503",
                "_type":"_doc",
                "_id":"402739865",
                "_score":61.857334,
                "_source":{
                    "istoplevel":"1",
                    "indextype":"disease",
                    "parentname":"",
                    "votecount":"412",
                    "synonyms":"松果体区生殖细胞瘤,丘脑生殖细胞瘤,恶性生殖细胞瘤,鞍区生殖细胞瘤,颅内生殖细胞瘤,松果体生殖细胞瘤",
                    "thesaurus":"松果体区生殖细胞瘤,丘脑生殖细胞瘤,恶性生殖细胞瘤,鞍区生殖细胞瘤,颅内生殖细胞瘤,松果体生殖细胞瘤",
                    "diseasekey":"shengzhixibaoliu",
                    "goodvotecount":"384",
                    "commentcount":"389",
                    "isvoteself":"1",
                    "parentkey":"",
                    "idx_diseaseid":"DiseaseId_402739865",
                    "name":"生殖细胞瘤",
                    "rank":"20040",
                    "id":"402739865",
                    "spacedoctorcnt":"312",
                    "threadcategoryid":"1397"
                },
                "_explanation":{
                    "value":61.85733,
                    "description":"sum of:",
                    "details":[
                        {
                            "value":3.8543968,
                            "description":"weight(name:生 in 121) [PerFieldSimilarity], result of:",
                            "details":[
                                {
                                    "value":3.8543968,
                                    "description":"score(doc=121,freq=1.0 = termFreq=1.0
), product of:",
                                    "details":[
                                        {
                                            "value":3.7130134,
                                            "description":"idf, computed as log(1   (docCount - docFreq   0.5) / (docFreq   0.5)) from:",
                                            "details":[
                                                {
                                                    "value":65,
                                                    "description":"docFreq",
                                                    "details":[

                                                    ]
                                                },
                                                {
                                                    "value":2683,
                                                    "description":"docCount",
                                                    "details":[

                                                    ]
                                                }
                                            ]
                                        },
                                        {
                                            "value":1.0380778,
                                            "description":"tfNorm, computed as (freq * (k1   1)) / (freq   k1 * (1 - b   b * fieldLength / avgFieldLength)) from:",
                                            "details":[
                                                {
                                                    "value":1,
                                                    "description":"termFreq=1.0",
                                                    "details":[

                                                    ]
                                                },
                                                {
                                                    "value":1.2,
                                                    "description":"parameter k1",
                                                    "details":[

                                                    ]
                                                },
                                                {
                                                    "value":0.75,
                                                    "description":"parameter b",
                                                    "details":[

                                                    ]
                                                },
                                                {
                                                    "value":16.477451,
                                                    "description":"avgFieldLength",
                                                    "details":[

                                                    ]
                                                },
                                                {
                                                    "value":15,
                                                    "description":"fieldLength",
                                                    "details":[

                                                    ]
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        },
                        {
                            "value":5.974135,
                            "description":"weight(name:生殖 in 121) [PerFieldSimilarity], result of:",
                            "details":[ ... ]
                        },
                        {
                            "value":7.774786,
                            "description":"weight(name:生殖细 in 121) [PerFieldSimilarity], result of:",
                            "details":[ ... ]
                        },
                        {
                            "value":7.774786,
                            "description":"weight(name:生殖细胞 in 121) [PerFieldSimilarity], result of:",
                            "details":[ ... ]
                        },
                        {
                            "value":5.974135,
                            "description":"weight(name:殖 in 121) [PerFieldSimilarity], result of:",
                            "details":[ ... ]
                        },
                        {
                            "value":7.774786,
                            "description":"weight(name:殖细 in 121) [PerFieldSimilarity], result of:",
                            "details":[ ... ]
                        },
                        {
                            "value":7.774786,
                            "description":"weight(name:殖细胞 in 121) [PerFieldSimilarity], result of:",
                            "details":[ ... ]
                        },
                        {
                            "value":4.007835,
                            "description":"weight(name:细 in 121) [PerFieldSimilarity], result of:",
                            "details":[ ... ]
                        },
                        {
                            "value":4.145139,
                            "description":"weight(name:细胞 in 121) [PerFieldSimilarity], result of:",
                            "details":[ ... ]
                        },
                        {
                            "value":4.145139,
                            "description":"weight(name:胞 in 121) [PerFieldSimilarity], result of:",
                            "details":[ ... ]
                        },
                        {
                            "value":2.657409,
                            "description":"weight(name:瘤 in 121) [PerFieldSimilarity], result of:",
                            "details":[ ... ]
                        }
                    ]
                }
            }
        ]
    }
}

相关度检索评分

boosting

原理说明:通过boosting修改文档相关性

  • boost取值:0 - 1 之间的值,如:0.2,代表降低评分
  • boost取值:> 1, 如:1.5,代表提升评分(提升权重取1-10比较合理)
代码语言:javascript复制
GET /_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "text": "apple"
        }
      },
      "negative": {
        "match": {
          "text": "pie tart fruit crumble tree"
        }
      },
      "negative_boost": 0.5
    }
  }
}

它接受 positivenegative 查询。只有那些匹配 positive 查询的文档罗列出来,对于那些同时还匹配 negative 查询的文档将通过文档的原始 _scorenegative_boost 相乘的方式降级后的结果。

为了达到效果, negative_boost 的值必须小于 1.0 。在这个示例中,所有包含负向词的文档评分 _score 都会减半。

constant_score

constant_score 查询中,它可以包含查询或过滤,为任意一个匹配的文档指定评分 1 ,忽略 TF/IDF 信息。

代码语言:javascript复制
GET /_search
{
  "query": {
    "bool": {
      "should": [
        {
          "constant_score": {
            "filter": {
              "match": {
                "name": "生殖细胞肿瘤"
              }
            },
            "boost": 1.2
          }
        },
        {
          "constant_score": {
            "filter": {
              "match": {
                "name": "疱疹"
              }
            },
            "boost": 2
          }
        }
      ]
    }
  }
}

function_score

function_score 查询 是用来控制评分过程的终极武器,它允许为每个与主查询匹配的文档应用一个函数, 以达到改变甚至完全替换原始查询评分 _score 的目的。

代码语言:javascript复制
GET /blogposts/post/_search
{
  "query": {
    "function_score": {   #function_score 查询将主查询和函数包括在内。 
      "query": {   #主查询优先执行。
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "field_value_factor": {   #field_value_factor函数会被应用到每个与主query匹配的文档。 
        1."field": "votes"   #每个文档的votes字段都必须有值供function_score计算。如果没有文档的votes字段有值,那么就必须使用missing属性提供的默认值来进行评分计算。 
        2."modifier": "log1p"   #modifier为log1p 。
        3."factor":   2   #双倍效果。factor值大于1会提升效果,factor值小于1会降低效果
      },
      4."boost_mode": "sum" ,  #将函数计算结果值累加到评分_score 。
      5."max_boost":  1.5   #无论field_value_factor函数的结果如何,最终结果都不会大于1.5 。 
    }
  }
}

1.new_score = old_score * number_of_votes
2.new_score = old_score * log(1   number_of_votes)
3.new_score = old_score * log(1   factor * number_of_votes)
4.new_score = old_score   log(1   0.1 * number_of_votes)

weight

为每个文档应用一个简单而不被规范化的权重提升值:当 weight2 时,最终结果为 2 * _score

field_value_factor

  • filed
代码语言:javascript复制
GET /blogposts/post/_search
{
  "query": {
    "function_score": {   #function_score 查询将主查询和函数包括在内。 
      "query": {   #主查询优先执行。
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "field_value_factor": {   #field_value_factor函数会被应用到每个与主query匹配的文档。 
        "field": "votes"   #每个文档的votes字段都必须有值供 function_score 计算。如果没有文档的votes字段有值,那么就必须使用missing属性提供的默认值来进行评分计算。 
      }
    }
  }
}

new_score = old_score * number_of_votes
  • modifier

一种融入受欢迎度更好方式是用 modifier 平滑 votes 的值。 换句话说,我们希望最开始的一些赞更重要,但是其重要性会随着数字的增加而降低。 0 个赞与 1 个赞的区别应该比 10 个赞与 11 个赞的区别大很多。

代码语言:javascript复制
GET /blogposts/post/_search
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "field_value_factor": {
        "field":    "votes",
        "modifier": "log1p"   #modifier 为 log1p 。
      }
    }
  }
}

new_score = old_score * log(1   number_of_votes)

修饰语 modifier 的值可以为: none (默认状态)、 loglog1plog2plnln1pln2psquaresqrt 以及 reciprocal

  • factor

可以通过将 votes 字段与 factor 的积来调节受欢迎程度效果的高低。

代码语言:javascript复制
GET /blogposts/post/_search
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "field_value_factor": {
        "field":    "votes",
        "modifier": "log1p",
        "factor":   2   #双倍效果。
      }
    }
  }
}

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 因子时。

代码语言:javascript复制
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 。 
    }
  }
}

new_score = old_score   log(1   0.1 * number_of_votes)

max_boost

可以使用 max_boost 参数限制一个函数的最大效果。

代码语言:javascript复制
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 产生直接影响。

衰减函数

它们可以操作数值、时间以及经纬度地理坐标点这样的字段。

linear:线性

exp:指数

gauss:高斯

三种衰减函数—— linearexpgauss (线性、指数和高斯函数),它们可以操作数值、时间以及经纬度地理坐标点这样的字段。所有三个函数都能接受以下参数:

  • origin中心点 或字段可能的最佳值,落在原点 origin 上的文档评分 _score 为满分 1.0
  • scale衰减率,即一个文档从原点 origin 下落时,评分 _score 改变的速度。(例如,每 £10 欧元或每 100 米)。
  • decay从原点 origin 衰减到 scale 所得的评分 _score ,默认值为 0.5
  • offset以原点 origin 为中心点,为其设置一个非零的偏移量 offset 覆盖一个范围,而不只是单个原点。在范围 -offset <= origin <= offset 内的所有评分 _score 都是 1.0

functions

functions 关键字保持着一个将要被使用的函数列表。 可以为列表里的每个函数都指定一个 filter 过滤器,在这种情况下,函数只会被应用到那些与过滤器匹配的文档。

  • 函数:保持着一个将要被使用的函数列表,可以为列表里的每个函数指定一个filter过滤器,此时,函数只会被应用到那些与过滤器匹配的文档。

一般函数(filter)

衰减函数

script_score

weight:提升单个函数权重

  • score_mode:每个函数返回一个结果,所以需要一种将多个结果缩减到单个值的方式,然后才能将其与原始评分 _score 合并。不与任何过滤器匹配的文档会保有其原始评分, _score 值的为 1 。评分模式 score_mode 参数正好扮演这样的角色, 它接受以下值:

multiply函数结果求积(默认)

sum函数结果求和

avg函数结果的平均值

max函数结果的最大值

min函数结果的最小值

first使用首个函数(可以有过滤器,也可能没有)的结果作为最终结果

  • script_score
  • random_score:会输出一个 0 到 1 之间的数, 当种子 seed 值相同时,生成的随机结果是一致的。为每个用户都使用一个不同的随机评分对结果排序,但对某一具体用户来说,看到的顺序始终是一致的。

到目前为止,我们展现的都是为所有文档应用单个函数的使用方式,现在会用过滤器将结果划分为多个子集(每个特性一个过滤器),并为每个子集使用不同的函数。

代码语言:javascript复制
GET /_search
{
  "query": {
    "function_score": {
      "filter": {  #function_score 查询有个 filter 过滤器而不是 query 查询。 
        "term": { "city": "Barcelona" }
      },
      "functions": [   #functions 关键字存储着一个将被应用的函数列表。 
        {
          "filter": { "term": { "features": "wifi" }},   #函数会被应用于和 filter 过滤器(可选的)匹配的文档。
          "weight": 1
        },
        {
          "filter": { "term": { "features": "garden" }},   #函数会被应用于和 filter 过滤器(可选的)匹配的文档。
          "weight": 1
        },
        {
          "filter": { "term": { "features": "pool" }},   #函数会被应用于和 filter 过滤器(可选的)匹配的文档。
          "weight": 2   #pool 比其他特性更重要,所以它有更高 weight 。 
        },
        {
          "random_score": {   #random_score 语句没有任何过滤器 filter ,所以会被应用到所有文档。 
            "seed":  "the users session id"   #将用户的会话 ID 作为种子 seed ,让该用户的随机始终保持一致,相同的种子 seed 会产生相同的随机结果。 
          }
        }
      ],
      "score_mode": "sum",   #score_mode 指定各个函数的值进行组合运算的方式。
    }
  }
}

function_score 查询会提供一组 衰减函数(decay functions) , 让我们有能力在两个滑动标准,如地点和价格,之间权衡。

代码语言:javascript复制
GET /_search
{
  "query": {
    "function_score": {
      "functions": [
        {
          "gauss": {
            "location": {   #location 字段以地理坐标点 geo_point 映射。 
              "origin": { "lat": 51.5, "lon": 0.12 },
              "offset": "2km",
              "scale":  "3km"
            }
          }
        },
        {
          "gauss": {
            "price": {   #price 字段是数值。 
              "origin": "50",   #参见 理解价格语句 ,理解 origin 为什么是 50 而不是 100 。
              "offset": "50",
              "scale":  "20"
            }
          },
          "weight": 2   #price 语句是 location 语句权重的两倍。
        },
        {
        "script_score": {
          "params": {   #将这些变量作为参数 params 传递,我们可以查询时动态改变脚本无须重新编译。
            "threshold": 80,
            "discount": 0.1,
            "target": 10
          },
          "script": "price  = doc['price'].value; margin = doc['margin'].value;
          if (price < threshold) { return price * margin / target };
          return price * (1 - discount) * margin / target;"   #JSON 不能接受内嵌的换行符,脚本中的换行符可以用 n 或 ; 符号替代。 
          }
        }
      ]
    }
  }
}

rescore_query

原理说明:二次评分是指重新计算查询返回结果文档中指定个数文档的得分,Elasticsearch会截取查询返回的前N个,并使用预定义的二次评分方法来重新计算他们的得分。

适用场景:对查询语句的结果不满意,需要重新打分的场景。但,如果对全部有序的结果集进行重新排序的话势必开销会很大,使用rescore_query只对结果集的子集进行处理。

代码语言:javascript复制
GET news_index/_search
{
  "query": {
    "exists": {
      "field": "like"
    }
  },
  "rescore": {
    "window_size": 50,
    "query": {
      "rescore_query": {
        "function_score": {
          "script_score": {
            "script": {
              "source": "doc.like.value"
            }
          }
        }
      }
    }
  }
}

window_size含义:query rescorer仅对query和 post_filter阶段返回的前K个结果执行第二个查询

每个分片上要检查的文档数量可由window_size参数控制,默认为10。

0 人点赞