Elasticsearch 的相似度算法被定义为检索词频率/反向文档频率, TF/IDF 。

一. 相关概念:

  1. 检索词频率:tf

词 t 在文档 d 的词频( tf )是该词在文档中出现次数的平方根。

tf(t in d) = √frequency

检索词在该字段出现的频率?出现频率越高,相关性也越高。 字段中出现过 5 次要比只出现过 1 次的相关性高。

  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))  

其它参数定义:

  1. 字段长度准则:norm

字段长度归一值( norm )是字段中词数平方根的倒数。

norm(d) = 1 / √numTerms

字段的长度是多少?长度越长,相关性越低。 检索词出现在一个短的 title 要比同样的词出现在一个长的 content 字段权重更大。

  1. 查询归一因子

查询归一因子queryNorm )试图将查询 归一化 ,这样就能将两个不同的查询结果相比较。

这个因子是在查询过程的最前面计算的,具体的计算依赖于具体查询

queryNorm = 1 / √sumOfSquaredWeights 

sumOfSquaredWeights 是查询里每个词的 IDF 的平方和。

以上是对于一个词项检索时的相关度计算,当查询多个词项时,得出多个相关度,则需要按照向量空间模型来计算整体的相似度:

向量空间模型:vector

向量空间模型(vector space model) 提供一种比较多词查询的方式,单个评分代表文档与查询的匹配程度

在向量空间模型里,向量空间模型里的每个数字都代表一个词的 权重 ,与 词频/逆向文档频率(term frequency/inverse document frequency) 计算方式类似。

3、控制相关度

一般来说,控制相关度的需求,分为两种:

  1. 忽略TF/IDF
    即不需要评分,可以使用constant_score来达成,在 constant_score 查询中,它可以包含查询或过滤,为任意一个匹配的文档指定评分 1 ,忽略 TF/IDF 信息。
  2. 定制评分
    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 ifswitch

循环

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中访问到

标签: none

添加新评论