Elasticsearches打分机制讲解

目录
  • 一 例子
  • 二 文档打分的运作机制:TF-IDF
    • 2.1 词频:TF
    • 2.2 逆文档频率:IDF
  • 三 Lucene评分公式
  • 四 其他的打分方法
  • 五 配置打分模型
    • 5.1 简要配置BM25打分模型
    • 5.2 为BM25配置高级的settings
    • 5.3 配置全局打分模型
  • 六 boosting
    • 6.1 索引期间的boosting
    • 6.2 查询期间的boosting
    • 6.3 跨越多个字段的查询
  • 七 使用“解释”来理解文档是如何评分的

一 例子

现在,讲述一个真实的故事!

故事一定是伴随着赵忠祥老师的声音开始的,雨季就要来临了,又到了动物们发情的季节了...

还记得,之前发生的作家六六吐槽xx的事情吗?对了,有图有真相!上图上图:

身为吃瓜群众,要从专业的角度来分析,就事论事哈:

就搜索结果本身而言,xx返回了正确的结果(是的,人家已经调整了,现在搜没问题!)。因为返回的结果中,都包含了搜索的关键字。而我们从逻辑上来看,这他娘的一堆广告算是咋回事!这个吐槽是从用户的角度出发的。很显然,返回的结果中,尤其是前几条,有时甚至是前几页,都跟我们想要的结果相差深远!

进一步说,仅仅以二元的方式来考虑文档和查询的匹配可能是有意义的,也就是百度搜索引擎返回了二元的匹配结果:是的,找到了,不,老娘没找到!虽然返回了结果,其中也包含了我们想要的结果,即便你要在大堆的广告中找正确的结果实属不易,但就像大家都习惯了广告中插播电视剧一样,习惯就好嘛!xx从x的角度出发,为广告的词条增加权重,至于那个真正的结果,我擦,你也没给我钱........

而需要xx才能访问的xx浏览器,在正确的给用户返回二元结果之前,更多的考虑文档的相关性(relevancy),因为就某个结果而言,如果A文档要比B文档更和结果相关,那么A文档在结果中就要比B文档靠前,再加上以其他的优化,最终将所有结果返回,而用户最期待的那条结果很可能排在最高位,这岂不美哉?

确定文档和查询有多么相关的过程被称为打分(scoring)。

二 文档打分的运作机制:TF-IDF

Lucene和es的打分机制是一个公式。将查询作为输入,使用不同的手段来确定每一篇文档的得分,将每一个因素最后通过公式综合起来,返回该文档的最终得分。这个综合考量的过程,就是我们希望相关的文档被优先返回的考量过程。在Lucene和es中这种相关性称为得分。

在开始计算得分之前,es使用了被搜索词条的频率和它有多常见来影响得分,从两个方面理解:

  • 一个词条在某篇文档中出现的次数越多,该文档就越相关。
  • 一个词条如果在不同的文档中出现的次数越多,它就越不相关!

我们称之为TF-IDF,TF是词频(term frequency),而IDF是逆文档频率(inverse document frequency)。

2.1 词频:TF

考虑一篇文档得分的首要方式,是查看一个词条在文档中出现的次数,比如某篇文章围绕es的打分展开的,那么文章中肯定会多次出现相关字眼,当查询时,我们认为该篇文档更符合,所以,这篇文档的得分会更高。

闲的蛋疼的可以Ctrl + f搜一下相关的关键词(es,得分、打分)之类的试试。

2.2 逆文档频率:IDF

相对于词频,逆文档频率稍显复杂,如果一个词条在索引中的不同文档中出现的次数越多,那么它就越不重要。

来个例子,示例:

The rules-which require employees to work from 9 am to 9 pm
In the weeks that followed the creation of 996.ICU in March
The 996.ICU page was soon blocked on multiple platforms including the messaging tool WeChat and the UC Browser.

假如es索引中,有上述3篇文档:

  • 词条ICU的文档频率是2,因为它出现在2篇文档中,文档的逆源自得分乘以1/DF,DF是该词条的文档频率,这就意味着,由于ICU词条拥有更高的文档频率,所以,它的权重会降低。
  • 词条the的文档频率是3,它在3篇文档中都出现了,注意:尽管the在后两篇文档出都出现两次,但是它的词频是还是3,因为,逆文档词频只检查词条是否出现在某篇文档中,而不检查它在这篇文档中出现了多少次,那是词频该干的事儿。

逆文档词频是一个重要的因素,用来平衡词条的词频。比如我们搜索the 996.ICU。单词the几乎出现在所有的文档中(中文中比如的“的”),如果这个鬼东西要不被均衡一下,那么the的频率将完全淹没996.ICU。所以,逆文档词频就有效的均衡了the这个常见词的相关性影响。以达到实际的相关性得分将会对查询的词条有一个更准确地描述。

当词频和逆文档词频计算完成。就可以使用TF-IDF公式来计算文档的得分了。

三 Lucene评分公式

之前的讨论Lucene默认评分公式被称为TF-IDF,一个基于词频和逆文档词频的公式。Lucene实用评分公式如下:

你以为我会着重介绍这个该死的公式?!

我只能说,词条的词频越高,得分越高;相似地,索引中词条越罕见,逆文档频率越高,其中再加商调和因子和查询标准化,调和因子考虑了搜索过多少文档以及发现了多少词条;

查询标准化,是试图让不同的查询结果具有可比性,这显然.....很困难。

我们称这种默认的打分方法是TF-IDF和向量空间模型(vector space model)的结合。

四 其他的打分方法

除了TF-IDF结合向量空间模型的实用评分模式,是es和Lucene最为主流的评分机制,但这并不是唯一的,除了TF-IDF这种实用模型之外,其他的模型包括:

  • Okapi BM25。
  • 随机性分歧(Divergence from randomness),即DFR相似度。
  • LM Dirichlet相似度。
  • LM Jelinek Mercer相似度。

这里简要的介绍BM25几种主要设置,即k1、b和discount_overlaps:

  • k1和b是数值的设置,用于调整得分是如何计算的。
  • k1控制对于得分而言词频(TF)的重要性。
  • b是介于0 ~ 1之间的数值,它控制了文档篇幅对于得分的影响程度。
  • 默认情况下,k1设置为1.2,而b则被设置为0.75
  • discount_overlaps的设置用于告诉es,在某个字段中,多少个分词出现在同一位置,是否应该影响长度的标准化,默认值是true。

五 配置打分模型

5.1 简要配置BM25打分模型

BM25(是不是跟pm2.5好像!!!)是一种基于概率的打分框架。我们来简要的配置一下:

PUT w2
{
  "mappings": {
    "doc": {
      "properties": {
        "title": {
          "type": "text",
          "similarity": "BM25"
        }
      }
    }
  }
}
PUT w2/doc/1
{
  "title":"The rules-which require employees to work from 9 am to 9 pm"
}
PUT w2/doc/2
{
  "title":"In the weeks that followed the creation of 996.ICU in March"
}
PUT w2/doc/3
{
  "title":"The 996.ICU page was soon blocked on multiple platforms including the messaging tool WeChat and the UC Browser."
}
GET w2/doc/_search
{
  "query": {
    "match": {
      "title": "the 996"
    }
  }
}

上例是通过similarity参数来指定打分模型。至于查询,还是当数据量比较大的时候,多试几次,比较容易发现不同之处。

5.2 为BM25配置高级的settings

PUT w3
{
  "settings": {
    "index": {
      "analysis": {
        "analyzer":"ik_smart"
      }
    },
    "similarity": {
      "my_custom_similarity": {
        "type": "BM25",
        "k1": 1.2,
        "b": 0.75,
        "discount_overlaps": false
      }
    }
  },
  "mappings": {
    "doc": {
      "properties": {
        "title": {
          "type": "text",
          "similarity":"my_custom_similarity"
        }
      }
    }
  }
}
PUT w3/doc/1
{
  "title":"The rules-which require employees to work from 9 am to 9 pm"
}
PUT w3/doc/2
{
  "title":"In the weeks that followed the creation of 996.ICU in March"
}
PUT w3/doc/3
{
  "title":"The 996.ICU page was soon blocked on multiple platforms including the messaging tool WeChat and the UC Browser."
}
GET w3/doc/_search
{
  "query": {
    "match": {
      "title": "the 996"
    }
  }
}

5.3 配置全局打分模型

如果我们要使用某种特定的打分模型,并且希望应用到全局,那么就在elasticsearch.yml配置文件中加入:

index.similarity.default.type: BM25

六 boosting

boosting是一个用来修改文档相关性的程序。boosting有两种类型:

  • 索引的时候,比如我们在定义mappings的时候。
  • 查询一篇文档的时候。

以上两种方式都可以提升一个篇文档的得分。需要注意的是:在索引期间修改的文档boosting是存储在索引中的,要想修改boosting必须重新索引该篇文档。

6.1 索引期间的boosting

啥也不说了,都在酒里!上代码:

PUT w4
{
  "mappings": {
    "doc": {
      "properties": {
        "name": {
          "boost": 2.0,
          "type": "text"
        },
        "age": {
          "type": "long"
        }
      }
    }
  }
}

一劳永逸是没错,但一般不推荐这么玩。

原因之一是因为一旦映射建立完成,那么所有name字段都会自动拥有一个boost值。要想修改这个值,那就必须重新索引文档。

另一个原因是,boost值是以降低精度的数值存储在Lucene内部的索引结构中。只有一个字节用于存储浮点型数值(存不下就损失精度了),所以,计算文档的最终得分时可能会损失精度。

最后,boost是应用与词条的。因此,再被boost的字段中如果匹配上了多个词条,就意味着计算多次的boost,这将会进一步增加字段的权重,可能会影响最终的文档得分。

现在我们再来介绍另一种方式。

6.2 查询期间的boosting

es中,几乎所有的查询类型都支持boost,正如你想象的那些match、multi_match等等。

来个示例,在查询期间,使用match查询进行boosting:

PUT w5
{
  "mappings":{
    "doc":{
      "properties": {
        "title": {
          "type": "text",
          "analyzer": "ik_max_word"
        },
        "content": {
          "type": "text",
          "analyzer": "ik_max_word"
        }
      }
    }
  }
}
PUT w5/doc/1
{
  "title":"Lucene is cool",
  "content": "Lucene is cool"
}
PUT w5/doc/2
{
  "title":"Elasticsearch builds on top of lucene",
  "content":"Elasticsearch builds on top of lucene"
}
PUT w5/doc/3
{
  "title":"Elasticsearch rocks",
  "content":"Elasticsearch rocks"
}

来查询:

GET w5/doc/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title":{
              "query": "elasticserach rocks",
              "boost": 2.5
            }
          }
        },
        {
          "match": {
            "content": "elasticserach rocks"
          }
        }
      ]
    }
  }
}

就对于最终得分而言,content字段,加了boost的title查询更有影响力。也只有在bool查询中,boost更有意义。

6.3 跨越多个字段的查询

boost也可以用于multi_match查询。

GET w5/doc/_search
{
  "query": {
    "multi_match": {
      "query": "elasticserach rocks",
      "fields": ["title", "content"],
      "boost": 2.5
    }
  }
}

除此之外,我们还可以使用特殊的语法,只为特定的字段指定一个boost。通过在字段名称后添加一个^符号和boost的值。告诉es只需对那个字段进行boost:

GET w5/doc/_search
{
  "query": {
    "multi_match": {
      "query": "elasticserach rocks",
      "fields": ["title^3", "content"]
    }
  }
}

上例中,title字段被boost了3倍。

需要注意的是:在使用boost的时候,无论是字段或者词条,都是按照相对值来boost的,而不是乘以乘数。

如果对于所有的待搜索词条boost了同样的值,那么就好像没有boost一样(废话,就像大家都同时长高一米似的)!因为Lucene会标准化boost的值。

如果boost一个字段4倍,不是意味着该字段的得分就是乘以4的结果。所以,如果你的得分不是按照严格的乘法结果,也不要担心。

七 使用“解释”来理解文档是如何评分的

一切都不是你想的那样!是的,在es中,一个文档要比另一个文档更符合某个查询很可能跟我们想象的不太一样!

这一小节,我们来研究下es和Lucene内部使用了怎样的公式来计算得分。

我们通过explain=true来告诉es,你要给洒家解释一下为什么这个得分是这样的?!背后到底以有什么py交易!

比如我们来查询:

GET py1/doc/_search
{
  "query": {
    "match": {
      "title": "北京"
    }
  },
  "explain": true,
  "_source": "title",
  "size": 1
}

由于结果太长,我们这里对结果进行了过滤("size": 1返回一篇文档),只查看指定的字段("_source": "title"只返回title字段)。

看结果:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 24,
    "max_score" : 4.9223156,
    "hits" : [
      {
        "_shard" : "[py1][1]",
        "_node" : "NRwiP9PLRFCTJA7w3H9eqA",
        "_index" : "py1",
        "_type" : "doc",
        "_id" : "NIjS1mkBuoj17MYtV-dX",
        "_score" : 4.9223156,
        "_source" : {
          "title" : "大写的尴尬 插混为啥在北京不受待见?"
        },
        "_explanation" : {
          "value" : 4.9223156,
          "description" : "weight(title:北京 in 36) [PerFieldSimilarity], result of:",
          "details" : [
            {
              "value" : 4.9223156,
              "description" : "score(doc=36,freq=1.0 = termFreq=1.0\n), product of:",
              "details" : [
                {
                  "value" : 4.562031,
                  "description" : "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                  "details" : [
                    {
                      "value" : 4.0,
                      "description" : "docFreq",
                      "details" : [ ]
                    },
                    {
                      "value" : 430.0,
                      "description" : "docCount",
                      "details" : [ ]
                    }
                  ]
                },
                {
                  "value" : 1.0789746,
                  "description" : "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                  "details" : [
                    {
                      "value" : 1.0,
                      "description" : "termFreq=1.0",
                      "details" : [ ]
                    },
                    {
                      "value" : 1.2,
                      "description" : "parameter k1",
                      "details" : [ ]
                    },
                    {
                      "value" : 0.75,
                      "description" : "parameter b",
                      "details" : [ ]
                    },
                    {
                      "value" : 12.1790695,
                      "description" : "avgFieldLength",
                      "details" : [ ]
                    },
                    {
                      "value" : 10.0,
                      "description" : "fieldLength",
                      "details" : [ ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      }
    ]
  }
}

在新增的_explanation字段中,可以看到value值是4.9223156,那么是怎么算出来的呢?

来分析,分词“北京”在描述字段(title)出现1次,所以TF的综合得分经过"description" : "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:"计算,得分是1.0789746。

那么逆文档词频呢?根据"description" : "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:"计算得分是4.562031。

所以最终得分是:

1.0789746 * 4.562031 = 4.9223155734126

结果在四舍五入后就是4.9223156。

需要注意的是,explain的特性会给es带来额外的性能开销。所以,除了在调试时可以使用,生产环境下,应避免使用explain。

以上就是Elasticsearches打分机制讲解的详细内容,更多关于Elasticsearches打分机制的资料请关注我们其它相关文章!

(0)

相关推荐

  • Elasticsearches的集群搭建及数据分片过程详解

    目录 Elasticsearch高级之集群搭建,数据分片 广播方式 单播方式 选取主节点 什么是脑裂 错误识别 Elasticsearch高级之集群搭建,数据分片 es使用两种不同的方式来发现对方: 广播 单播 也可以同时使用两者,但默认的广播,单播需要已知节点列表来完成 广播方式 当es实例启动的时候,它发送了广播的ping请求到地址224.2.2.4:54328.而其他的es实例使用同样的集群名称响应了这个请求. 一般这个默认的集群名称就是上面的cluster_name对应的elastics

  • Django集成搜索引擎Elasticserach的方法示例

    1.背景 当用户在搜索框输入关键字后,我们要为用户提供相关的搜索结果.可以选择使用模糊查询 like 关键字实现,但是 like 关键字的效率极低.查询需要在多个字段中进行,使用 like 关键字也不方便,另外分词的效果也不理想. 全文检索方案 全文检索即在指定的任意字段中进行检索查询. 全文检索方案需要配合搜索引擎来实现. 搜索引擎原理 搜索引擎 进行全文检索时,会对数据库中的数据进行一遍预处理,单独建立起一份 索引结构数据 . 索引结构数据 类似字典的索引检索页 ,里面包含了关键词与词条的对

  • Elasticsearches通过坐标位置实现对附近人的搜索

    目录 一 创建mapping 二 导入数据 三 查询 3.1根据给定两个点组成的矩形,查询矩形内的点 3.2根据给定的多个点组成的多边形,查询范围内的点 3.3查询给定1000KM距离范围内的点 3.4查询距离范围区间内的点的数量 一 创建mapping PUT test { "mappings": { "test":{ "properties": { "location":{ "type": "

  • go语言实现Elasticsearches批量修改查询及发送MQ操作示例

    目录 update_by_query批量修改 索引添加字段 查询es发送MQ update_by_query批量修改 POST post-v1_1-2021.02,post-v1_1-2021.03,post-v1_1-2021.04/_update_by_query { "query": { "bool": { "must": [ { "term": { "join_field": { "val

  • python中的Elasticsearch操作汇总

    这篇文章主要介绍了python中的Elasticsearch操作汇总,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 导入包 from elasticsearch import Elasticsearch 本地连接 es = Elasticsearch(['127.0.0.1:9200']) 创建索引 es.indices.create(index="python_es01",ignore=400) ingore=400 ingore是

  • Elasticsearches之python使用及Django与Flask集成示例

    目录 Elasticsearch之Python使用 Elasticsearch之Django/Flask集成 elasticsearch-dsl django集成 Elasticsearch之Python使用 from elasticsearch import Elasticsearch obj = Elasticsearch() # 创建索引(Index) result = obj.indices.create(index='user', body={"userid":'1','us

  • python 使用elasticsearch 实现翻页的三种方式

    使用ES做搜索引擎拉取数据的时候,如果数据量太大,通过传统的from + size的方式并不能获取所有的数据(默认最大记录数10000),因为随着页数的增加,会消耗大量的内存,导致ES集群不稳定.因此延伸出了scroll,search_after等翻页方式. 一.from + size 浅分页 "浅"分页可以理解为简单意义上的分页.它的原理很简单,就是查询前20条数据,然后截断前10条,只返回10-20的数据.这样其实白白浪费了前10条的查询. GET test/_search { &

  • Elasticsearches打分机制讲解

    目录 一 例子 二 文档打分的运作机制:TF-IDF 2.1 词频:TF 2.2 逆文档频率:IDF 三 Lucene评分公式 四 其他的打分方法 五 配置打分模型 5.1 简要配置BM25打分模型 5.2 为BM25配置高级的settings 5.3 配置全局打分模型 六 boosting 6.1 索引期间的boosting 6.2 查询期间的boosting 6.3 跨越多个字段的查询 七 使用“解释”来理解文档是如何评分的 一 例子 现在,讲述一个真实的故事! 故事一定是伴随着赵忠祥老师的

  • PHP垃圾回收机制讲解

    PHP的垃圾回收机制 垃圾回收机制是一种动态存储分配的方案.它会自动释放程序不再需要的已分配的内存块.垃圾回收机制可以让程序员不必过分关心程序内存分配,从而将更多的精力投入到业务逻辑.在现在的流行各种语言当中,垃圾回收机制是新一代语言所共有的特征,如Python.PHP.C#.Ruby等都使用了垃圾回收机制. 好了,进入代码实战阶段,注意两点: $a = 'hello'. mt_rand( 1, 1000 ); echo xdebug_debug_zval( 'a'); $b = $a; ech

  • Java RMI机制讲解

    Java RMI Java RMI之HelloWorld篇 Java RMI 指的是远程方法调用 (Remote Method Invocation).它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法.可以用此方法调用的任何对象必须实现该远程接口. Java RMI不是什么新技术(在Java1.1的时代都有了),但却是是非常重要的底层技术. 大名鼎鼎的EJB都是建立在rmi基础之上的,现在还有一些开源的远程调用组件,其底层技术也是rmi. 在大力鼓

  • asp.net 的错误处理机制讲解

    程序健壮性最基本要求就是程序错误的处理与捕捉,在ASP.NET中,错误的处理有和其他编程语言一样的机制,可以使用Try-Catch-Finally等方式,这一点和ASP相比具有较大的进步.而且,使用这些错误处理方法,可以大大提高程序的可读性和程序调试速度,在这几个优势结合的情况下,我们更加应该注意这一点.  关于错误的处理,我们可以参考这篇文章: Try...Catch...Finally in ASP.NET Introduction Error handling in Classic ASP

  • Java事件监听机制讲解

    给组件加上监听器 定义一个类,这个类继承ActionListener pubulic class ButListener implements ActionListener{ Public void actionPerformed(ActionEvent e){ }} 给按钮添加动作监听器方法 ButListener but = new ButListen(); jbu.addActionListener(but); 加上监听机制后再监听器ButListener时间处理方法中再创建窗口即可得到点

  • Python中整数的缓存机制讲解

    在python中,如下代码结果一定不会让你吃惊: Python 3.3.2 (v3.3.2:d047928ae3f6, May 16 2013, 00:06:53) [MSC v.1600 64 bit (AMD64)] on win32 Type "copyright", "credits" or "license()" for more information. >>> a=345 >>> b=a >

  • Spring数据库事务的实现机制讲解

    目录 事务控制的核心--Connection 用AOP技术保持当前的Connection Service层和Dao层共享Connection 事务为什么要切在Service层的理由 spring事务与数据库事务的区别 事务控制的核心--Connection 在开始之前,先让我们回忆一下数据库较原始的JDBC是怎么管理事务的: //仅做演示,代码不完整,不完全规范 try { con.setAutoCommit(false); statement1 = con.prepareStatement(s

  • Hadoop中namenode和secondarynamenode工作机制讲解

    1)流程 2)FSImage和Edits nodenode是HDFS的大脑,它维护着整个文件系统的目录树,以及目录树里所有的文件和目录,这些信息以俩种文件存储在文件系统:一种是命名空间镜像(也称为文件系统镜像,File System Image,FSImage),即HDFS元数据的完整快照,每次NameNode启动的时候,默认会加载最新的命名空间镜像,另一种是命令空间镜像的编辑日志(Edit log). FSImage文件其实是文件系统元数据的一个永久性检查点,但并非每一个写操作都会更新这个文件

  • 老生常谈android中的事件传递和处理机制

    一直以来,都被android中的事件传递和处理机制深深的困扰!今天特意来好好的探讨一下.现在的感觉是,只要你理解到位,其实事件的 传递和处理机制并没有想象中的那么难.总之,不要自己打击自己,要相信自己能掌握这块知识.好了,下面是我今天的收获,希望也 能对你有一点帮助. 一.拟人化来理解android中的事件机制 其实android中的事件传递与处理机制跟我们生活中的事件处理是一样的.这里有一个生活中的例子,很能说明这个问题.阐述如下: 你是一个公司的员工,你的上头有一个主管,主管上头呢还有一个经

  • python实现五子棋人机对战游戏

    本文代码基于 python3.6 和 pygame1.9.4. 五子棋比起我之前写的几款游戏来说,难度提高了不少.如果是人与人对战,那么,电脑只需要判断是否赢了就可以.如果是人机对战,那你还得让电脑知道怎么下. 我们先从简单的问题来看. 开端 画棋盘 首先肯定是要画出棋盘来,用 pygame 画出一个 19 × 19 或 15 × 15 的棋盘并不是什么难事,这在之前的文章中已经多次用到,就不赘述了. 画棋子 需要说一下的是画棋子,因为没找到什么合适的棋子图片,所以只要自己来画棋子. 我们用 p

随机推荐