Elasticsearch 定制评分(自定义评分)
Elasticsearch 的相似度算法被定义为检索词频率/反向文档频率, TF/IDF 。
一. 相关概念:
- 检索词频率:tf
词 t 在文档 d 的词频( tf )是该词在文档中出现次数的平方根。
tf(t in d) = √frequency
检索词在该字段出现的频率?出现频率越高,相关性也越高。 字段中出现过 5 次要比只出现过 1 次的相关性高。
- 反向文档频率:idf
词 t 的逆向文档频率( idf )是:索引中文档数量除以所有包含该词的文档数,然后求其对数。
idf(t) = 1 + log ( numDocs / (docFreq + 1))
每个检索词在索引中出现的频率?频率越高,相关性越低。检索词出现在多数文档中会比出现在少数文档中的权重更低。
二、计算公式为:
score(q,d) = queryNorm(q) · coord(q,d) · ∑ (tf(t,d) · idf(t)² · t.getBoost() · norm(t,d))
其它参数定义:
- 字段长度准则:norm
字段长度归一值( norm )是字段中词数平方根的倒数。
norm(d) = 1 / √numTerms
字段的长度是多少?长度越长,相关性越低。 检索词出现在一个短的 title 要比同样的词出现在一个长的 content 字段权重更大。
- 查询归一因子
查询归一因子 ( queryNorm
)试图将查询 归一化 ,这样就能将两个不同的查询结果相比较。
这个因子是在查询过程的最前面计算的,具体的计算依赖于具体查询
queryNorm = 1 / √sumOfSquaredWeights
sumOfSquaredWeights
是查询里每个词的 IDF 的平方和。
以上是对于一个词项检索时的相关度计算,当查询多个词项时,得出多个相关度,则需要按照向量空间模型来计算整体的相似度:
向量空间模型:vector
向量空间模型(vector space model) 提供一种比较多词查询的方式,单个评分代表文档与查询的匹配程度
在向量空间模型里,向量空间模型里的每个数字都代表一个词的 权重 ,与 词频/逆向文档频率(term frequency/inverse document frequency) 计算方式类似。
3、控制相关度
一般来说,控制相关度的需求,分为两种:
- 忽略TF/IDF
即不需要评分,可以使用constant_score来达成,在constant_score
查询中,它可以包含查询或过滤,为任意一个匹配的文档指定评分1
,忽略 TF/IDF 信息。 - 定制评分
function_score
查询 是用来控制评分过程的终极武器,它允许为每个与主查询匹配的文档应用一个函数,以达到改变甚至完全替换原始查询评分_score
的目的。本文主要介绍使用script_score
函数。
使用脚本计算评
script_score
自定义脚本可以完全控制评分计算:
{
"function_score": {
"functions": {
"script_score": {
"script": "doc['price'].value + doc['margin'].value"
}
}
}
}
4、Painless
es脚本引擎,简单安全,无痛使用,Painless使用白名单来限制函数与字段的访问,针对es的场景来进行优化,只做es数据的操作,更加轻量级。
Painless中变量可以声明为基本数据类型、引用类型、字符串、void
(不返回值)、数组以及动态类型。其支持下面基本类型:
byte, short, char, int, long, float, double, boolean.声明变量与java类似:
int i = 0; double a; boolean g = true;
数组类型支持一维和多维,初始值为null。与引用类型一样,使用new关键字,并为每个维度设置中括号
int[] x = new int[2];
x[0] = 3;
x[1] = 4;
int[] b = new int[] {1,2,3,4,5};
painless支持动态类型,elasticsearch会自动推断类型
def a = 1;
def b = "foo";
def[][] h = new def[2][2];
条件语句和运算符
Painless包含完整的操作符列表,除了它们的优先级和结合性之外,这些操作符与其他高级语言几乎兼容。
if (doc['foo'].value = 5) {
doc['foo'].value *= 10;
}
else {
doc['foo'].value += 10;
}
Painless支持if else
,但不支持else if
或switch
循环
def total = 0;
for (def i = 0; i < doc['scores'].length; i++) {
total += doc['scores'][i];
}
return total;
5、控制相关度实践
该实例中将使用script_score,将评分设置为:doc['download_cnt'].value * 2.5 +doc['replication_cnt'].value * 1.2
{
"query": {
"function_score": {
"query": {
"match": {
"name": "1"
}
},
"functions": [
{
"script_score": {
"script": {
"params": {
"download_ratio": 2.5,
"replication_ratio": 1.2
},
"lang": "painless",
"inline": "doc['download_cnt'].value * params.download_ratio + doc['replication_cnt'].value * params.replication_ratio"
}
}
}
]
}
}
}
_search操作中所有的返回值,都可以通过一个
map
类型变量doc获取。和所有其他脚本语言一样,用[]获取map
中的值。这里要强调的是,doc只可以在_search中访问到