ITPub博客

首页 > IT基础架构 > > ElasticSearch是实现搜索的?----评分篇

ElasticSearch是实现搜索的?----评分篇

作者:格伯纳 时间:2020-06-01 13:34:14 0 删除 编辑
GET my_index/_search
{
  "explain": true, 
  "query":{
            "multi_match": {
              "fields": [
                "name^3.87",
                "name_native^4.12",
                "address_native^0.75",
                "street^0.50",
              ],
              "query": "jinshan Hotel",
              "tie_breaker": 0.12
            }
  }
}

面对这样一个常见的query,ElasticSearch是如何执行Query?我们只知道它是要在Fields数组里的这些字段搜索“jinshan Hotel”,但是ElasticSearch是如何找出相关的doc并且评分返回最相关的docs?

这个Query,ElasticSearch的执行可以分为两步

  1. Boolean:找出匹配的document。
  2. Score:对这些匹配的document计算相关性评分,然后排序,返回指定位置的doc,比如返回top 10,返回第20到30个doc。

Boolean

Boolean用于找到相关的doc,这里分为两步:

  1. 根据搜索关键字划分出要搜索的term:在上面的例子里搜索的关键字是“jinshan Hotel”,如果采用默认的分词器,就会划分出jinshan和Hotel两个term
  2. 根据搜索term,在指定的fields中根据倒排索引,找出所有出现doc。这里搜索的term有两个jinshan和Hotel,
    1. ElasticSearch会先找出包含jinshan的所有docA集合,然后找出包含Hotel的所有doc,这个为B集合。
    2. 根据两者的关系,在这里关系是or,也就是合并AB两个集合。还有很多关系,比如and,就是同时包含两个,也还有“非”,就是不包含某个term。

Score

ElasticSearch默认的score函数是采用TFIDF,它是两个值TF和IDF,最后得分就是两者的乘积:

  • TF:Term Frequency,表示search term和这个Field的相关性。
    • 词频:search term出现的次数。在Field中如果search term出现的次数越多,这个Field就越相关
    • Field term长度:如果这个Field包含的term越少,而它又search term相关,则这个Field就越相关。比如一个Field有三个term,search term只出现了一次,而另外一个Field只有一个term,那就是search term。那第二个Field肯定会更加相关。

最后的公式就是TF=(freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength))。

    • freq:search term出现的次数
    • fieldLength:表示当前Field的term数量,比如I am a grabber,那fieldLength就是4
    • avgFieldLength表示当前Field的term数量平均值,比如有三个doc,一个是I,一个是I am a grabber,最后一个是hello grabber。那最后的得分就是(1+4+2)/3=2.3333。
  • IDF:search term的区分度,用于比较不同search term之间的相关性。上面的例子有两个search term,一个是jinshan,另外一个是hotel。我们找到包含jinshan的doc并计算它们和jinshan的相关性,找到包含hotel的doc并计算相关性,那这两组doc之间如何比较呢?这就要看这两个term的区分度。假设在整个数据库里,当前Field出现jinshan的doc数量只有2个,当前Field出现Hotel的doc数量有100个,那自然jinshan这个词的区分度会更大些,也就会jinshan的doc里会得到更多的分数。

最后的公式就是IDF=log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5))

  • docCount:表示全部doc数量
  • docFreq:表示包含search term的doc数量

计算得分是基于目前shard级别,最后合并的机器只是做分数的统一排序

具体事例如下:

有一个index一共有5个shard,里面有9个doc,具体分布为

执行Query如下

GET /my_index/_search
{
  "explain": true, 
  "query": {
    "match": {
      "text": "fox"
    }
  }
}

返回结果

{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 5,
    "max_score": 1.3857586,
    "hits": [
      {
        "_shard": "[my_index][1]",这个doc在第1个shard
        "_node": "h9ZfNNHpSDCx8hNTcSlGxQ",
        "_index": "my_index",
        "_type": "doc",
        "_id": "9",
        "_score": 1.3857586,
        "_source": {
          "text": "hello fox"
        },
        "_explanation": {
          "value": 1.3857586,
          "description": "weight(text:fox in 0) [PerFieldSimilarity], result of:",
          "details": [
            {
              "value": 1.3857586,
              "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
              "details": [
                {
                  "value": 0.98082924,
                  "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                  "details": [
                    {
                      "value": 1,
                      "description": "docFreq",第一个shard里出现fox的doc只有1个
                      "details": []
                    },
                    {
                      "value": 3,
                      "description": "docCount",第一个shard里一共有3个doc
                      "details": []
                    }
                  ]
                },
                {
                  "value": 1.412844,
                  "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                  "details": [
                    {
                      "value": 1,
                      "description": "termFreq=1.0", 在这个shard里fox这个关键字这个Field里出现了1次
                      "details": []
                    },
                    {
                      "value": 1.2,
                      "description": "parameter k1", 常量
                      "details": []
                    },
                    {
                      "value": 0.75,
                      "description": "parameter b", 常量
                      "details": []
                    },
                    {
                      "value": 7,
                      "description": "avgFieldLength",  在这个shard里这个field的平均term数,在这个shard里有三个doc,第一个doc有一个term,第二个doc有18个term,第三个doc有2个term,平均下来这个field的长度就是(1+18+2)/3=7
                      "details": []
                    },
                    {
                      "value": 2,
                      "description": "fieldLength", 当前doc所包含term数2个
                      "details": []
                    }
                  ]
                }
              ]
            }
          ]
        }
      },
      {
        "_shard": "[my_index][3]", 这个doc在第3个shard
        "_node": "h9ZfNNHpSDCx8hNTcSlGxQ",
        "_index": "my_index",
        "_type": "doc",
        "_id": "1",
        "_score": 0.5754429,
        "_source": {
          "text": "quick brown fox"
        },
        "_explanation": {...}
      },
      {
        "_shard": "[my_index][4]", 这个doc在第4个shard上
        "_node": "h9ZfNNHpSDCx8hNTcSlGxQ",
        "_index": "my_index",
        "_type": "doc",
        "_id": "3",
        "_score": 0.45207188,
        "_source": {
          "text": "fox fox fox"
        },
        "_explanation": {...}
      },
      {
        "_shard": "[my_index][2]", 这个doc在第2个shard
        "_node": "h9ZfNNHpSDCx8hNTcSlGxQ",
        "_index": "my_index",
        "_type": "doc",
        "_id": "4",
        "_score": 0.4471386,
        "_source": {
          "text": "fox is unlucky"
        },
        "_explanation": {...}
      },
      {
        "_shard": "[my_index][2]",这个doc在第2个shard
        "_node": "h9ZfNNHpSDCx8hNTcSlGxQ",
        "_index": "my_index",
        "_type": "doc",
        "_id": "2",
        "_score": 0.3901917,
        "_source": {
          "text": "I am a fox"
        },
        "_explanation": {...}
      }
    ]
  }
}


上面是搜索关键字分词之后只有一个term,如果分词之后有多个term是如何打分的呢?

其实很简单,就是针对每个term重复上面的操作,然后将两个分数加起来。

GET /my_index/_search
{
  "explain": true, 
  "query": {
    "match": {
      "text": "fox hello"
    }
  }
}

现在搜索的term有两个,一个是fox,另外一个是hello

        "_explanation": {
          "value": 1.4435054,
          "description": "sum of:",
          "details": [每个搜索term的得分
            {
              "value": 0.9530774,
              "description": "weight(text:fox in 0) [PerFieldSimilarity], result of:", fox的得分
              "details": [
                {
                  "value": 0.9530774,
                  "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
                  "details": [
                    {
                      "value": 0.6931472,
                      "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                      "details": [
                        {
                          "value": 2,
                          "description": "docFreq",
                          "details": []
                        },
                        {
                          "value": 4,
                          "description": "docCount",
                          "details": []
                        }
                      ]
                    },
                    {
                      "value": 1.375,
                      "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": 6,
                          "description": "avgFieldLength",
                          "details": []
                        },
                        {
                          "value": 2,
                          "description": "fieldLength",
                          "details": []
                        }
                      ]
                    }
                  ]
                }
              ]
            },
            {
              "value": 0.49042803,
              "description": "weight(text:hello in 0) [PerFieldSimilarity], result of:", hello的得分
              "details": [
                {
                  "value": 0.49042803,
                  "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
                  "details": [
                    {
                      "value": 0.35667494,
                      "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                      "details": [
                        {
                          "value": 3,
                          "description": "docFreq",
                          "details": []
                        },
                        {
                          "value": 4,
                          "description": "docCount",
                          "details": []
                        }
                      ]
                    },
                    {
                      "value": 1.375,
                      "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": 6,
                          "description": "avgFieldLength",
                          "details": []
                        },
                        {
                          "value": 2,
                          "description": "fieldLength",
                          "details": []
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }


如果是搜索term里某个term出现多次

GET /my_index/_search
{
  "explain": true, 
  "query": {
    "match": {
      "text": "fox hello hello"
    }
  }
}


也是求和 score(fox)+2*score(hello)

        "_explanation": {
          "value": 2.012745,
          "description": "sum of:",
          "details": [
            {
              "value": 0.87138504,
              "description": "weight(text:fox in 0) [PerFieldSimilarity], result of:",
              "details": [
                {
                  "value": 0.87138504,
                  "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
                  "details": [
                    {
                      "value": 0.6931472,
                      "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                      "details": [
                        {
                          "value": 2,
                          "description": "docFreq",
                          "details": []
                        },
                        {
                          "value": 4,
                          "description": "docCount",
                          "details": []
                        }
                      ]
                    },
                    {
                      "value": 1.2571429,
                      "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": 6,
                          "description": "avgFieldLength",
                          "details": []
                        },
                        {
                          "value": 3,
                          "description": "fieldLength",
                          "details": []
                        }
                      ]
                    }
                  ]
                }
              ]
            }


所以假设一个search keywords是A B C C,那这个doc的最终得分就是TFIDF(A)+TFIDF(B)+2*TFIDF(C)

如果针对多个Field进行打分,就看这多个Field是如何组合方式?

比如是multimatch,那就是选择Field得分最高的分数作为这个doc的分数

shard级别分数

从上面看到所有的得分都是shard级别,也就是说最后的排序并不是按照全局得分进行排序,而是按照shard得分进行排序的。这就会带来一个问题,shard得分比较高,但是全局来看并不高,这种doc还会在前面。比如上面的布局

GET my_index/_search
{
  "explain": true, 
  "query":{
    "match": {
      "text": "fox"
    }
  }
}

得分最高的是“hello fox”,而不是“fox fox fox”。从全局来看后者得分应该更高,但是在shard得分里“hello fox”的得分会更高。

总结

ElasticSearch是在shard级别找到符合search term的doc,然后计算search term在各个Field的TFIDF值,然后根据query条件计算出最终得分。再又合并的机器将所有shard结果进行合并并且根据各自的TFIDF值进行排序返回给用户。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31509949/viewspace-3156701/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
管理员

注册时间:2018-03-30

  • 博文量
    2383
  • 访问量
    983995