DarkMatter in Cyberspace
  • Home
  • Categories
  • Tags
  • Archives

Elasticsearch的相关度计算方法和排序规则


期望搜索效果

Q: 我想首先按相关度排序,然后再根据时间进行倒序排列

A: 同一个查询问题,每个结果的匹配度是不用的,例如,搜索“俄罗斯五金机械”,得到如下三个结果:

  • 2017俄罗斯莫斯科机械,设备技术与产品展览会

  • 2017俄罗斯机械设备展

  • 2016俄罗斯五金机械展

以上三个例子中,相关度最好的是”俄罗斯五金机械展“, 原因是同时包含所有关键词,并且长度最短。 由于这3个结果的匹配度各不相同,不可能”再根据时间进行倒序排列“。 只有匹配度相同的多个展会,才能按照时间顺序二次排列。

Q: 可是竞争对手(例如展酷)就能做出这样的效果

A: 搜一两个关键词效果好不难,难的是各种类型的搜索效果都不太差。 用下面列出来的关键词进行搜索,看看他们的搜索结果有多不靠谱。

Q: 可是百度,Google的搜索结果就很靠谱,这种技术已经相当普及了,为什么我们不能实现?

A: 国内用户被盗版惯坏了,以为世界上并不存在”软件研发成本“这种事,真的该醒醒了。 软件产品的研发成本是巨大的,免费的软件要么是人家太有钱不跟你计较,顺便整死竞争对手, 要么是靠植入广告流氓插件挣钱,要么是开源,然而Google、百度、展酷、牛展网……没有一个愿意开源。

现在展会搜索用户体验不够好,希望大家多提意见和建议,帮助我们向高水平看齐,持续改进。

搜索效果检查关键词

下面列出的查询语句用于测试搜索效果的好坏:

  • 2016五金

  • 2016科隆五金

  • 五金2016

  • 五金机械2016

相关度计算方法

指标定义和公式

相关度指搜索问题(query, q)与搜索目标域(field, f)之间的匹配程度。 例如搜索中文名称中有“五金机械”的展会,这里“中文名称”对应的数据库字段nameZHCN 就是目标域,“五金机械”就是问题。问题可以被拆分成多个关键词(term, t),例如五金机械 包含五金和机械两个关键词。

根据以下指标计算问题和目标域之间的相关度:

  • tf (term frequency): 表示关键词出现的频率的高低,关键词出现的频率越高, 匹配程度越高,例如查询“五金机械”,“2016俄罗斯五金机械展”比"2017俄罗斯机械设备展"匹配度高。 计算公式:tf(q in f) = sqrt(frequency), 其中frequency指field中包含的term数量,例如“2016俄罗斯五金机械展”的frequency是2(五金,机械), 所以tf("五金机械" in "2016俄罗斯五金机械展") = sqrt(2) = 1.414, tf("五金机械" in "2017俄罗斯机械设备展") = sqrt(1) = 1.

  • idf (inverse document frequency): 一个关键词在所有文档中出现的频率越高, 则其匹配权重越低,例如搜索“五金机械展”,“2017俄罗斯五金机械博览会”(A) 和 “2016俄罗斯五金机械展”(B)哪个匹配度高? 我们认为A高于B,因为“展”这个词太常见了,匹配到“展”的价值显然小于匹配“五金”的价值。 怎么把这个特点用算法表达出来?就要用到idf。计算公式: idf(t) = 1 + ln(maxDoc / (matchedDoc + 1)). 其中maxDoc是所有文档数量,matchedDoc是包含关键词t的文档的数量。 例如整个type包含3422个文档,其中40个包含关键词“五金”,则 idf("五金") = 1 + ln(3422 / (40 + 1)) = 5.4244, idf("展") = 1 + ln(3422 / (2410 + 1)) = 1.35018。 另外,A和B的tf是一样的:分别包含3个term(五金,机械,展)中的两个。

  • fn (field-length norm): 域越长,匹配度越低,实际上反映了”关键词密度“的高低, 域越长,关键词密度越低。例如搜索“2017俄罗斯五金机械”,”2017俄罗斯五金机械展“ 的匹配度高于“2017俄罗斯莫斯科机械,设备技术与五金产品展览会”。 计算公式:fn(f) = 1 / sqrt(numTerm). 其中numTerm是域f包含的关键词数量。 例如fn("2017年土耳其国际门及门锁五金机械博览会") = 1 / sqrt(10) = 0.3162, numTerm = 10是域被中文分词后词的数量。

  • qn (query norm): 这个指标没什么用,可以忽略,计算公式: qn(f) = 1 / sqrt(idf1^2 + idf2^2 + ...), 其中idf1,idf2... 是f中包含的每个关键词的idf值。 例如qn("2017年土耳其国际门及门锁五金机械博览会") = 1 / sqrt(5.4244^2 + 4.2476^2) = 0.1451

实例分析

搜索"五金机械",我们来看看它的相关度2.1818347是怎么算出来的:

api='api.newfairs.com'
idx=production
type=Fair
http -b POST "https://$api/$type/_search?explain=1&pretty=1&search_type=dfs_query_then_fetch" query:='{"bool":{"must":[{ "match": { "recurrence.nameZHCN": "五金机械" }}]}}' sort:='[{ "_score": { "order": "desc" }}, {"recurrence.timeStart": {"order" : "desc", "mode": "max"}}]' size=1 | jq -r '.hits.hits[] | "\(._score): \(._source.recurrence[0].nameZHCN), \(._source.recurrence[0].timeStart), \(._explanation)"' 

2.1818347: 2017年土耳其国际门及门锁五金机械博览会, 2017-01-04T16:00:00.000Z,
{
  "value": 2.1529999,
  "description": "sum of:",
  "details": [
    {
      "value": 2.1529999,
      "description": "sum of:",
      "details": [
        {
          "value": 1.3346298,
          "description": "weight(recurrence.nameZHCN:五金 in 3005) [PerFieldSimilarity], result of:",
          "details": [
            {
              "value": 1.3346298,
              "description": "score(doc=3005,freq=1.0), product of:",
              "details": [
                {
                  "value": 0.7873329,
                  "description": "queryWeight, product of:",
                  "details": [
                    {
                      "value": 5.4244084,
                      "description": "idf(docFreq=40, maxDocs=3422)",
                      "details": []
                    },
                    {
                      "value": 0.14514631,
                      "description": "queryNorm",
                      "details": []
                    }
                  ]
                },
                {
                  "value": 1.6951276,
                  "description": "fieldWeight in 3005, product of:",
                  "details": [
                    {
                      "value": 1,
                      "description": "tf(freq=1.0), with freq of:",
                      "details": [
                        {
                          "value": 1,
                          "description": "termFreq=1.0",
                          "details": []
                        }
                      ]
                    },
                    {
                      "value": 5.4244084,
                      "description": "idf(docFreq=40, maxDocs=3422)",
                      "details": []
                    },
                    {
                      "value": 0.3125,
                      "description": "fieldNorm(doc=3005)",
                      "details": []
                    }
                  ]
                }
              ]
            }
          ]
        },
        {
          "value": 0.81837,
          "description": "weight(recurrence.nameZHCN:机械 in 3005) [PerFieldSimilarity], result of:",
          "details": [
            {
              "value": 0.81837,
              "description": "score(doc=3005,freq=1.0), product of:",
              "details": [
                {
                  "value": 0.61652803,
                  "description": "queryWeight, product of:",
                  "details": [
                    {
                      "value": 4.2476315,
                      "description": "idf(docFreq=132, maxDocs=3422)",
                      "details": []
                    },
                    {
                      "value": 0.14514631,
                      "description": "queryNorm",
                      "details": []
                    }
                  ]
                },
                {
                  "value": 1.3273848,
                  "description": "fieldWeight in 3005, product of:",
                  "details": [
                    {
                      "value": 1,
                      "description": "tf(freq=1.0), with freq of:",
                      "details": [
                        {
                          "value": 1,
                          "description": "termFreq=1.0",
                          "details": []
                        }
                      ]
                    },
                    {
                      "value": 4.2476315,
                      "description": "idf(docFreq=132, maxDocs=3422)",
                      "details": []
                    },
                    {
                      "value": 0.3125,
                      "description": "fieldNorm(doc=3005)",
                      "details": []
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "value": 0,
      "description": "match on required clause, product of:",
      "details": [
        {
          "value": 0,
          "description": "# clause",
          "details": []
        },
        {
          "value": 0.14514631,
          "description": "_type:Fair, product of:",
          "details": [
            {
              "value": 1,
              "description": "boost",
              "details": []
            },
            {
              "value": 0.14514631,
              "description": "queryNorm",
              "details": []
            }
          ]
        }
      ]
    }
  ]
}

分析上面的_explanation的输出: 计算结果图示

上面的搜索命令中,搜索URL后面的?explain=1&pretty=1&search_type=dfs_query_then_fetch是什么意思? 主要为了解决ES的sharding效果带来的计算结果不一致问题, 详见参考文献"How scoring works in Elasticsearch"的"The Sharding Effect"一节。

参考文献

  • How scoring works in Elasticsearch

  • Theory Behind Relevance Scoring

  • What Is Relevance?

搜索结果的排序方法

观察上面的搜索命令,其中排序部分是:

sort:='[{ "_score": { "order": "desc" }}, {"recurrence.timeStart": {"order" : "desc", "mode": "max"}}]'

其中包含两个排序指令:

  1. 按照相关度逆序排列;

  2. 对于匹配度相同的展会,按照展会开始时间逆序排列

参考文献:Sorting



Published

Aug 17, 2016

Last Updated

Aug 17, 2016

Category

Tech

Tags

  • elasticsearch 6
  • sort 2

Contact

  • Powered by Pelican. Theme: Elegant by Talha Mansoor