真实项目中使用到的ES自定义评分脚本

查询语句

POST /tm_index/_search
{
  "query": {
    "bool": {
      "filter": [
        { "match": { 
          "original": {
            "query": "你多少岁",
            "minimum_should_match": "69%"
          }
        }}
      ],
      "must": [
        { "function_score": {
          "functions": [
            {
              "filter": { "term": { "originalTag": "<1>1</1><2/><3>3</3>" } },
              "weight": 2 
            },
            {
              "script_score": {
              "script": { 
                "source": """
                    // 定义相似度计算函数(放于脚本顶部,str1是请求参数)
                    double calculate(String str, String str1) {
                        // 快速返回条件
                        if (str == null || str1 == null || str.isEmpty() || str1.isEmpty()) return 10.0;
                
                        int len = str.length(), len1 = str1.length();
                        int minLen = len < len1 ? len : len1;
                        int maxLen = len < len1 ? len1 : len;
                        double ratio = (double)minLen / maxLen;
                        if(ratio < 0.7) {
                            return 30;
                        }
                
                        String strLower = str.toLowerCase();
                        String str1Lower = str1.toLowerCase();
                
                        if(str.equals(str1)){
                            return 100;
                        }
                        if(strLower.equals(str1Lower)){
                            return 90;
                        }
                        // 预处理
                        char[] arr1 = strLower.toCharArray();
                        char[] arr2 = str1Lower.toCharArray();
                
                        // 动态规划数组
                        int[] dp = new int[len1 + 1];
                        for (int j = 0; j <= len1; j++) dp[j] = j;
                
                        for (int i = 1; i <= len; i++) {
                            int prevDiagonal = dp[0];
                            dp[0] = i;
                            char c1 = arr1[i-1];
                
                            for (int j = 1; j <= len1; j++) {
                                int cost = (c1 == arr2[j-1]) ? 0 : 1;
                                // 取最小值
                                int newVal = dp[j] + 1;
                                newVal = newVal < dp[j-1] + 1 ? newVal : dp[j-1] + 1;
                                newVal = newVal < prevDiagonal + cost ? newVal : prevDiagonal + cost;
                                prevDiagonal = dp[j];
                                dp[j] = newVal;
                            }
                        }
                        return 80.0 * (1.0 / dp[len1]);
                    }
                    String es2 = doc['original.keyword'].value;
                    String str2 = params.val2;
                    // 计算纯文本的评分
                    double textRatio = calculate(es2, str2);
                    return textRatio;
                """,
                "params": {
                  "val2": "你多少岁"
                }
              }
            }}
          ],
          "score_mode": "sum",
          "boost_mode": "replace"
        }}
      ]
    }
  }
}

自定义评分脚本

double calculate(String str, String str1) {
                        // 快速返回条件
                        if (str == null || str1 == null || str.isEmpty() || str1.isEmpty()) return 10.0;
                
                        int len = str.length(), len1 = str1.length();
                        int minLen = len < len1 ? len : len1;
                        int maxLen = len < len1 ? len1 : len;
                        double ratio = (double)minLen / maxLen;
                        if(ratio < 0.7) {
                            return 30;
                        }
                
                        String strLower = str.toLowerCase();
                        String str1Lower = str1.toLowerCase();
                
                        if(str.equals(str1)){
                            return 100;
                        }
                        if(strLower.equals(str1Lower)){
                            return 90;
                        }
                        // 预处理
                        char[] arr1 = strLower.toCharArray();
                        char[] arr2 = str1Lower.toCharArray();
                
                        // 动态规划数组
                        int[] dp = new int[len1 + 1];
                        for (int j = 0; j <= len1; j++) dp[j] = j;
                
                        for (int i = 1; i <= len; i++) {
                            int prevDiagonal = dp[0];
                            dp[0] = i;
                            char c1 = arr1[i-1];
                
                            for (int j = 1; j <= len1; j++) {
                                int cost = (c1 == arr2[j-1]) ? 0 : 1;
                                // 取最小值
                                int newVal = dp[j] + 1;
                                newVal = newVal < dp[j-1] + 1 ? newVal : dp[j-1] + 1;
                                newVal = newVal < prevDiagonal + cost ? newVal : prevDiagonal + cost;
                                prevDiagonal = dp[j];
                                dp[j] = newVal;
                            }
                        }
                        return 80.0 * (1.0 / dp[len1]);
                    }

根据纯文本和标签格式匹配语料。标签格式一致则加分。

加分逻辑如下:

{
   "filter": { "term": { "originalTag": "<1>1</1><2/><3>3</3>" } },
   "weight": 2 
}

上述自定义评分脚本问题

原本的需求:优先匹配纯文本一致的数据,纯文本一致的情况下,结构相同的数据评分要更高。

上述脚本存在的问题,当匹配的数据存在编辑距离时,可能存在满足以下条件的两条数据:

        1、后一条的编辑距离比前一条的编辑距离大(即前一条数据与待匹配文本更加相似)

        2、后一条的结构与待匹配文本的结构一致

从需求上来说,前一条数据的评分理论上应该大于后一条数据的评分。

但是经过测试,当编辑距离大概超过6时,就容易出现编辑距离大于6的数据的评分比编辑距离为6的数据的评分更高。

5给编辑距离,16分

6个编辑距离,13.333分

7个编辑距离,11.428分

可以看到,如果存在编辑距离为7的数据,并且结构一致,则分数为11.428+2=13.428分,大于了编辑距离为6的数据的评分。与需求不符。

第二次调整自定义脚本

之前判断结构一致时,是直接加上固定的分数2分,现调整为动态加分,也就是根据编辑距离加分。具体加分为:1/(编辑距离+1)。

但是经过测试,也会存在第一次的脚本问题。大概是当编辑距离大于81的时候就会出现同样问题。

编辑距离  结构不一样   结构一样

0              100                101    

1              80                  80.5

2              40                  40.333

3              26.666           26.91

..

6              13.333           13.476

7              11.428           11.553

..

100           0.8                 0.809

101           0.792             0.802

1000         0.08               0.08099

1001         0.0799           0.08092

最后一次调整自定义脚本

最终打分逻辑:

编辑距离为x。

结构不一致,打分80/x。

结构一致,打分80/(x-0.5)。

解释:即使后一个编辑距离(x)大于前一个编辑距离(y),并且后一个的结构一致,那么x-0.5也始终是大于y的,满足需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值