ElasticSearch 8.x – 文档得分计算

内容纲要

前言

Lucene 和 ES 的得分机制是一个基于词频和逆文档词频的公式,简称为 TF-IDF 公式。

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

考虑到查询内容和文档得关系比较复杂,所以公式中需要输入得参数和条件非常得多。但是其中比较重要得其实是两个算法机制

  • TF (词频)
    Term Frequency : 搜索文本中的各个词条(term)在查询文本中出现了多少次,
    出现次数越多,就越相关,得分会比较高
  • IDF(逆文档频率)
    Inverse Document Frequency : 搜索文本中的各个词条(term)在整个索引的所有文档中出现了多少次,出现的次数越多,说明越不重要,也就越不相关,得分就比较低。

打分机制

接下来咱们用一个例子简单分析一下文档的打分机制:

1、首先,咱们先准备一个基础数据

# 创建索引
PUT /atguigu
# 增加文档数据
# 此时索引中只有这一条数据
PUT /atguigu/_doc/1
{
 "text":"hello"
}

2、查询匹配条件的文档数据

GET /atguigu/_search
{
 "query": {
   "match": {
     "text": "hello"
   }
 }
}

这里文档的得分为:0.2876821,很奇怪,此时索引中只有一个文档数据,且文档数据中可以直接匹配查询条件,为什么分值这么低?这就是公式的计算结果,咱们一起来看看

3、分析文档数据打分过程

GET后添加参数explain=true

# 增加分析参数
GET /atguigu/_search?explain=true
{
 "query": {
   "match": {
     "text": "hello"
   }
 }
}

执行后,会发现打分机制中有 2 个重要阶段:计算 TF 值和 IDF 值

最后的分数为:

4、计算 TF 值

参数 含义 取值
freq 文档中出现词条的次数 1.0
k1 术语饱和参数 1.2(默认值)
b 长度规格化参数(单词长度对于整个文档的影响程度) 0.75(默认值)
dl 当前文中分解的字段长度 1.0
avgdl 查询文档中分解字段数量/查询文档数量 1.0
TF(词频) 1.0/(1 + 1.2 (1 - 0.75 + 0.751.0/1.0)) 0.454545

5、计算 IDF 值

参数 含义 取值
N 包含查询字段的文档总数(不一定包含查询词条) 1
n 包含查询词条的文档数 1
IDF(逆文档频率) log(1 + (1 - 1 + 0.5) / (1 + 0.5)) 0.2876821

注:这里的 log 是底数为 e 的对数

6、计算文档得分

参数 含义 取值
boost 词条权重 2.2(基础值)* 查询权重(1)
idf 逆文档频率 0.2876821
tf 词频 0.454545
Score(得分) 2.2 0.2876821 0.454545 0.2876821

7、增加新的文档,测试得分

  • 增加一个毫无关系的文档

    # 增加文档
    PUT /atguigu/_doc/2
    "text" : "spark"
    }
    # 因为新文档无词条相关信息,所以匹配的文档数据得分就应该较高:
    # 0.6931741
    GET /atguigu/_search
    {
    "query": {
    "match": {
     "text": "hello"
    }
    }
    }

  • 增加一个一模一样的文档

    # 增加文档
    PUT /atguigu/_doc/2
    {
    "text" : "hello"
    }
    # 因为新文档含词条相关信息,且多个文件含有词条,所以显得不是很重要,得分会变低
    # 0.18232156
    GET /atguigu/_search
    {
    "query": {
    "match": {
     "text": "hello"
    }
    }
    }

  • 增加一个含有词条,但是内容较多的文档

    # 增加文档
    PUT /atguigu/_doc/2
    {
    "text" : "hello elasticsearch"
    }
    # 因为新文档含词条相关信息,但只是其中一部分,所以查询文档的分数会变得更低一些。
    # 0.14874382
    GET /atguigu/_search
    {
    "query": {
    "match": {
     "text": "hello"
    }
    }
    }

案例

需求:

查询文档标题中含有“Hadoop”,“Elasticsearch”,“Spark”的内容。

优先选择“Spark”的内容

1、准备数据

# 准备数据
PUT /testscore/_doc/1001
{
 "title" : "Hadoop is a Framework",
 "content" : "Hadoop 是一个大数据基础框架"
}
PUT /testscore/_doc/1002
{
 "title" : "Hive is a SQL Tools",
 "content" : "Hive 是一个 SQL 工具"
}
PUT /testscore/_doc/1003
{
 "title" : "Spark is a Framework",
 "content" : "Spark 是一个分布式计算引擎"
}

2、查询数据

# 查询文档标题中含有“Hadoop”,“Elasticsearch”,“Spark”的内容
GET /testscore/_search?explain=true
{
 "query": {
    "bool": {
       "should": [
         {
           "match": {
             "title": {"query": "Hadoop", "boost": 1}
           }
         },
         {
           "match": {
             "title": {"query": "Hive", "boost": 1}
           }
         },
         {
           "match": {
           "title": {"query": "Spark", "boost": 1}
         }
       }
     ]
   }
 }
}

此时,你会发现,Spark 的结果并不会放置在最前面

此时,咱们可以更改 Spark 查询的权重参数 boost.看看查询的结果有什么不同

# 查询文档标题中含有“Hadoop”,“Elasticsearch”,“Spark”的内容
GET /testscore/_search?explain=true
{
 "query": {
   "bool": {
     "should": [
       {
         "match": {
           "title": {"query": "Hadoop", "boost": 1}
         }
       },
       {
         "match": {
           "title": {"query": "Hive", "boost": 1}
         }
       },
       {
         "match": {
           "title": {"query": "Spark", "boost": 2}
         }
       }
     ]
    }
  }
}

Leave a Comment

您的电子邮箱地址不会被公开。 必填项已用*标注

close
arrow_upward