在前面的课程中,通过搭建ELK相关的中间件,以及配置动态mapping结构用于存储我们需要检索的文档信息;
我们通过输入关键词进行分词检索然后ES默认通过评分的形式将数据排序好展示给我们,当文档评分越高,那么搜索的结果越靠前;如下图所示,我们通过百度搜索关键字"兔子李磊"得到的搜索结果
可以看到,在上述搜索结果中,可以看到第一条记录中,由于兔子 和 李磊 两个关键词都出现了,排名相对靠前一点;我们暂时可以理解为上述第一条文章的搜索结果相对搜索评分较高,相关度较高;
上述的理论基于一些自我逻辑,那么在ES中,我们搜索的相关度评分,又是如何计算出来的呢;
我们通过ES的复合框搜索语句到结果展示,其展示规则默认就是根据相关度进行排序的,即默认根据score排序;
查询ES的执行计划
执行计划查询方式如下:
GET shopping/_search
{
"explain": true,
"query": {
"match": {
"goodsInfoName": "苏泊尔"
}
}
}
在上述DSL语句中,开启explain即可开启ES的查询计划(类似于Mysql数据库的执行计划);
上述DSL语句在我本地的查询结果如下:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 3,
"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 24,
"relation" : "eq"
},
"max_score" : 5.3067513,
"hits" : [
{
"_shard" : "[shopping][1]",
"_node" : "h665-yAdSzGgjxamBh5CjA",
"_index" : "shopping",
"_type" : "_doc",
"_id" : "10976",
"_score" : 5.3067513,
"_source" : {
"goodsInfoName" : "苏泊尔不锈钢压力锅高压锅YS22ED+苏泊尔保鲜盒饭盒便当盒330mlKB033AE1(银色)",
"其他字段省略....."
},
"_explanation" : {
"value" : 5.3067513,
"description" : "weight(goodsInfoName:苏泊尔 in 328) [PerFieldSimilarity], result of:",
"details" : [
{
"value" : 5.3067513,
"description" : "score(freq=2.0), computed as boost * idf * tf from:",
"details" : [
{
"value" : 2.2,
"description" : "boost",
"details" : [ ]
},
{
"value" : 3.6549778,
"description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
"details" : [
{
"value" : 10,
"description" : "n, number of documents containing term",
"details" : [ ]
},
{
"value" : 405,
"description" : "N, total number of documents with field",
"details" : [ ]
}
]
},
{
"value" : 0.65996563,
"description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:",
"details" : [
{
"value" : 2.0,
"description" : "freq, occurrences of term within document",
"details" : [ ]
},
{
"value" : 1.2,
"description" : "k1, term saturation parameter",
"details" : [ ]
},
{
"value" : 0.75,
"description" : "b, length normalization parameter",
"details" : [ ]
},
{
"value" : 11.0,
"description" : "dl, length of field",
"details" : [ ]
},
{
"value" : 13.553086,
"description" : "avgdl, average length of field",
"details" : [ ]
}
]
}
]
}
]
}
},
.....略.....
通过查询ES的执行计划(explain)我们可以得出,score评分基础公式如下:score(freq=2.0), computed as boost * idf * tf from: ;
如果查询关键字被分词为多个词组,那么score评分等于每个关键词的score评分总和;
ES执行计划详解
上述DSL语句查询结果中,查询总耗时为1毫秒(took),当前查询索引有3个分片(_shards.total),当前查询共查询到24条记录(hits.value),当前查询最大查询相关度评分为5.3067513分(hits.max_score),当前查询的第一条记录是从分片编号为1的分片中查询出来的(hits.hits._shard)......
"description" : "weight(goodsInfoName:苏泊尔 in 328) [PerFieldSimilarity], result of:" 查询关键词 苏泊尔 在文档id为328记录中的查询字段goodsInfoName的打分情况;
其中查询结果集的第一条记录的执行计划部分代码为 hits.hits._explanation,其评分为5.3067513分,其评分的计算方式为
"description" : "score(freq=2.0), computed as boost * idf * tf from:"
在这里,有三个关键词含义分别如下:
boost:查询时候查询项加权值,当前值可以通过创建mapping结构的时候指定,也可以在后期查询的时候动态修改
idf:
反转文档词频,意思是出现的逆词频数,意为查询到的文档记录数和索引中总文档数的一个占比关系,其计算公式如下:
"description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:"
tf: 词频归一值,即搜索的关键词(未分词或分词后的词组)在被搜索的字段的(未分词或分词后的词组)中所出现的个数对总的分词后的词组数的比例,其计算公式如下:
"description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:"
在上述DSL语句中,goodsInfoName字段中出现了2次查询关键词"苏泊尔",而排在第二条记录的当前字段中只出现了1次(两条记录分词词组数量相同),
所以在tf评分中,第一条记录的评分高于第二条记录,从而影响到整体评分的走向
根据上述执行计划中的三项评分相乘,从而得到文档的最终score评分值
2.2 * 3.6549778 * 0.65996563 = 5.3067513
ES中评分加权项
boost:对于每一个term的权值,其默认值为2.2,我们可以在创建索引mapping结构的时候指定字段的boost的值,更多情况下,我们可以使用bosst来作为ES搜索结果的调优方案,比如搜索文档标题我们可以将boost权重设置大一些,在搜索文档内容的时候,我们可以将boost权重设置小一些,从而实现动态的调整搜索结果,实现搜索不同的字段计算权重不同;
ES中评分反转文档词频
idf:反转文档词频,上述搜索结果中的explain解释为:
{
"value" : 3.6549778,
"description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
"details" : [
{
"value" : 10,
"description" : "n, number of documents containing term",
"details" : [ ]
},
{
"value" : 405,
"description" : "N, total number of documents with field",
"details" : [ ]
}
]
}
idf打分由log(1 + (N - n + 0.5) / (n + 0.5))公式计算出来的此时log是以e为底数的,由于版本迭代关系,ES中关于idf的算法也在逐步优化,通过公式我们可以看到,我们使用科学计算器以e为底计算两个变量的值发生改变时,idf的得分的变化情况如下
说到这里,我们需要了解一下N 和 n之间的关系;N表示的是当前查询记录所处的分片上当前索引的文档数;如果我们有多个分片,那么索引数据会被存储到多个分片上,每个分片上的文档记录数相加,得到的就是当前索引的文档总计录数了;(网上找了很多文章,一直没理解到N的值是如何计算出来的,因为网上都说N的值为文档的总记录数,但是忽略了分片的存在,所以导致学习过程中出现了较多的疑惑);如上面的这个执行计划我们可以看到当前分片"_shard" : "[shopping][1]"中记录了405条shopping索引的记录;
另外省略掉的部分分别从"_shard" : "[shopping][0]" 和 "_shard" : "[shopping][2]" 查询到的记录值,所存储的记录值分别为358、384;这三个数值相加刚好等于我这边shopping索引的总文档记录数1147;
同理,n的值表示从当前分片中匹配到的文档记录数,如上述执行计划结果所示,一共查询到总计录为24,当前分片"_shard" : "[shopping][1]"中匹配到了10条记录,另外所省略掉的部分分别从"_shard" : "[shopping][0]" 和 "_shard" : "[shopping][2]" 匹配到记录值为5 和 9;这三个数值相加就刚好等于24了;
这样的话我们就更容易理解idf分支的计算由来了;如何理解呢,就好比一部《西游记》电视剧一共100集,每一集都有“孙悟空”这个角色出现,那么计算关键词“孙悟空”的idf打分为:log(1 + (100 - 100 + 0.5) / (100 + 0.5)) = 0.0049627893421 ,这样就计算的“孙悟空”相关的文档的idf分数比较低,因为在每一集中都有;
相反,如果100集电视剧中,"白骨精"出现了2集,那么计算"白骨精"的idf分数为:log(1 + (100 - 2 + 0.5) / (2 + 0.5)) = 3.698829785;
所以在搜索"孙悟空" 和 "白骨精" 时,后者的评分会远高于前者; 如果同时搜索“孙悟空和白骨精”,这样分词得到的词组为 [“孙悟空”, “白骨精”], 那么不出意外的话,包含“白骨精” 的文档得分会远高于只包含“孙悟空”的文档; 写的有一点绕,意思就是在当前分片中,搜索匹配到的文档越多,词频整体出现的越高,得分越低;
ES中评分词频归一值
tf:词频,又称为 词频归一值,即搜索的关键词(未分词或分词后的词组)在被搜索的字段的(未分词或分词后的词组)中所出现的个数对总的分词后的词组数的比例;在上述执行计划中解释如下
{
"value" : 0.65996563,
"description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:",
"details" : [
{
"value" : 2.0,
"description" : "freq, occurrences of term within document",
"details" : [ ]
},
{
"value" : 1.2,
"description" : "k1, term saturation parameter",
"details" : [ ]
},
{
"value" : 0.75,
"description" : "b, length normalization parameter",
"details" : [ ]
},
{
"value" : 11.0,
"description" : "dl, length of field",
"details" : [ ]
},
{
"value" : 13.553086,
"description" : "avgdl, average length of field",
"details" : [ ]
}
]
}
tf词频归一值的计算公式由freq / (freq + k1 * (1 - b + b * dl / avgdl)) 组成,可以看到一共有5个关键词;
归一在ES中是一个比较重要的打分标准,和idf一样;不过idf是针对检索结果记录数与总记录数之间的关系来计算的得分,而归一值则是通过计算单个文档内部的查询结果进行打分;
通俗易懂的描述,即查询的关键词(词组)在被检索字段的分词后词组中出现的评率越高,其归一值分数相对越好;在ES中,针对不需要分词的字段(keyword)类型的默认是关闭归一值的;
freq:检索关键词组在被检索字段的词组中出现的频率,即出现了多少次,比如上面的执行计划搜索 苏泊尔 在字段中出现了2次,其结果为2
k1:词的饱和度值,默认值为1.2 (没有深入去了解)
b:长度归一化参数,默认值为0.75 (没有深入去了解)
d1:被检索字段分词后的词组长度,比如上面的执行计划搜索结果第一条搜索的关键字段是 goodsInfoName,其分词后一共产生了11个词组,所以在第一条记录的执行计划中,d1的值为 11;通过上面的公式可以看到,d1出现在分母的位置,即d1的值越大,其计算的评分会越小;
avgdl:分片中当前被检索字段的平均词组数值;即在当前分片中当前索引每个文档中当前字段 goodsInfoName 拆分的词组数量 的平均值;不同的分片中avgdl的值可能是不一样的;因为不同的字段存储的内容不同,每个字段的avgdl也是不一样的,当我们多字段查询时,相比较title之类的字段可以获取的评分相对会更高,因为词组更短;为了均衡这一规律,实现归一值,所以除以avgdl从而获取一个相对比较平和的比较分数,获取的分数更加客观;
总结:ElasticSearch的score字段搜索评分由3个部分组成,分别是boost、idf、tf;score(freq=2.0), computed as boost * idf * tf from:
增加关键词的多元化 和 提升关键词在单文档中出现的频率等都可以直接影响到ES检索的打分;