我们曾经讲过,默认情况下,返回结果是按相关性倒序排列的。但是什么是相关性?相关性如何计算?

每个文档都有相关性评分,用一个正浮点数字段_sorce 来表示。_sorce 的评分越高,相关性越高。

查询语句会为每个文档生成一个_sorce 字段。评分的计算方式取决于查询类型 不同的查询语句用于不同的目的:fuzzy查询会计算与关键字的拼写相似程度,terms查询会计算找到的内容我们说的relevance 是我们用来计算全文本字段的值相对于全文本检索词相似程度的算法。

Elasticsearch的相似的算法被定义为检索词频率/反向文档频率,TF/IDF,包括以下内容:

检索词频率

检索词在该字段出现的频率?出现频率越高,相关性也越高。字段中出现过5次要比只出现1次的相关性高。

反向文档频率

每个检索词在索引中出现的频率?频率越高,相关性越低。检索词出现在多数文档中会比出现在少数文档中的权重更低。

字段长度准则

字段的长度是多少?长度越长,相关性越低。检索词出现在一个短的title 要比同样的词出现在一个长的content 字段权重更大。

单个查询可以联合使用TF/IDF和其他方式,比如短语查询中检索词的距离或模糊查询里的检索词相似度。

相关性并不只是全文本检索的专利。也适用于yes|no 的子句,匹配的子句越多,相关性评分越高。

如果多条查询子句被合并为一条复合查询语句,比如bool查询,则每个查询子句计算得出的评分会被合并到总的相关性评分中。

TIP

我们有一️整章着眼于相关性计算和如何让其配合你的需求 控制相关度

理解评分标准

当调试一条复杂的查询语句时,想要理解_score 究竟是如何计算是比较困难的。Elasticsearch在每个查询语句中都有一个explain 参数,将 explain 设为true 就可以得到更详细的信息。

  1. GET /_search?explain //1
  2. {
  3. "query" : { "match" : { "tweet" : "honeymoon" }}
  4. }
  1. explain 参数可以让返回结果添加一个_score 评分的得来依据。

NOTE

增加一个explain 参数会每个匹配的文档产生一大堆额外内容,但是花时间去理解他是很有意义的。如果现在看不明白也没关系——等你需要的时候再来回顾这一节就行。下面我们来一点点了解这块知识点。

首先,我们看一下普通查询返回的元数据:

{
    "_index" :      "us",
    "_type" :       "tweet",
    "_id" :         "12",
    "_score" :      0.076713204,
    "_source" :     { ... trimmed ... },

这里加入了该文档来自于那个节点那个分片上的信息,这对我们是比较有帮助的,因为词频率和文档频率是在每个分片中计算出来的,而不是每个索引中:

 "_shard" :      1,
    "_node" :       "mzIVYCsqSWCG_M_ZffSs9Q",

然后他提供了 _explanation 。每个入口都包含一个description、value、details字段,他分别告诉你计算的类型、计算结果和任何我们需要的计算细节。

"_explanation": { //1
   "description": "weight(tweet:honeymoon in 0)
                  [PerFieldSimilarity], result of:",
   "value":       0.076713204,
   "details": [
      {
         "description": "fieldWeight in 0, product of:",
         "value":       0.076713204,
         "details": [
            {  //2
               "description": "tf(freq=1.0), with freq of:",
               "value":       1,
               "details": [
                  {
                     "description": "termFreq=1.0",
                     "value":       1
                  }
               ]
            },
            { //3
               "description": "idf(docFreq=1, maxDocs=1)",
               "value":       0.30685282
            },
            { //4
               "description": "fieldNorm(doc=0)",
               "value":        0.25,
            }
         ]
      }
   ]
}
  1. honeymoon 相关性评分计算的总结
  2. 检索词频率
  3. 反向文档频率
  4. 字段长度准则

WARNING

输出explain 结果代价是十分昂贵的,他只能作调试工具。千万不要用于生产环境。

第一部分是关于计算的总结。告诉了我们honeymoon 在tweet 字段中的检索词频率/反向文档频率或TF/IDF,(这里的文档0 是一个内部的ID,跟我们没有关系,可以忽略。)

然后他提供了权重是如何计算的细节:

检索词频率:

检索词 `honeymoon` 在这个文档的 `tweet` 字段中的出现次数。

反向文档频率:

检索词 `honeymoon` 在索引上所有文档的 `tweet` 字段中出现的次数。

字段长度准则:

在这个文档中, `tweet` 字段内容的长度 -- 内容越长,值越小。


复杂的查询语句解释也非常复杂,但是包含的内容与上面例子大致相同。通过这段信息我们可以了解搜索结果是如何产生的。

TIP

JSON形式的 explain 描述是 难以阅读的,但是转成YAML 会好很多,只需要在参数中加上formay=yaml。

理解文档是如何被匹配到的

当explain 选项加到某一文档上时,explain api 会帮助你理解为何这个文档会被匹配,更重要的是,一个文档为何没有被匹配。

请求路径为/index/type/id/_explain,如下所示:

GET /us/tweet/12/_explain
{
   "query" : {
      "bool" : {
         "filter" : { "term" :  { "user_id" : 2           }},
         "must" :  { "match" : { "tweet" :   "honeymoon" }}
      }
   }
}

不只是我们之前看到的充分解释,我们现在有了一个description 元素,他将告诉我们:

"failure to match filter: cache(user_id:[2 TO 2])"

也就是说我们的user_id 过滤子句使该文档不能匹配到。