Elasticsearch实战——function_score 查询详解

Elasticsearch实战——function_score 查询详解

1. function_score简介

在使用ES进行全文搜索时,搜索结果会以文档的相关度进行排序,而这个相关度是可以通过function_score自己定义的。我们可以通过使用function_score来控制文档相关。

function_score是专门用来处理文档_score的DSL,它允许每个主查询query匹配的文档应用加强函数,以达到改变原始查询评分_score的目的。

function_score 会在主查询query结束后对每一个匹配的文档进行一系列的重打分操作,能够对多个字段一起进行综合评估,并且能够使用filter将结果划分为多个子集,并为每个子集使用不同的加强函数。

function_score提供了几种加强_score计算的函数:

  • weight:设置一个简单而不被规范化的权重提升值

    weight加强函数和boost参数类似,可以用于任何查询,不过有一点差别就是weight不会被Lucene nomalize成难以理解的浮点数,而是直接被应用(boost会被nomalize)。

    例如,当weight3时,最终结果为 :

    new_score = old_score * 3
    
  • field_value_factor:将某个字段的值乘以old_score

    将字段shardCount(分享次数)或clickCount(点击次数)作为考虑因素:

    new_score = old_score * shardCount(文档的分享次数)
    
  • random_score:为每个用户使用不同的随机评分对结果排序,但对某一具体用户来说,看到的顺序始终是一致的。

  • 衰减函数(linearexpguass):以某个字段的值为基准,距离某个值越近,得分越高。

function_score其他辅助的参数:

  • boost_mode:决定old_score和加强score如何合并

    • multiplynew_score = old_score * 加强score
    • sumnew_score = old_score + 加强score
    • minnew_score= min(old_score, 加强score)
    • maxnew_score= max(old_score, 加强score)
    • replacenew_score = 加强score,直接替换old_score
  • score_mode:决定functions里面的加强score怎么合并,会先合并加强score成一个总加强score,再使用总加强score区和old_score做合并,换言之就是先执行score_mode,再执行boost_mode

    multiply(默认)、sum、avg、first(使用第一个函数的结果作为最终结果)、max、min

  • max_boost:限制加强函数的最大效果,就是限制加强score最大能多少,但是不会限制old_score。如果加强score超过了max_boost的限制值,会把加强score的值设置成max_boost

2. function_score(field_value_factor具体实例)

2.1创建索引

PUT func_index
{
    
    
  "settings": {
    
    
    "number_of_replicas": 1
    , "number_of_shards": 3
  },
  "mappings": {
    
    
    "info":{
    
    
      "properties":{
    
    
        "title":{
    
    
          "type":"text",
          "analyzer":"ik_max_word",
          "search_analyzer":"ik_smart"
        },
        "shardCount":{
    
    
          "type":"integer"
        }
      }
    }
  }
}

2.2 添加文档

PUT _bulk
{"index":{"_index":"func_index","_type":"info","_id":"1"}}
{"title":"ES入门","shardCount":2}
{"index":{"_index":"func_index","_type":"info","_id":"2"}}
{"title":"ES进阶","shardCount":5}
{"index":{"_index":"func_index","_type":"info","_id":"3"}}
{"title":"ES应用宝典","shardCount":10}

2.3 演示

使用普通的查询

GET func_index/info/_search
{
    
    
  "query": {
    
    
    "match": {
    
    
      "title": "ES"
    }
  }
}

[
  {
    
    
    "_score": 0.2876821,
    "_source": {
    
    "title": "ES进阶","shardCount": 5}
  },
  {
    
    
    "_score": 0.19856805,
    "_source": {
    
    "title": "ES入门","shardCount": 2}
  },
  {
    
    
    "_score": 0.16853254,
    "_source": {
    
    "title": "ES应用宝典","shardCount": 10}
  }
]

使用function_scorefield_value_factor改变_score,将old_score乘上shardCount的值

GET func_index/info/_search
{
    
    
  "query":{
    
    
    "function_score": {
    
    
      "query": {
    
    
        "match": {
    
    
          "title": "ES"
        }
      },
      "functions": [
        {
    
    
          "field_value_factor": {
    
    
            "field": "shardCount"
          }
        }
      ]
    }
  }
}

[
  {
    
    
    "_score": 1.6853254,#原来的得分:0.16853254
    "_source": {
    
    "title": "ES应用宝典","shardCount": 10}
  },
  {
    
    
    "_score": 1.4384104,#原来的得分:0.2876821
    "_source": {
    
    "title": "ES进阶","shardCount": 5}
  },
  {
    
    
    "_score": 0.3971361,#原来的得分:0.19856805
    "_source": {
    
    "title": "ES入门","shardCount": 2
    }
  }
]

加上max_boost,限制field_value_factor的最加强score

可以看到ES入门加强score2,在max_boost限制里,所以不受影响。

ES进阶ES应用宝典field_value_factor函数产生的加强score因为超过max_boost的限制,所以被设为3

GET func_index/info/_search
{
    
    
  "query":{
    
    
    "function_score": {
    
    
      "query": {
    
    
        "match": {
    
    
          "title": "ES"
        }
      },
      "functions": [
        {
    
    
          "field_value_factor": {
    
    
            "field": "shardCount"
          }
        }
      ],
      "max_boost": 3
    }
  }
}

[
  {
    
    
    "_score": 0.8630463,#原来的得分:0.2876821
    "_source": {
    
    "title": "ES进阶","shardCount": 5}
  },
  {
    
    
    "_score": 0.5055976,#原来的得分:0.16853254
    "_source": {
    
    "title": "ES应用宝典","shardCount": 10}
  },
  {
    
    
    "_score": 0.3971361,#原来的得分:0.19856805
    "_source": {
    
    "title": "ES入门","shardCount": 2}
  }
]

2.4 modifier参数的支持的值

有时线性计算new_score=old * shardCount值的效果不是很好,field_value_factor中还支持modifierfactor参数,可以改变shardCount值对old_score的影响。

  • nonenew_score = old * shardCount,默认值
  • log1pnew_score = old_score * log(1+shardCount)
  • log2pnew_score = old_score * log(2+shardCount)
  • lnnew_score = old_score * ln(shardCount)
  • ln1pnew_score = old_score * ln(1+shardCount)
  • ln2pnew_score = old_score * ln(2+shardCount)
  • square:计算平方
  • sqrt:计算平方根
  • reciprocal:计算倒数

2.5 factor参数

factor作为一个调节参数,没有modifier那么强大能改变整个曲线,它仅改变一些常量值,设置factor > 1会提升效果,factor < 1会降低效果。

假设modifier是log1p,那么加入了factor的公式就是:

new_score = old_score * log(1 + factor * shardCount)

2.6 综合应用

GET func_index/info/_search
{
    
    
  "query":{
    
    
    "function_score": {
    
    
      "query": {
    
    
        "match": {
    
    
          "title": "ES"
        }
      },
      "functions": [
        {
    
    
          "field_value_factor": {
    
    
            "field": "price",
            "modifier": "log1p",
            "factor": 1.2
          }
        }
      ],
      "max_boost": 10,
      "boost_mode": "sum"
    }
  }
}

3. function_score (weight具体实例)

functions是一个数组,里面放着的是将要被使用的加强函数列表,我们在里面使用了3个filter去过滤数据,并且每个filter都设置了一个加强函数,并且还是用了一个会应用到所有文档的field_value_factor加强函数。

可以为列表里面的每个加强函数指定一个filter。这样只有在文档满足此filter的要求才会给该文档使用上加强函数。如果不使用filter,加强函数就会应用到全部的文档上。

一个文档可以一次满足多条加强函数和多个filter,如果同时满足多个加强函数,则会产生多个加强score,然后ES通过score——mode定义的方式来合并这些加强score,得到一个总的加强score。得到总加强score后,再用boost_mode定义的方式和old_score做合并。

GET func_index/info/_search
{
    
    
  "query": {
    
    
    "function_score": {
    
    
      "query": {
    
    
        "match": {
    
    
          "title": "ES"
        }
      },
      "functions": [
        {
    
    
          "filter": {
    
    
            "term": {
    
    
              "city": "北京市"
            }
          }, 
          "weight": 10
        },
        {
    
    
          "filter": {
    
    
            "term": {
    
    
              "city": "深圳市"
            }
          }, 
          "weight": 8
        },
        {
    
    
          "filter": {
    
    
            "term": {
    
    
              "city": "上海市"
            }
          }, 
          "weight": 5
        }
      ]
    }
  }
}

java使用示例:

FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = {
    
    
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("city","北京市"), ScoreFunctionBuilders.weightFactorFunction(10)),
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("city","深圳市"), ScoreFunctionBuilders.weightFactorFunction(8)),
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("city","上海市"), ScoreFunctionBuilders.weightFactorFunction(5)),
        };
FunctionScoreQueryBuilder query = QueryBuilders.functionScoreQuery(builders);

weight加强函数也可以用来调整每个语句的贡献度,weight的默认值是1.0,当设置了weight,这个weight就会和评分函数的值相乘,在通过score_mode和其他加强函数合并。

GET func_index/info/_search
{
    
    
  "query":{
    
    
    "function_score": {
    
    
      "query": {
    
    
        "match": {
    
    
          "title": "ES"
        }
      },
      "functions": [
        {
    
    
          "field_value_factor": {
    
    
            "field": "price",
            "modifier": "log1p",
            "factor": 1.5
          },
          "weight": 1.2
        }
      ],
      "max_boost": 10,
      "boost_mode": "sum"
    }
  }
}

java使用示例:

FieldValueFactorFunctionBuilder factor = ScoreFunctionBuilders.fieldValueFactorFunction("price").modifier(FieldValueFactorFunction.Modifier.LOG1P).factor(1.5f).setWeight(1.2f);
QueryBuilders.functionScoreQuery(factor).maxBoost(10).boostMode(CombineFunction.SUM);

4. function_score (衰减函数 linear、exp、gauss 具体实例)

先看下面这个例子。用户选择酒店,希望酒店离市中心近一点,但是如果价格足够偏移,为了省钱。可以妥协,选择比较远的酒店。

如果我们使用一个filter排除市中心方圆1km外的酒店,再使用filter排除价格超过150的酒店。这种做法太过强硬,可能由一个酒店在1.2km,但是比较便宜,用户可能会因此妥协选择住这家酒店。

为了解决这个问题,function_score查询提供了一组衰减函数,让我们可以在两个滑动标准之间权衡。

4.1 衰减函数的简介

function_score支持三种衰减函数:linear、exp和gauss。

这三种函数在用法上完全一样,只是差别与他们的衰减曲线的形状。

  • linear:线性函数是一条直线,一旦与Y轴相交,所有其他值的评分都是0.
  • exp:指数函数,先剧烈衰减,然后变缓慢。
  • guass(最常用):高斯函数。它钟型的,它的衰减速度时先缓慢,然后变快,最后放缓。

在这里插入图片描述

4.2 衰减函数的参数

衰减函数支持的参数:

  • origin:中心点,落在原点上的文档评分_score为满分1.0。支持数值、时间、经纬度坐标点。
  • offset:从origin为中心,设置一个偏移量offset覆盖一个范围,在此范围内所有评分_score也是都和origin一样是满分1.0。
  • scale:衰减率,即一个文档从origin下落时,_score改变的速度
  • decay:从origin衰减到scale所得的评分_score。默认0.5(一般不需要改变,这个参数使用默认的就行)

以上图为例:

  • 所有曲线的origin都是40,offset是5,因此范围在 40-5 <= value <=40+5的文档的评分_score都是满分1.0
  • 在此范围外,评分会开始衰减,衰减率有scale值(此处为5)和decay值(此处是默认值0.5)决定,在origin +/- (offset + scale)处的评分是decay值,也就是在30、50的评分是0.5分
  • 也就是说,在origin + offset + scale或是origin - offset - scale的点上,得到的分数仅有decay

4.3 具体案例

案例一:

准备数据,其中title为text类型,readCount为integer类型。

PUT blog
{
    
    
"settings": {
    
    
    "number_of_replicas": 0,
    "number_of_shards": 1
  },
  "mappings":{
    
    
  	"properties":{
    
    
  		"title":{
    
    
  			"type":"text"
  		},
  		"readCount":{
    
    
  			"type":"integer"
  		}
  	}
  }
}
{
    
     "title": "Elasticsearch实战——搜索详解", "readCount": 5 }
{
    
     "title": "Elasticsearch实战——环境搭建", "readCount": 10 }
{
    
     "title": "Elasticsearch实战——JAVA客户端API", "readCount": 15 }
GET blog/_search
{
    
    
  "query": {
    
    
    "function_score": {
    
    
      "query": {
    
    
        "match_all": {
    
    }
      },
      "functions": [
        {
    
    
          "gauss": {
    
    
            "readCount": {
    
    
              "origin": "10",
              "scale": "5",
              "decay": 0.5
            }
          }
        }
      ]
    }
  }
}
[
  {
    
    
    "_score": 1,
    "_source": {
    
    
      "title": "Elasticsearch实战——JAVA客户端API",
      "readCount": 15
    }
  },
  {
    
    
    "_score": 0.5,//因为decay=0.5,所以当位于 origin-offset-scale=10 的位置时,分数为decay,就是0.5
    "_source": {
    
    
      "title": "Elasticsearch实战——环境搭建",
      "readCount": 10
    }
  },
  {
    
    
    "_score": 0.0625,
    "_source": {
    
    
      "title": "Elasticsearch实战——搜索详解",
      "readCount": 5
    }
  }
]

案例二:

假设有一个用户希望租一个离市区近一点的酒店,并且每晚不超过100元的酒店。而且与距离相比,用户对价格更敏感。那么使用衰减函数guass查询如下:

  • 把price语句的origin点设为50,由于价格的特性一定是越低越好,所以0~100元的所有价格的酒店都认为是比较好的酒店,而100以上的酒店就慢慢衰减。如果把origin设置为100,那么价格低于100元的酒店的评分反而会降低。这并不是我们想要的结果。把origin和offset同时设置为50,只让price大于100元的评分才会变低。
{
    
    
  "query": {
    
    
    "function_score": {
    
    
      "query": {
    
    
        "match_all": {
    
    
          "boost": 1
        }
      },
      "functions": [
        {
    
    
          "filter": {
    
    
            "match_all": {
    
    
              "boost": 1
            }
          },
          "gauss": {
    
    
            "location": {
    
    
              "origin": {
    
    
                "lat": 40.15077,
                "lon": 116.28414
              },
              "scale": "10km",
              "offset": "5km",
              "decay": 0.5
            },
            "multi_value_mode": "MIN"
          }
        },
        {
    
    
          "filter": {
    
    
            "match_all": {
    
    
              "boost": 1
            }
          },
          "gauss": {
    
    
            "price": {
    
    
              "origin": 50,
              "scale": 20,
              "offset": 50,
              "decay": 0.5
            },
            "multi_value_mode": "MIN"
          }
        }
      ],
      "score_mode": "multiply",
      "boost_mode": "sum",
      "max_boost": 3.4028235e+38,
      "boost": 1
    }
  }
}

java实例:

//原点
GeoPoint origin = new GeoPoint(40.15077, 116.28414);
//偏移量(offset):与原点相差在偏移量之内的值,也可以得到满分
String offset = "5km";
//衰减规模(scale):当值超出了原点到偏移量折叠范围,他所得的分数开始进行衰减
String scale = "10km";
//衰减值(decay):该字段可以被接受的值(默认0.5),相当于一个分阶段,具体效果与衰减的模式有关
double decay = 0.5;
GaussDecayFunctionBuilder location = ScoreFunctionBuilders.gaussDecayFunction("location", origin, scale, offset, decay);
GaussDecayFunctionBuilder price = ScoreFunctionBuilders.gaussDecayFunction("price", 50, 20, 50, decay);
FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = {
    
        
    new FunctionScoreQueryBuilder.FilterFunctionBuilder(location),
    new FunctionScoreQueryBuilder.FilterFunctionBuilder(price)
};
FunctionScoreQueryBuilder query = QueryBuilders.functionScoreQuery(filterFunctionBuilders).boostMode(CombineFunction.SUM);
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(query);

5. 关注我

搜索微信公众号:java架构强者之路
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/dwjf321/article/details/103975478
今日推荐