首页 > 其他分享 >ES搜索框架--自定义评分规则

ES搜索框架--自定义评分规则

时间:2023-04-10 20:45:12浏览次数:40  
标签:分数 函数 自定义 -- 评分 score mode ES

一、评分规则需求

按照用户画像(不同的标签分数)和用户省份在用户查询时,对查询结果进行自定义评分


二、ES自定义评分方式

参考:

博客:https://blog.csdn.net/W2044377578/article/details/128636611

官网:https://www.elastic.co/guide/en/elasticsearch/guide/master/function-score-query.html

重点仔细看官方文档,介绍的很详细,下面只是我的案例。

image

1.functions,weight权重形式

functions内部可以组合多种自定义评分函数+查询过滤函数

{
  "explain":true,
  "query": {
    "function_score": {
      //1.匹配:只有在通过这里的基本匹配后才有机会对结果进行自定义评分,即满足查询是基本要求
      "query": { "match": {"policyTitle": "儿童教育"} },

      //functions中可以放置多种评分规则,使用score_mode定义这些评分规则的总分模式
      //评分规则:过滤label中是否为指定标签以及province是否为指定省份,如果是则返回指定权重分数*随机数评分,如果两者同时满足则评分求和
      "functions": [
        {
          "filter": { "match": { "label": "教育" } },
          //因为在特定查询上设置的boost提升值会被标准化,而对于此评分函数使用weight提升则不会
          //可以为数组functions中的每个函数定义weight,使其与相应函数计算的分数相乘。如果在没有任何其他函数声明的情况下给出 weight,则仅返回weight
          "random_score": {}, 
          "weight": 10
        },
        {
          "filter": { "match": { "province": "北京市" } },
          "weight": 10
        }
      ],

      //max_boost表示自定义的函数的分数不能超过指定分数
      "max_boost": 100,
      //总评分的评分规则:score_mode为自定义的函数(functions)的计算规则,boost_mode为查询分数和函数分数的计算规则
      //方法中分数的最低分为1(即即使设置权重为0,或者filter结果完全不匹配,仍然会返回结果1(即按理结果因当为0时)。其他结果则正常返回(小于1也正常返回))
      "score_mode": "sum",
      //boost_mode=replace表示仅使用函数分数,忽略查询分数
      "boost_mode": "sum",
      //min_score表示结果列表中会显示的最低分数(总分)
      "min_score": 0
    }
  }
}


2.script_score脚本形式

{
  "query": {
    "function_score": {
      //1.查询评分
      "query": { "match": {"province": "湖北省"} },

      //2.script_score评分函数
      //在 Elasticsearch中,所有文档得分都是正的 32 位浮点数
      //script_score函数允许包装另一个查询并自定义它的评分,而且可以使用脚本表达式对索引中数字类型的字段进行计算评分
      "script_score": {
        "script": {
            "source": "Math.log(2 + doc['provinceNum'].value)"
        }
      },
      //max_boost表示自定义的函数的分数不能超过指定分数
      "max_boost": 42,
      //总评分的评分规则:score_mode为自定义的函数(functions)的计算规则,boost_mode为查询分数和函数分数的计算规则
      "score_mode": "max",
      "boost_mode": "sum",
      //min_score表示结果列表中会显示的最低分数(总分)
      "min_score": 0
    }
  }
}


3.random_score随机数

{
  "query": {
    "function_score": {
      "query": { "match": {"province": "湖北省"} },

      //3.random_score随机评分函数
      //生成0到1但不包括1的随机数评分,通过设置种子和字段的方式使随机数评分可以重现
      "random_score": {
        "seed": 10,
        "field": "id"
      },
      "boost_mode": "sum"
    }
  }
}


4.field_value_factor影响因子形式

{
  "query": {
    "function_score": {
      "query": { "match": {"province": "湖北省"} },

      //4.field_value_factor函数允许您使用文档中的字段(数值型)来影响分数。
      //它类似于使用script_score函数,但是它避免了编写脚本的开销。
      "field_value_factor": {
        "field": "labelNum",
        "factor": 1.2,
        "modifier": "sqrt",
        //missing:如果文档该字段缺失值,则使用该值
        "missing": 1
      },
      "boost_mode": "sum"
    }
  }
}


5.衰减函数

{
  "query": {
    "function_score": {
      "query": { "match": {"province": "湖北省"} },

      //5.衰减函数对文档进行评分,该函数根据文档的数字字段值与用户给定原点的距离而衰减。
      //指定的字段必须是数字、日期或地理点字段。
      //衰减的形状:linear(线性衰减)、exp(指数衰减)、gauss(正常衰减),结合图像理解
      "linear": { 
        "pubTime": { 
          //原点:必须以数字字段的数字、日期字段的日期和地理字段的地理点的形式给出。地理和数字字段必填。
          //对于日期字段,默认值为now,支持使用日期公式 (例如 now-1h)
          "origin": "2021-01-01", 
          //与原点的距离:在距离范围内,文档分数按照规则从1开始衰减到decay
          //对于地理字段:可以定义为数字+单位(1km,12m,...)。默认单位是米。
          //对于日期字段:可以定义为数字+单位(“1h”、“10d”、… )。默认单位是毫秒。
          //对于数字字段:任何数字。
          "scale": "30d",
          //偏移量:在(原点+-偏移量)内的文档分数=1,在(原点-scale-offset和原点+scale+offset)范围内的文档分数将按照规则进行衰减,直到达到decay的低点
          //默认为0,即文档分数=1的点只有原点,呈峰状;设定值小则文档间区别较大,否则一定范围内的文档会难以区分
          "offset": "10d",         
          "decay": 0.5   
          }
      },
      //这里就需要进行乘积评分了,因为gauss给出的是1以内的一个权重分数,如果字段对应为空函数返回为1
      //改变为空字段返回0的方式:https://github.com/elastic/elasticsearch/issues/18892
      "boost_mode": "multiply"
    }
  }
}

结合参数与下方的图像函数进行返回值的理解:

image


以上这些评分规则都可以综合起来写入functions中,于是思考后我得到了下面的请求来实现我的需求:

{
  "explain": true, 
  "query": {
    "function_score": {
      "query": {
        "match": {
          "policyTitle": "政府"
        }
      },
      //设定在
      "functions": [
        //在省份符合用户省份时:匹配省份id(仅此id)对应的得分为1
        {
          "linear": {
            "provinceNum": {
              "origin": 0,
              "scale": 1,
              "offset": 0,
              "decay": 0.1
            }
          }
        },
        //在标签值符合用户标签时:返回用户在此标签上的权重
        {
          "script_score": {
            "script": {
              "source": "if(doc['labelNum'].value==13){return 1.0;}else if(doc['labelNum'].value==17){return 0.5;}else if(doc['labelNum'].value==18){return 0.3;}else if(doc['labelNum'].value==11){return 0.2;}",
              "lang": "painless"
            }
          }
        }
      ],
      "score_mode": "sum",
      //自定义评分结果与查询评分结果相乘
      "boost_mode": "multiply"
    }
  }
}


三、Java实现自定义评分

参考:https://blog.csdn.net/xiaoll880214/article/details/86716393

代码:

functions内部构造,然后将得到的functions与查询语句一起放入functionScore,设定相应的mode计算方式就行(下面的是不可运行的,仅供参考,需要注意的是functions的层层包装和内部的构建函数使用方式)

public FunctionScoreQueryBuilder.FilterFunctionBuilder[] changeFunction(long userId,String province,Map<String,Float> face){
        //userId==-1表示游客登录,不需要个性化,只用根据省份
        double labelNumScore=faceService.labelNum;
        double maxLabelScore= faceService.maxLabelScore;
        double minLabelScore= faceService.minLabelScore;
        List<String> labels=faceService.labels;
        String[] provinces= PolicyService.chinaProvince;
        List<String> provinceList = List.of(provinces);
        StringBuilder scoreScript= new StringBuilder();
        //记录function的数量
        int functionNum=0;
        FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders;
        //1.游客登录,仅记录省份影响,数组长度=1(设置过长会导致function==null产生错误)
        if(userId==-1){
            filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[1];
        }
        //2.非游客登录:添加对应用户标签画像,标签score_script脚本自定义评分
        else {
            filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[2];
            //对省份和标签的自定义结果进行求和
            Object[] label= face.keySet().toArray();
            List<Short> labelNum=new ArrayList<>();
            // 将字符串标签转换为数字编号形式,用于排序规则的编写
            for (Object o : label) {
                labelNum.add((short) labels.indexOf(o));
            }
            //label在用户占比超过25%,认为这个label是有利的,此时匹配省份=0.5,标签>1,score评分升高
            //若匹配省份=0,标签>1,score评分升高也合理
            //若占比小于25%,则此标签对用户没有明显影响,此时匹配省份=0.5,标签=1,score评分升高
            //若匹配省份=0,标签=1,则score评分保持不变(也合理,如果查询评分非常高则足以超越前面的内容)
            //对标签的影响进行一定限制,避免查询结果完全由标签控制
            double labelScore=Math.min(face.get(label[0]) * labelNumScore,maxLabelScore);
            labelScore=Math.max(labelScore,minLabelScore);
            scoreScript.append("if(doc['labelNum'].value==").append(labelNum.get(0)).append("){return ").append(labelScore).append(";}");
            for (int i=1;i<label.length;i++){
                if(face.get(label[i]) * labelNumScore<minLabelScore){
                    continue;
                }
                labelScore=Math.min(face.get(label[i]) * labelNumScore,maxLabelScore);
                scoreScript.append("else if(doc['labelNum'].value==").append(labelNum.get(i)).append("){return ").append(labelScore).append(";}");
            }
            scoreScript.append("else {return 1.0}");
            //**层层包装填充放到functions中:https://blog.csdn.net/xiaoll880214/article/details/86716393
            ScoreFunctionBuilder<ScriptScoreFunctionBuilder> labelScoreFunction = ScoreFunctionBuilders.scriptFunction(new Script(scoreScript.toString()));
            FunctionScoreQueryBuilder.FilterFunctionBuilder labelFunction=new FunctionScoreQueryBuilder.FilterFunctionBuilder(labelScoreFunction);
            filterFunctionBuilders[functionNum]=labelFunction;
            functionNum++;
        }
        //2.省份num衰减评分
        //利用衰减函数,设定在给定省份id(仅此id)对应的得分为0.5(以id+偏移量为原点,搜索偏移量范围得分为decay)
        ScoreFunctionBuilder<LinearDecayFunctionBuilder> provinceScoreFunction = ScoreFunctionBuilders.linearDecayFunction("provinceNum", provinceList.indexOf(province)+0.1, 0.1, 0, 0.5);
        FunctionScoreQueryBuilder.FilterFunctionBuilder provinceFunction=new FunctionScoreQueryBuilder.FilterFunctionBuilder(provinceScoreFunction);
        filterFunctionBuilders[functionNum]=provinceFunction;

        return filterFunctionBuilders;
    }

标签:分数,函数,自定义,--,评分,score,mode,ES
From: https://www.cnblogs.com/Studywith/p/17304242.html

相关文章

  • InnoDB引擎之内存与磁盘结构
     一、逻辑存储结构      1、表空间(Tablespace)表空间(Tablespace)是一个逻辑容器,在一个表空间中可以有一个或多个段,一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间等。 ......
  • 逆序对的数量(Acwing)
     1.首先要想到排序问题中的归并排序来解决此问题;其次我们要看逆序数的定义是i<j&&a[i]>a[j];下面就来模拟一下;1324789567 ......
  • 分页查询的实现过程
    分页查询目前我看到的最简单的分页查询的实现就是直接在servlet查询得到list列表之后,在jsp页面或者html页面引用<%进行实现1、首先,获取到session保存的list数据List<docu>list=(List<docu>)request.getSession().getAttribute("list");//此处是取出所存储的数据2、设置......
  • 异常检测 | 迁移学习《Anomaly Detection in IR Images of PV Modules using Supervis
    论文信息论文标题:AnomalyDetectioninIRImagesofPVModulesusingSupervisedContrastiveLearning论文作者:AbhayRawat, IshaDua, SauravGupta, RahulTallamraju 论文来源:LukasBommes,MathisHoffmann,ClaudiaBuerhop-Lutz,TobiasPickel,JensHauch,Christ......
  • Django笔记二十一之使用原生SQL查询数据库
    本文首发于公众号:Hunter后端原文链接:Django笔记二十一之使用原生SQL查询数据库Django提供了两种方式来执行原生SQL代码。一种是使用raw()函数,一种是使用connection.cursor()。但是官方还是推荐在使用原生SQL之前,尽量的先去探索一下QuerySet提供的各种API。目前......
  • AHOI2023 游记
    Day-1摆烂。问题不大。守门就是胜利。打完省选要滚回去学whk了,寄。Day0试机。无面基。没啥好说的。rp++.Day1面到了Aehnuwx(好耶8:30开始。开场15min过了T1。然后同时开T2和T3。T2读完感觉不太妙,自己并不擅长解决这样的图论问题。T3部分分好像很多......
  • 57、K8S-监控机制-Prometheus-PromQL基础-运算符、聚合、功能函数
    Kubernetes学习目录1、数据基础1.1、时间序列1.1.1、介绍时间序列数据:按照时间顺序记录系统、设备状态变化的数据,每个数据称为一个样本;数据采集以特定的时间周期进行,因而,随着时间流逝,将这些样本数据记录下来,将生成一个离散的样本数据序列;该序列也称为向量(Vector);而将多个序......
  • Java中创建线程的方式以及线程池创建的方式、推荐使用ThreadPoolExecutor以及示例
    场景Java中创建线程的方式有三种1、通过继承Thread类来创建线程定义一个线程类使其继承Thread类,并重写其中的run方法,run方法内部就是线程要完成的任务,因此run方法也被称为执行体,使用start方法来启动线程。2、通过实现Runanle接口来创建线程首先定义Runnable接口,并重写Runnab......
  • ROS系统(3)创建工作空间
    在主文件夹中打开终端,输入命令:mkdir-pcatkin_ws1/src  在主文件夹中就创建了一个名为catkin_ws1的文件夹,打开ws1文件夹里面还有一个名为src的文件夹  通过命令初始化创建工作空间,进入到catkin_ws1/src中(输入命令:cdcatkin_ws1/src/)输入命令catkin_init_workspace......
  • 百鸡问题以及借书方案知多少
    百钱百鸡问题一只公鸡值五钱,一只母鸡值三钱,三只小鸡值一钱,现在要用百钱买百鸡,请问公鸡,母鸡,小鸡各多少只根据数学知识可以转化为方程组: 解题思路:根据鸡的数量不超过100只以及总价格不超过100,可以得出:公鸡数量为0-20,母鸡数量为0-33,小鸡数量为0-100。运用for循环嵌套实现公鸡,母......