原文地址

上次写了关于 Elasticsearch 如何分词索引, 接着继续写 Elasticsearch 怎么计算搜索结果的得分(_score).

Elasticsearch 默认是按照文档与查询的相关度(匹配度)的得分倒序返回结果的. 得分 (_score) 就越大, 表示相关性越高.

所以, 相关度是啥? 分数又是怎么计算出来的? (全文检索和结构化的 SQL 查询不太一样, 虽然看起来结果比较'飘忽', 但也是可以追根问底的)


在 Elasticsearch 中, 标准的算法是 Term Frequency/Inverse Document Frequency, 简写为 TF/IDF, (刚刚发布的 5.0 版本, 改为了据说更先进的 BM25 算法)

Term Frequency

某单个关键词(term) 在某文档的某字段中出现的频率次数, 显然, 出现频率越高意味着该文档与搜索的相关度也越高

具体计算公式是 tf(q in d) = sqrt(termFreq)

另外, 索引的时候可以做一些设置, "index_options": "docs" 的情况下, 只考虑 term 是否出现(命中), 不考虑出现的次数.

PUT /my_index
{"mappings": {"doc": {"properties": {"text": {"type":          "string","index_options": "docs"}}}}
}

Inverse document frequency

某个关键词(term) 在索引(单个分片)之中出现的频次. 出现频次越高, 这个词的相关度越低. 相对的, 当某个关键词(term)在一大票的文档下面都有出现, 那么这个词在计算得分时候所占的比重就要比那些只在少部分文档出现的词所占的得分比重要低. 说的那么长一句话, 用人话来描述就是 "物以稀为贵", 比如, '的', '得', 'the' 这些一般在一些文档中出现的频次都是非常高的, 因此, 这些词占的得分比重远比特殊一些的词(如'Solr', 'Docker', '哈苏')占比要低,

具体计算公式是 idf = 1 + ln(maxDocs/(docFreq + 1))

Field-length Norm

字段长度, 这个字段长度越短, 那么字段里的每个词的相关度也就越大. 某个关键词(term) 在一个短的句子出现, 其得分比重比在一个长句子中出现要来的高.

具体计算公式是 norm = 1/sqrt(numFieldTerms)

默认每个 analyzed 的 string 都有一个 norm 值, 用来存储该字段的长度,

用 "norms": { "enabled": false } 关闭以后, 评分时, 不管文档的该字段长短如何, 得分都一样.

PUT /my_index
{"mappings": {"doc": {"properties": {"text": {"type": "string","norms": { "enabled": false }}}}}
}
最后的得分是三者的乘积 tf * idf * norm

以上描述的是最原始的针对单个关键字(term)的搜索. 如果是有多个搜索关键词(terms)的时候, 还要用到的 Vector Space Model

如果查询复杂些, 或者用到一些修改了分数的查询, 或者索引时候修改了字段的权重, 比如 function_score 之类的,计算方式也就又更复杂一些.

Explain

看上去 TF/IDF 的算法已经一脸懵逼吓跑人了, 不过其实, 用 Explain 跑一跑也没啥, 虽然各种开方, 自然对数的, Google一个科学计算器就是了.

举个例子

/*先删掉索引, 如果有的话*/
curl -XDELETE 'http://localhost:9200/blog'curl -XPUT 'http://localhost:9200/blog/' -d '
{"mappings": {"post": {"properties": {"title": {"type": "string","analyzer": "standard","term_vector": "yes"}}}}
}'

存入一些文档 (Water 随手加进去测试的.)

curl -s -XPOST localhost:9200/_bulk -d '
{ "create": { "_index": "blog", "_type": "post", "_id": "1" }}
{ "title": "What is the best water temperature, Mr Water" }
{ "create": { "_index": "blog", "_type": "post", "_id": "2" }}
{ "title": "Water no symptoms" }
{ "create": { "_index": "blog", "_type": "post", "_id": "3" }}
{ "title": "Did Vitamin B6 alone work for you? Water?" }
{ "create": { "_index": "blog", "_type": "post", "_id": "4" }}
{ "title": "The ball drifted on the water." }
{ "create": { "_index": "blog", "_type": "post", "_id": "5" }}
{ "title": "No water no food no air" }
'

bulk insert 以后先用 Kopf 插件输出看一下, 5 个文档并不是平均分配在 5 个分片的, 其中, 编号为 2 的这个分片里边有两个文档, 其中编号为 0 的那个分片是没有分配文档在里面的.

接下来, 搜索的同时 explain

原本输出的 json 即使加了 pretty 也很难看, 换成 yaml 会好不少

curl -XGET "http://127.0.0.1:9200/blog/post/_search?explain&format=yaml" -d '
{"query": {"term": {"title": "water"}}
}'

输出如图(json)

可以看到五个文档都命中了这个查询, 注意看每个文档的 _shard

整个输出 yml 太长了, 丢到最后面, 只截取了其中一部分, 如图,

返回排名第一的分数是 _score: 0.2972674, _shard(2),

"weight(title:water in 0) [PerFieldSimilarity], result of:" 这里的 0 不是 _id, 只是 Lucene 的一个内部文档 ID, 可以忽略.

排名第一和第二的两个文档刚好是在同一个分片的, 所以跟另外三个的返回结果有些许不一样, 主要就是多了一个 queryWeight, 里面的 queryNorm 只要在同一分片下, 都是一样的, 总而言之, 这个可以忽略(至少目前这个例子可以忽略)

只关注 fieldWeight, 排名第一和第二的的 tf 都是 1,

在 idf(docFreq=2, maxDocs=2) 中, docFreq 和 maxDocs 都是针对单个分片而言, 2号分片一共有 2个文档(maxDocs), 然后命中的文档也是两个(docFreq).

所以 idf 的得分, 根据公式, 1 + ln(maxDocs/(docFreq + 1)) 是 0.59453489189

最后 fieldNorm, 这个 field 有三个词, 所以是 1/sqrt(3), 但是按官方给的这个公式怎么算都不对, 不管哪个文档. 后来查了一下, 说是 Lucene 存这个 lengthNorm 数据时候都是用的 1 byte来存, 所以不管怎么着都会丢掉一些精度. 呵呵哒了 = . =

最后的最后, 总得分 = 1 * 0.5945349 * 0.5 = 0.2972674.

同理其他的几个文档也可以算出这个得分, 只是都要被 fieldNorm 的精度问题蛋疼一把.

完整结果太长了, 贴个 gist
https://gist.github.com/xguox/077e18afe24f52f6e2b45efb0b4e304f


Elasticsearch 5 (Lucene 6) 的 BM25 算法

Elasticsearch 前不久发布了 5.0 版本, 基于 Lucene 6, 默认使用了 BM25 评分算法.

BM25 的 BM 是缩写自 Best Match, 25 貌似是经过 25 次迭代调整之后得出的算法. 它也是基于 TF/IDF 进化来的. Wikipedia 那个公式看起来很吓唬人, 尤其是那个求和符号, 不过分解开来也是比较好理解的.

总体而言, 主要还是分三部分, TF - IDF - Document Length

IDF 还是和之前的一样. 公式 IDF(q) = 1 + ln(maxDocs/(docFreq + 1))

f(q, D) 是 tf(term frequency)

|d| 是文档的长度, avgdl 是平均文档长度.

先不看 IDF 和 Document Length 的部分, 变成 tf * (k + 1) / (tf + k),

相比传统的 TF/IDF (tf(q in d) = sqrt(termFreq)) 而言, BM25 抑制了 tf 对整体评分的影响程度, 虽然同样都是增函数, 但是, BM25 中, tf 越大, 带来的影响无限趋近于 (k + 1), 这里 k 值通常取 [1.2, 2], 而传统的 TF/IDF 则会没有临界点的无限增长.

而文档长度的影响, 同样的, 可以看到, 命中搜索词的情况下, 文档越短, 相关性越高, 具体影响程度又可以由公式中的 b 来调整, 当设值为 0 的时候, 就跟之前 'TF/IDF' 那篇提到的 "norms": { "enabled": false } 一样, 忽略文档长度的影响.

综合起来,

k = 1.2

b = 0.75

idf * (tf * (k + 1)) / (tf + k * (1 - b + b * (|d|/avgdl)))

最后再对所有的 terms 求和. 就是 Elasticsearch 5 中一般查询的得分了.


Related:

http://opensourceconnections.com/blog/2015/10/16/bm25-the-next-generation-of-lucene-relevation/

What Is Relevance?

From:

Elasticsearch 的分数(_score)是怎么计算得出
Elasticsearch 5.X(Lucene 6) 的 BM25 相关度算法

ElasticSearch 的分数 (_score) 是怎么计算得出 (2.X 5.X)相关推荐

  1. elasticsearch搜索分数自定义以及相关度计算相关

    elasticsearch搜索分数自定义以及相关度计算相关 es通过其score字段对搜索结果进行排序 在进行业务开发时通常其默认的分数计算是不符合预期的. 最简单的方法是通过boost字段来对每一个 ...

  2. python输出假分数_分数的表示以及计算(c++)

    之前一直总是简单的想将分数化为小数进行计算,其实使用相应的结构进行分子分母的分开保存,可以有奇效: 分数的存储: struct Fraction{ int up; int down; }; 其中up代 ...

  3. 分数的表示以及计算(c++)

    之前一直总是简单的想将分数化为小数进行计算,其实使用相应的结构进行分子分母的分开保存,可以有奇效: 分数的存储: struct Fraction{int up;int down; }; 其中up代表分 ...

  4. 正负分数计算机在线算使用,分数的表示以及计算(c++)

    之前一直总是简单的想将分数化为小数进行计算,其实使用相应的结构进行分子分母的分开保存,可以有奇效: 分数的存储: struct Fraction{ int up; int down; }; 其中up代 ...

  5. HDU2156 分数矩阵【数学计算+水题】

    分数矩阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submi ...

  6. C++ 关于分数的处理与计算

    1.分数的表示:结构体 struct Fraction{int up;//分子int down;//分母Fraction(){};Fraction(int _up,int _down){this-&g ...

  7. es Score Query优化查询

    通过Function Score Query function_score查询是处理分值计算过程的终极工具.它让你能够对所有匹配了主查询的每份文档调用一个函数来调整甚至是完全替换原来的_score. ...

  8. 【ElasticSearch】es 使用function_score及soft_score定制搜索结果的分数

    1.概述 官网:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-que ...

  9. Elasticsearch:使用 function_score 及 script_score 定制搜索结果的分数

    我们将介绍使用 function_score 的基础知识,并介绍一些 function core 技术非常有用和有效的用例. 介绍 评分的概念是任何搜索引擎(包括 Elasticsearch)的核心. ...

最新文章

  1. dos 下启动mysql时,报服务器找不到错误
  2. 17福师《计算机应用基础,17春福师《计算机应用基础》在线作业一.doc
  3. Unet实现图像分割(二)
  4. 超强、超详细Redis入门教程【转】
  5. Tomcat maven 插件启动出现tomcat\conf\tomcat-users.xml cannot be read异常解决方法
  6. 保留12位小数的浮点数(信息学奥赛一本通-T1025)
  7. 【Flink】Flink AM container is launched, waiting for AM container to Register with RM
  8. mysql公告信息管理系统_JSP班级公告管理系统+mysql 班级公告管理系统 - 下载 - 搜珍网...
  9. JQueryDOM之样式操作
  10. Java IO源码目录
  11. DNF游戏三方制裁解决方案(亲测有效)虚拟机
  12. 电子表格软件2013 免费版
  13. VMware安装失败 找不到msi文件
  14. unity+ar制作太阳系
  15. linux 下DSP音频处理
  16. java计算机毕业设计Vue垃圾分类指南平台设计与实现源码+数据库+系统+lw文档
  17. 2021年9面试总结
  18. 数字冰雹警务情指一体合成作战指挥决策系统
  19. Python学习_3(数据类型)
  20. 好东西,iOS 16.5+ 系统加速工具,不用越狱也行

热门文章

  1. matlab实用教程第2版 答案,MATLAB实用教程(第2版)
  2. java字典初始化_在Java中用一组特定的数据初始化字典
  3. Vim中如何替换字符串
  4. 过年回家,程序员最怕的 5 件事
  5. PADS学习3(绘制原理图)
  6. Android Content Providers(三)——Contacts Provider
  7. Java新手小白入门篇 JDK安装及环境变量配置(超详细)
  8. linux命令ps -ef输出结果,Linux ps 命令
  9. 2010计算机一级选择题,2010年计算机一级考试选择题题库(最新版)
  10. 5GNR漫谈10:5G RF射频设计验证