查询语句
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的,满足需求。