1. 借鉴
极客时间 阮一鸣老师的Elasticsearch核心技术与实战
ElasticSearch7+Spark构建高相关性搜索服务&千人千面推荐系统
Lucene学习总结之六:Lucene打分公式的数学推导
【Elasticsearch】打分策略详解与explain手把手计算
官网 Theory Behind Relevance Scoring
对数计算器
2. 开始
自定义打分
使用function score自定义打分
在查询结束后,对匹配的每个文档进行重新算分并排序
- 如何来构建一个function_score的查询呢?其实就是将我们平时在query里面的部分移到function_score的query里面,加上一些余外的算法。我们来举个栗子,然后一步一步了解其算法规则,因为在functions中可以指定多个规则,我们先以一个规则的为例
GET /notes/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "elasticsearch hadoop canal"
}
},
"functions": [
{
"field_value_factor": {
"field": "grade"
}
}
]
}
}
}
- 可以看到返回的结果中的分数已经被grade取代了
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 98.08292,
"hits" : [
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "1",
"_score" : 98.08292,
"_source" : {
"title" : "Elasticsearch",
"content" : "About Elasticsearch",
"grade" : 100
}
},
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "2",
"_score" : 3.9233167,
"_source" : {
"title" : "Hadoop",
"content" : "About Hadoop",
"grade" : 4.5
}
},
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "3",
"_score" : 3.9233167,
"_source" : {
"title" : "Canal",
"content" : "About Canal",
"grade" : 4.7
}
}
]
}
}
- 可以看到目前function_score的算分规则就是:match匹配的相关的分数*grade的数值
可以看到如果grade差距非常大时,分数差距特别大,有哪些参数可以调整它的离散程度呢?
变更分数1
- 使用modifier:我们可以添加modifier属性来使分数更加平滑。它有以下备选值
值 | 释义 |
---|---|
none | 不使用 |
log | 以10为底,新分值=老分值*log(filed的值) |
log1p | 以10为底,新分值=老分值*log(1 + filed的值) |
log2p | 以10为底,新分值=老分值*log(2 + filed的值) |
ln | 以e为底,新分值=老分值*ln( filed的值) |
ln1p | 以e为底,新分值=老分值*ln(1 + filed的值) |
ln2p | 以e为底,新分值=老分值*ln(2 + filed的值) |
square | 平方 |
sqrt | 开方 |
reciprocal | 倒数 |
-
在说modifier之前,我们先说下上面所谓的新老分数。
- 老分数:说的是function_score的query部分的分数,也就是相关度分数,查看这一部分算分跟我们直接写query看到的分数是一样的。
- 新分数:就是说function_score返回的分数。
-
我们使用modifier,来看看
GET /notes/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "elasticsearch hadoop canal"
}
},
"functions": [
{
"field_value_factor": {
"field": "grade",
"modifier": "log1p"
}
}
]
}
}
}
- 可以看到,就算grade的分支差别特别大,最后算的分数的离散程度不会特别高
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 1.9658968,
"hits" : [
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.9658968,
"_source" : {
"title" : "Elasticsearch",
"content" : "About Elasticsearch",
"grade" : 100
}
},
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.6855702,
"_source" : {
"title" : "Hadoop",
"content" : "About Hadoop",
"grade" : 4.5
}
},
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.6855702,
"_source" : {
"title" : "Canal",
"content" : "About Canal",
"grade" : 4.7
}
}
]
}
}
- 嗯,我们来以这个例子的id为1,title是Elasticsearch的文档分数为例,手动计算一下分数到底是不是我们上面说的那一回事。
- | - | 补充说明 |
---|---|---|
老分数 | 0.9808292 |
通过以下查询得来的分数: GET /notes/_search { "query": { "match": { "title": "elasticsearch hadoop canal" } } } |
计算公式为 | 新分值=老分值*log(1 + filed的值) | 我们使用的modifier为log1p |
我们计算的新分数的值: | 0.9808292 * log(1 + 100) = 0.9808292 * 2.0043 = 1.96587597 | - |
es通过function_score查询出来的值: | 1.9658968 | - |
- 通过计算可以看到,跟我们计算的结果基本一致。(●´∀`●)ノ
变更分数2
- 使用factor:我们可以添加factor属性来在modifier的基础上使其增加相应的权重,我们以modifier为log1p为例,同时设置factor为3,则最终得分的计算公式为:新分数=老分数*log(1 + factor * grade的分数)
GET /notes/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "elasticsearch hadoop canal"
}
},
"functions": [
{
"field_value_factor": {
"field": "grade",
"modifier": "log1p",
"factor": 2
}
}
]
}
}
}
- 结果如下,ID为1的文档我计算的分数结果是2.25904581,可以看到分数也基本一致
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 2.2590418,
"hits" : [
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "1",
"_score" : 2.2590418,
"_source" : {
"title" : "Elasticsearch",
"content" : "About Elasticsearch",
"grade" : 100
}
},
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.9359489,
"_source" : {
"title" : "Hadoop",
"content" : "About Hadoop",
"grade" : 4.5
}
},
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.9359489,
"_source" : {
"title" : "Canal",
"content" : "About Canal",
"grade" : 4.7
}
}
]
}
}
变更分数3
- 使用score_mode:
控制functions内部的计算规则。
这是什么意思呢?因为functions是一个数组,里面可以有多个函数。
值 | 释义 | 是否默认 |
---|---|---|
Multiply | match匹配的相关的分数*grade的数值 | 是 |
Sum | match匹配的相关的分数+grade的数值 | 否 |
Max/Min | 最大/最小;【取(match匹配的相关的分数)或(grade的数值)中的最大/最小值】 | 否 |
Replace | 使用函数值【grade的数值】替换算分 | 否 |
- 这里我们以sum为例
GET /notes/_search
{
"_source": "",
"query": {
"function_score": {
"query": {
"match": {
"title": "elasticsearch hadoop canal"
}
},
"functions": [
{
"field_value_factor": {
"field": "grade",
"modifier": "log2p",
"factor": 10
}
},
{
"field_value_factor": {
"field": "grade",
"modifier": "log2p",
"factor": 5
}
}
],
"score_mode": "sum"
}
}
}
- 返回结果:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 5.5922675,
"hits" : [
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "1",
"_score" : 5.5922675,
"_source" : { }
},
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "2",
"_score" : 2.9088175,
"_source" : { }
},
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "3",
"_score" : 2.9088175,
"_source" : { }
}
]
}
}
- 嗯,我们来以这个例子的id为1,title是Elasticsearch的文档分数为例,我们再来手动计算一下。
- | - | 补充说明 |
---|---|---|
老分数 | 0.9808292 |
通过以下查询得来的分数: GET /notes/_search { "query": { "match": { "title": "elasticsearch hadoop canal" } } } |
计算公式为 | 新分值=老分值 * (log(2 + 5* filed的值) + log(2 + 10* filed的值)) | 我们使用的modifier为log2p |
我们计算的新分数的值: | 0.9808292 * (log(2 + 5 *100) + log(2 + 10 *100)) = 0.9808292 * (2.7007 + 3.0009) = 5.59229577 | - |
es通过function_score查询出来的值: | 5.5922675 | - |
- 通过计算可以看到,跟我们计算的结果基本一致。(●´∀`●)ノ
变更分数4
- 使用boost_mode:
控制query和functions部分的计算规则
在上面我们看到分值的计算是使用相乘的方式:match匹配的相关的分数*grade的数值,使用boost mode则可以控制计算方式。
值 | 释义 | 是否默认 |
---|---|---|
Multiply | match匹配的相关的分数*grade的数值 | 是 |
Sum | match匹配的相关的分数+grade的数值 | 否 |
Max/Min | 最大/最小;【取(match匹配的相关的分数)或(grade的数值)中的最大/最小值】 | 否 |
Replace | 使用函数值【grade的数值】替换算分 | 否 |
- 这里我们以sum为例
GET /notes/_search
{
"_source": "",
"query": {
"function_score": {
"query": {
"match": {
"title": "elasticsearch"
}
},
"functions": [
{
"field_value_factor": {
"field": "grade"
}
}
],
"boost_mode": "sum"
}
}
}
- 结果如下,可以看到score已经变为相加了:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 100.98083,
"hits" : [
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "1",
"_score" : 100.98083,
"_source" : { }
}
]
}
}
- 这里我就不算了,如果不设置,默认为multiply,可以参照我们本篇前面的那个计算一下。
max_boost属性
- 使用max_boost:设置分数在某个范围以内
GET /notes/_search
{
"_source": "",
"query": {
"function_score": {
"query": {
"match": {
"title": "elasticsearch hadoop canal"
}
},
"functions": [
{
"field_value_factor": {
"field": "grade"
}
}
],
"max_boost": 10
}
}
}
- 结果如下,我们使用max_boost将分数规定在了10分以内
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 9.808291,
"hits" : [
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "1",
"_score" : 9.808291,
"_source" : { }
},
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "2",
"_score" : 3.9233167,
"_source" : { }
},
{
"_index" : "notes",
"_type" : "_doc",
"_id" : "3",
"_score" : 3.9233167,
"_source" : { }
}
]
}
}
random_score属性
- random_score:一致性随机函数。设置seed属性,如果seed值保持一致,那查询出来的结果排序就是相同的。
GET /notes/_search
{
"_source": "",
"query": {
"function_score": {
"query": {
"match": {
"title": "elasticsearch hadoop canal"
}
},
"functions": [
{
"random_score": {
"seed": 1231
}
}
]
}
}
}
- 计算分值的函数
函数 | 释义 |
---|---|
weight | 设置权重 |
field value factor | 使用该值来修改score |
random | 随机算分 |
衰减函数 | 以某个字段的值为标准,距离某个值越接近,得分越高 |
script | 自定义脚本实现算分 |
查看打分详情
1. 在查询中添加_explain参数
GET /tmdb_movies/_search
{
"explain": true,
"query": {
"multi_match": {
"query": "steve",
"fields": ["title"]
}
}
}
-
我们以查询结果的第一篇文档做例子
得分分析
2. 使用_validate/query?explain
GET /tmdb_movies/_validate/query?explain
{
"query": {
"multi_match": {
"query": "steve",
"fields": ["title"]
}
}
}
- 查询结果
{
"_shards" : {
"total" : 1,
"successful" : 1,
"failed" : 0
},
"valid" : true,
"explanations" : [
{
"index" : "tmdb_movies",
"valid" : true,
"explanation" : "title:steve"
}
]
}
网友评论