ElasticSearch评分分析 explian 解释和一些查询理解

按照es-ik分析器安装了ik分词器。创建索引:PUT /index_ik_test。索引包含2个字段:content和nick,如下:

GET index_ik_test/_mapping
{
"index_ik_test": {
"mappings": {
"fulltext": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word"
},
"nick": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}

实验环境为:单台的ElasticSearch6.3.2版本。索引配置如下:

GET index_ik_test/_settings
{
"index_ik_test": {
"settings": {
"index": {
"creation_date": "1533383757075",
"number_of_shards": "5",
"number_of_replicas": "1",
"uuid": "JajsYmAIT0-uhm-L5xKbeA",
"version": {
"created": "6030299"
},
"provided_name": "index_ik_test"
}
}
}
}

由此可知,ElasticSearch创建索引时,默认为5个primary shard,每个primary shard 一个replica。

在Kibana的Monitoring界面查看:有5个primary shard。其中有5个尚未分配的副本:

为什么有5个尚未分配的副本呢?因为是单节点的ElasticSearch,索引 index_ik_test 的每个primary shard 都有一个副本,而primary shard 与副本 不能在同一台机器上,由于一共有5个primary shard,故存在着5个尚未分配的副本。

该索引一共存储着5篇文档,

GET index_ik_test/fulltext/_search
{
"query": {
"match_all": {}
}
}

查询文档如下:

这5篇文档中有三篇文档(文档id为 5、4、3)包含了 词 “中国”。由于采用的ik_max_word分词,因此“其中国家投资了500万”,是包含“中国”这个词的。

每个分片中存储的文档如下:

其中,shard2代表 分片:[index_ik_test][2] ,shard2上存储着 doc id为 4和6 的两篇文档。shard1 代表分片:[index_ik_test][1],shard1上存储着 文档id为 5 的一篇文档。其它分片存储的文档以此类推。(是不是很奇怪我是怎么知道每个分片上存储具体哪篇文档的?这是因为:在这个演示环境中,文档数量少,我是通过不同的查询词(比如 通过 query explian "我们",就能知道 doc_6 存储在shard2上了)进行explian查询测试得到的。哈哈,知道这个主要是为了后面的 idf 计算分析)

下面以词 "中国" 为例 来解释:query explian。执行:

GET index_ik_test/fulltext/_search
{
"explain": true,
"query": {
"match": {
"content": "中国"
}
}
}

下面从该命令的执行结果详细分析:

{

"took": 1,

"timed_out": false,

"_shards": {

"total": 5,

"successful": 5,

"skipped": 0,

"failed": 0

},

"hits": {

"total": 3,

"max_score": 0.5480699,

这表明,查询请求 scatter 到了所有的 shard (5个shard),其中有3个shard “命中了” 查询词 “中国”。这3个shard如下:

"_shard": "[index_ik_test][2]"
"_shard": "[index_ik_test][1]"
"_shard": "[index_ik_test][4]"

每个shard都会计算一个score,这3个shard中,得分最大的分片是shard2 [index_ik_test][2],它的score是:0.5480699。因此,shard2上的返回结果排在了最前面,只是这里有个小疑问,为什么score返回结果是取最大值(max_score)?

对于 shard[index_ik_test][2]

    "_shard": "[index_ik_test][2]",
"_node": "7MyDkEDrRj2RPHCPoaWveQ",
"_index": "index_ik_test",
"_type": "fulltext",
"_id": "4",
"_score": 0.5480699,
"_source": {
"content": "中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
},

文档id 4 存储在shard2 上。该文档针对查询字符串 “中国” 计算出来的得分是0.5480699。具体的计算细节如下:

   "_explanation": {
"value": 0.5480699,
"description": "weight(content:中国 in 0) [PerFieldSimilarity], result of:",
"details": [
{
"value": 0.5480699,
"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": 1,
"description": "docFreq",
"details": []
},
{
"value": 2,
"description": "docCount",
"details": []
}
]
},
{
"value": 0.7906977,
"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": 8.5,
"description": "avgFieldLength",
"details": []
},
{
"value": 14,
"description": "fieldLength",
"details": []
}
]
}
]
}
]
}

0.5480699 由idf 乘以 tfNorm 计算得到。其中 idf=0.6931472,tfNorm=0.7906977

  • idf

    idf由公式 log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5))计算得出。其中,docFreq=1,docCount=2,因为如上面图所示 :在shard2上,一共有2篇文档,因此docCount为2,其中只有文档id为4的这篇文档包含 "中国" 这个词,也即:词 "中国" 出现 在了一篇文档中,因此docFreq=1。

    不信的话就亲自动手算算看。~

    我这里有疑问的地方是:这里的 idf 计算公式与官网提到的计算公式有一点不一样:

后来发现,在ElasticSearch6.3版本之后,字段评分算法默认是BM25算法了。

Elasticsearch allows you to configure a scoring algorithm or similarity per field. The similarity setting provides a simple way of choosing a similarity algorithm other than the default BM25, such as TF/IDF.

BM25算法的官方文档API中发现IDF的计算公式如下:

这样也就知道了,ElasticSearch在计算Term的字段得分是,采用的是BM25算法。计算出该term在这个字段中的idf值后,再结合其他因子(比如tf、字段长度Normalization、文档长度Normalization)最终得出文档的Score。

那么tf-idf与BM25的区别是什么?tf-idf是一个term scoring method,而BM25是:给定一个查询字符串,计算该查询字符串与文档之间的得分的一种方法。文档是由一个个的term组成的,计算文档得分需要计算文档中term的得分。将tf-idf结合余弦相似度就是另外一种计算查询字符串与文档之间的得分的一种方法。

BM25 is more than a term scoring method, but rather a method for scoring documents with relation to a query. Tf-idf is a term scoring method, which can be incorporated in a document scoring method using a similarity measure (say cosine).

并且BM25的理论基础是probabilistic retrieval model,而tf-idf的理论基础是 vector space model

docFreq=1表示:"中国"这个词 只在 一篇文档中出现了。

https://lucene.apache.org/core/7_4_0/core/org/apache/lucene/search/similarities/TFIDFSimilarity.html

docFreq (the number of documents in which the term t appears)

docFreq - the number of documents which contain the term

docCount - the total number of documents in the collection

dcoCount=2表示:分片[index_ik_test][2] 里面一共存储了2篇文档(即doc_4 和 doc_6)。

  • tfNorm

    tfNorm由公式(freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength))计算。

    1. freq ,即termFreq,应该是:term 在该分片下的所有文档中出现的频率。在这里,“中国” 在 shard2 的两篇文档中,只出现过一次

    2. k1 ,这个参数很有意思,默认值为1.2,是用来 平衡 词频termFreq 对评分的影响。在传统的TF评分计算过程中,termFreq越大,计算出来的评分就越大。但是当termFreq大到一定程度时,一般是那种常用词(或者叫stop words),而这种词会干扰文档的评分,因此引入参数 k1 惩罚 termFreq 对评分的影响。要想了解更多,可参考这篇文章:bm25-the-next-generation-of-lucene-relevation。这里也说明,ElasticSearch6.3.2中已经采用了BM25算法作为相关性得分计算公式了。

      https://www.desmos.com/calculator/g8zoebbtvs

    3. b,从tfNorm公式可看出:用来调节字段长度对评分的影响。

    4. avgFieldLength 值为8.5。为什么是8.5呢?

      在我们的示例中,shard2 [index_ik_test][2]中一共存储了2篇文档,一篇是doc_4,它的content字段就是"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"。另一篇是doc_6,它的content字段是"我们的国家"。

      对doc_6的content字段进行分析:

      GET index_ik_test/_analyze
      {
      "text": ["我们的国家"],
      "analyzer": "ik_max_word"
      }
      得到的各个 token 如下:
      我们、的、国家
      一共3个token

      在下面的第五点fieldLength中,对doc_4的content字段进行分析得到 14个token。

      因此,avgFieldLength = (14+3)/2=8.5。14 是doc_4 content字段分词之后的token数目;3是doc_6 content字段分词之后的token数目;2 代表:有两篇文档。

      由此可知:avgFieldLength 应该是:shard2分片中 content字段下所有内容 经过 ik_max_word 分词后的token 总数 除以 shard2里面的文档数目。

    5. fieldLength,长度为14。这个是doc_4 “中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首” ik_max_word分词之后的长度。因此,fieldLength指的是 查询字段(content字段) 被分析(创建索引时指定了 ik_max_word分析器) 之后 的长度。

      GET index_ik_test/_analyze
      {
      "text": ["中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"],
      "analyzer": "ik_max_word"
      } 得到的各个token如下:
      中国、驻、洛杉矶、领事馆、领事、馆、遭、亚裔、男子、子枪、枪击、嫌犯、已、自首
      一共 14 个token

      各个参数的值以及计算过程如下:

    freq=1.0

    k1=1.2

    b=0.75

    avgFieldLength=8.5

    fieldLength=14

    (freq*(k1+1))/(freq+k1*(1-b+b*fieldLength/avgFieldLength))

    0.7906976744186047

    现在,针对shard2,我们已经详细分析了 tfNorm 和 idf 这两个参数的计算结果。最终,shard2上的查询得分为 tfNorm*idf=0.7906977*0.6931472=0.5480699 。另外两个命中 “中国” 的分片的得分计算也类似,就不说了。

由此可看出:ElasticSearch中 tf-idf 的值 是根据单个分片来计算的,也即:以单个的shard为单位来计算 score,更具体地说:当我们讲 某 term 一共在 文档集合中出现了多少次?这个文档集合指的是:单个分片上存储的所有文档。为什么是统计单个分片上的文档/term 数量呢?这个就要从ElasticSearch的索引方式说起了。这里就简单地提一下,毕竟这不是本文的重点。

ElasticSearch中有两个不同Level的索引,一个是:文档到分片 这个级别的索引,它讲的是 数据的分布方式,即决定把哪篇文档存储在哪个分片上,这是通过hash文档ID的方式来实现的。采用hash方式的好处是,ElasticSearch不需要维护文档的位置信息(boundary),文档能够均匀地分布在各个shard上。ElasticSearch采用的哈希函数是:murmur3

另一个级别的索引是:term 到 文档的索引,俗称倒排索引,又称为:Secondary index。因为我们的查询需求并不是:给定一个docId,返回这个docId所代表的文档内容。我们的查询需求是:给定一个 查询关键词,找出哪些docId 包含了这个 查询关键词。因此,要完成这个查询,第一步是要知道 有哪些docId 包含了 查询关键词;第二步则是:根据docId,拿到相应的文档内容。

当文档的数量很多很多时,一台机器或者说一个shard都存储不下这个倒排索引了,因此需要对倒排索引进行分割(partition)。一种分割方式是:Secondary index by Document,另一种是:Secondary index by Term。

  • Secondary index by Document的示意图如下:(这里的Partition 可以与 ElasticSearch中的 shard 概念等价)

​ 这种Secondary Index的分布方式(或者叫数据分布方式,这里的数据当然是倒排索引数据了)是针对每个Partition上的文档建立一个独立的Secondary Index(倒排索引)。这种索引方式的好处是:当写入/更新文档时,只涉及到该Partition中的倒排索引,而不会修改其他的Partition中的倒排索引内容。

更具体地,以ElasticSearch举例,因为ElasticSearch就是采用Secondary index by Document。当创建索引时,是默认5个Primary shard,每个Primary shard 一个副本(replica)。Primary shard 就相当于这里的Partition概念。当向ElasticSearch的索引中写入文档时,写请求是请求给某个Primary shard,然后在该Primary shard上构建 倒排索引(posting list),而并不需要修改 其他4个Primary shard 中的倒排索引内容。

each partition is completely separate: each partition maintains its own secondary indexes, covering only the documents in that partition. It doesn’t care what data is stored in other partitions.

​ 因此,查询的时候,需要将查询请求发送到每一个partition(shard)。为什么呢?因为当我们查询的时候,一般是输入某个词进行查询,比如输入"中国"进行查询,而由于Elasticsearch采用 secondary index by document 这种方式,各个shard 维护着自己的 secondary index,比如,在文档1 中 包含了 "中国" 这个词,而文档1 被哈希分片到 shard1中存储;文档2也包含了"中国" 这个词,但是文档2可能被哈希到另外一个shard,比如shard2上存储……因此,所有包含"中国" 这个词的文档 可能分布在 Elasticsearch的所有分片中,因此查询请求需要分发到每个shard上去,这就是所谓的Scatter 查询。当然了,由于primary shard可以设置若干个 replica,因此,将查询请求分发到 replica上,通过 replica 来扛 大量的查询请求。毕竟 index操作(将文档写入Elasticsearch)是由primary shard处理的,那将查询请求交由 replica处理,一定程度上缓解了primary shard的压力。

  • Secondary index by Term示意图如下:

​ 这种Secondary index的分布方式 是按”范围“ 来进行分布,关于数据的分布方式,可参考:[分布式系统原理介绍。比如说,对于 color 这个字段,颜色有 black、red、silver……文档中 颜色范围首字母为 [a-r] 的那些docId 存储在 Partition0 分片上。而所有 颜色范围首字母 [s-z] 的docId,则存储在Partition1分片上。 采用这种分布方式的倒排索引,一篇文档中的不同字段 可能会在 多个Partition的字段中被索引。比如,文档893 的color 字段的内容是 silver,它在Partition1中被索引了;而文档893的make字段内容是Audi,它在Partition0中被索引了。这种索引方式的缺点显而易见:当更新/插入一篇文档时,有可能需要更新多个Partition中的倒排索引内容。 因此,查询的时候,可以只将查询请求发送到某个特定的partition(shard)。

以上内容,全是自己的理解。可能会有很多不严谨的地方。

补充说明

这篇文章记录的文档得分计算比较简单:1,它只涉及到 单个字段查询,即只查询content字段;2,查询字符串只有一个term,即:“中国”。

而现实中的查询,查询字符串可能包含多个term,并且针对索引中的多个字段查询。因此,文档得分的计算要复杂得多。

参考文献:

最后,放一份完整的explian 查询分析 返回内容:

{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 0.5480699,
"hits": [
{
"_shard": "[index_ik_test][2]",
"_node": "7MyDkEDrRj2RPHCPoaWveQ",
"_index": "index_ik_test",
"_type": "fulltext",
"_id": "4",
"_score": 0.5480699,
"_source": {
"content": "中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
},
"_explanation": {
"value": 0.5480699,
"description": "weight(content:中国 in 0) [PerFieldSimilarity], result of:",
"details": [
{
"value": 0.5480699,
"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": 1,
"description": "docFreq",
"details": []
},
{
"value": 2,
"description": "docCount",
"details": []
}
]
},
{
"value": 0.7906977,
"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": 8.5,
"description": "avgFieldLength",
"details": []
},
{
"value": 14,
"description": "fieldLength",
"details": []
}
]
}
]
}
]
}
},
{
"_shard": "[index_ik_test][1]",
"_node": "7MyDkEDrRj2RPHCPoaWveQ",
"_index": "index_ik_test",
"_type": "fulltext",
"_id": "5",
"_score": 0.2876821,
"_source": {
"content": "其中国家投资了500万"
},
"_explanation": {
"value": 0.2876821,
"description": "weight(content:中国 in 0) [PerFieldSimilarity], result of:",
"details": [
{
"value": 0.2876821,
"description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
"details": [
{
"value": 0.2876821,
"description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
"details": [
{
"value": 1,
"description": "docFreq",
"details": []
},
{
"value": 1,
"description": "docCount",
"details": []
}
]
},
{
"value": 1,
"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": 7,
"description": "avgFieldLength",
"details": []
},
{
"value": 7,
"description": "fieldLength",
"details": []
}
]
}
]
}
]
}
},
{
"_shard": "[index_ik_test][4]",
"_node": "7MyDkEDrRj2RPHCPoaWveQ",
"_index": "index_ik_test",
"_type": "fulltext",
"_id": "3",
"_score": 0.2876821,
"_source": {
"content": "中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"
},
"_explanation": {
"value": 0.2876821,
"description": "weight(content:中国 in 0) [PerFieldSimilarity], result of:",
"details": [
{
"value": 0.2876821,
"description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
"details": [
{
"value": 0.2876821,
"description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
"details": [
{
"value": 1,
"description": "docFreq",
"details": []
},
{
"value": 1,
"description": "docCount",
"details": []
}
]
},
{
"value": 1,
"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": 14,
"description": "avgFieldLength",
"details": []
},
{
"value": 14,
"description": "fieldLength",
"details": []
}
]
}
]
}
]
}
}
]
}
}

原文:https://www.cnblogs.com/hapjin/p/9677753.html

ElasticSearch评分分析 explian 解释和一些查询理解的更多相关文章

  1. Elasticsearch搜索之explain评分分析

    Lucene的IndexSearcher提供一个explain方法,能够解释Document的Score是怎么得来的,具体每一部分的得分都可以详细地打印出来.这里用一个中文实例来纯手工验算一遍Luce ...

  2. ElasticSearch 评分排序

    背景 通过脚本改变评分 背景 近期有一个需求,需要对优惠券可用商品列表加个排序,只针对面值类的券不包括折扣券. 需求是这样的,假设有一张面值券 50 块钱,可用商品列表 A 100.B 40.C 10 ...

  3. Solr4.8.0源码分析(6)之非排序查询

    Solr4.8.0源码分析(6)之非排序查询 上篇文章简单介绍了Solr的查询流程,本文开始将详细介绍下查询的细节.查询主要分为排序查询和非排序查询,由于两者走的是两个分支,所以本文先介绍下非排序的查 ...

  4. 数据分析 - 美国金融科技公司Prosper的风险评分分析

    数据分析 - 美国金融科技公司Prosper的风险评分分析 今年Reinhard Hsu觉得最有意思的事情,是参加了拍拍贷第二届魔镜杯互联网金融数据应用大赛.通过"富爸爸队",认识 ...

  5. ElasticSearch聚合分析

    聚合用于分析查询结果集的统计指标,我们以观看日志分析为例,介绍各种常用的ElasticSearch聚合操作. 目录: 查询用户观看视频数和观看时长 聚合分页器 查询视频uv 单个视频uv 批量查询视频 ...

  6. Elasticsearch日志分析系统

    Elasticsearch日志分析系统 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是Elasticsearch 一个采用Restful API标准的高扩展性的和高可用性 ...

  7. ElasticSearch入门 第九篇:实现正则表达式查询的思路

    这是ElasticSearch 2.4 版本系列的第九篇: ElasticSearch入门 第一篇:Windows下安装ElasticSearch ElasticSearch入门 第二篇:集群配置 E ...

  8. DB2 锁问题分析与解释

    DB2 锁问题分析与解释 DB2 应用中常常会遇到锁超时与死锁现象,那么这样的现象产生的原因是什么呢.本文以试验的形式模拟锁等待.锁超时.死锁现象.并给出这些现象的根本原因. 试验环境: DB2 v9 ...

  9. elasticsearch中的mapping映射配置与查询典型案例

    elasticsearch中的mapping映射配置与查询典型案例 elasticsearch中的mapping映射配置示例比如要搭建个中文新闻信息的搜索引擎,新闻有"标题".&q ...

随机推荐

  1. 【笔记】基于Python的数字图像处理

    [博客导航] [Python相关] 前言 基于Python的数字图像处理,离不开相关处理的第三方库函数.搜索网络资源,列出如下资源链接. Python图像处理库到底用哪家 python计算机视觉编程— ...

  2. git-将dev代码合并到test

    1. 在dev分支上刚开发完项目,执行以下命令: git add git commit -m 'dev' git push -u origin dev 2.切换到test分支上 如果是多人开发,先把远 ...

  3. (十一)Updating Documents

    In addition to being able to index and replace documents, we can also update documents. Note though ...

  4. 第一个Jsp页面,基于普元EOS

    跨界了.搞一下Jsp,实现MES系统看板一处.

  5. docker私有镜像仓库搭建

    环境:centos7,dockere版本:18.09.0,镜像仓库:v2 docker-registry:192.168.137.101   docker私有仓库服务器 docker-app: 192 ...

  6. 小程序——阿里服务器配置https及什么是IIS

    1.申请域名:阿里云 2.免费开启SSL证书:管理=>免费开启SSL证书>单域名>dev.xxx.top 3.配置服务器:下载=>IIS7证书 *注册一个域名,可以免费开启一个 ...

  7. PHP中的DateTime类

    DataTime类跟date(),strtotime(),gmdate()等函数有相同的作用,都是用来处理日期和时间的,但DateTime类更加直观.方便, 所以在PHP5.2.0以后推荐使用Date ...

  8. sql 书写 规范 优化

    规范 做注解  便于修改和优化  规范 <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE map ...

  9. Shell命令-文件压缩解压缩之tar、unzip

    文件及内容处理 - tar.unip 1.tar:打包压缩命令 tar命令的功能说明 tar 命令常用语用于备份文件,tar 是用来建立,还原备份文件的工具程序,它可以加入,解开备份文件内的文件 ta ...

  10. MySQL系列:数据库基本操作(1)

    1. 登录数据库 mysql -h localhost -u root -p 2. 数据库基本操作 2.1 查看数据库 mysql> SHOW DATABASES; +------------- ...