这篇分析elk 相关性检索和组合查询:
在全文检索中,检索结果与查询条件的相关性是一个极为重要的问题,优秀 的全文检索引擎应该将那些与查询条件相关性高的文档排在最前面。想象一下。 如果满足查询条件的文档成千上万,让用户在这些文档中再找出自己最满意的那 一条,这无异于再做一次人工检索。用户一般很少会有耐心在检索结果中翻到第 3 页,所以处理好检索结果的相关性对于检索引擎来说至关重要。
Google
公司就 是因为发明了 Page Rank
算法,巧妙地解决了网页检索结果的相关性问题,才在 众多搜索公司中迅速崛起。 相关性问题有两方面问题要解决, 一是如何评价单个查询条件的相关性, 二是如何将多个查询条件的相关性组合起来。
相关性评分
全文检索与数据库查询的一个显著区别, 就是它并不一定会根据查询条件 做完全精确的匹配。除了模糊查询以外,全文检索还会根据查询条件给文档的相 关性打分并排序,将那些与查询条件相关性高的文档排在最前面。相关性 ( Relevance)或相似性
(Similarity)
是指两个事物间相互关联的程度,在检索领城特
指检索请求与检索结果之间的相关程度。在
Elaticsearch
返回的每条结果中都会 包含一个_ score
字段,这个字段的值就是当前文档匹配检索请求的相关性评分, 我们也可以称为相关度。 解决相关性问题的核心是计算相关度的算法和模型,相关度算法和模型是全 文检索引章最重要的技术之一。相关度算法和相关度模型并非完全相同的概念, 相关度模型可以认为是具有相同理论基础的算法集合。所以在实际应用时都是指 定到具体的相关度算法,相关度模型则是从理论层面对相关度算法的归类。
相关度模型
Elasticsearch
支持多种相关度算法,它们通过类型名称来标识,包括
boolean
、 BM25、
DFR
等等很多。这些算法分别归属于几种不同的理论模型,它们是布尔 模型、向量空间模型、概率模型、语言模型等。
布尔模型
布尔模型
( Boolean Model)
是最简单的相关度模型,最终的相关度只有
1
或
0 两种。如果检索中包含多个查询条件,则查询条件之间的相关度组合方式取决它 们之间的逻辑运算符,即以逻辑运算中的与、或、非组合评分。文档的最终评分 为 1
时会被添加到检索结果中,而评分为
0
时则不会出现在检索结果中。这与使 用 SQL
语句查询数据库有些类似,完全根据查询条件决定结果,非此即彼。在 Elnticearch 支持的相关度算法中,
boolean
算法即采用布尔模型,有些地方也部 分地采用了布尔模型。
向量空间模型
向量空间模型
(Vector Space Mode)
组合多个相关度时采用的是基于向量的算 法。在向量空间模型中,多个查询条件的相关度以向量的形式表示。向量实际上 就是包含多个数的一维数组,例如[1
,
2, 3,4, 5, 6]
就是一个
6
维向量,其中每个 数字都代表一个查询条件的相关度。文档对于 n
个查询条件会形成一个
n
维的向 量空间,如果定义 1
个查询条件最佳匹配的
n
维向量,那么与这个向量越接近则 相关度越高。从向量的角度来看,就是两个向量之间的夹角越小相关度越高,所 以 n
个相关度的组合就转换为向量之间夹角的计算。 简单理解,可以只考虑一个二维向量,也就是查询条件只有两个,这样就可 以将两个相关度映射到二维坐标图的X
轴和
Y
轴上。假设两个查询条件权重相同, 那么最佳匹配值就可以设置为[1, 1]
。如果某文档匹配了第一个条件,部分地匹配 了第二个条件,则该文档的向量值为[1, 0.2]
。将这两个向量绘制在二维坐标图中, 就得到了它们的夹角。 对于多维向量说,线性代数提供了余弦近似度算法,专门用于计算两个多 维向量的夹角。
注:余弦相似性通过测量两个向量的夹角的余弦值来度量它们之间的相似性。
0
度角的余弦值是
1
,而其他任何角度的余弦值都不大于
1
;并且其最小值是
-1
。
从而两个向量之间的角度的余弦值确定两个向量是否大致指向相同的方向。两个
向量有相同的指向时,余弦相似度的值为
1
;两个向量夹角为
90
°时,余弦相似
度的值为
0
;两个向量指向完全相反的方向时,余弦相似度的值为
-1
。这结果是
与向量的长度无关的,仅仅与向量的指向方向相关。余弦相似度通常用于正空间,
因此给出的值为
-1
到
1
之间。
注意这上下界对任何维度的向量空间中都适用,而且余弦相似性最常用于高
维正空间。例如在信息检索中,每个词项被赋予不同的维度,而一个维度由一个
向量表示,其各个维度上的值对应于该词项在文档中出现的频率。余弦相似度因
此可以给出两篇文档在其主题方面的相似度。
概率模型
概率模型是基于概率论构建的模型,
BM25
、
DFR
、
DFI
都属于概率模型中 的一种实现算法, 背后有着非常严谨的概率理论依据。以其中最为流行的 BM25 为例,它背后的概率理论是贝叶斯定理,而这个定理在许多领城中都有广泛的应 用。BM25 法将检索出来的文档
(D)
分为相关文档
(R)
和不相关文档
(NR)
两类,使用 P(R|D)代表文档属于相关文档的概率,而使用
P(NR|D)
表示文档属于不相关文档 的概率,则当 P(R|D)>P(NR|D)
时认为这个文档与用户查询相关。根据贝叶斯公式 将 P(R|D)>P(NR|D)
转换为对某个比值的计算。在此基础上再进行一此转换,就可 以得到不同的相关度算法。
语言模型
语言模型最早并不是应用于全文检索领域,而是应用于语音识别、机器翻译、 拼写检查等领域。在全文检索中,语言模型为每个文档建立不同的计算模型,用 以判断由文档生成某一查询条件的概率是多少,而这个概率的值就可以认为是相 关度。可见,语言模型的与其他检索模型正好相反,其他检索模型都是从查询条 件查找满足条件的文档,而语言模型则是根据文档推断可能的查询条件。
TF/IDF
对于一篇几百字几千字的文章,如何生成足以准确表示该文章的特征向量 呢?就像论文一样,摘要、关键词毫无疑问就是全篇最核心的内容,因此,我们 要设法提取一篇文档的关键词,并对每个关键词计算其对应的特征权值,从而形 成特征向量。这里涉及一个非常简单但又相当强大的算法,即 TF-IDF
算法。 TF/IDF 实际上两个影响相关度的因素,即
TF
和
IDF.
其中,
TF
是
词项频率
简 称词频
,指一个词项在当前文档中出现的次数,而
IDF
则是
逆向文档频率
,指词 项在所有文档中出现的次数。 Elasticsearch 提供的几种算法中都或多或少有 TF/IDF
的思想,例如
BM25
算 法虽然是通过概率论推导而来,但最终的计算公式与 TF/IDF
在本质上也是一致 的。TF/IDF 算法的核心思想是
TF
越高则相关度越高,而
IDF
越高相关度越低。 TF 对相关度的响比较容易理解,但
IDF
为什么会在词项出现次数多的时候反 而相关度低呢?
举例来说,如果使用
"elasticsearch
全文检索
"
两个词项做检索,文档中 “elasticsearch"
出现次数高的文档比“全文检索”出现次数高的文档相关度要高。 这是因为“elasticsearch "
是专业性比较强的词汇,更加专有,它在其他文档中出 现的次数会比较少,也就是 IDF
低,而“全文检索”虽然也是专业性词汇,但它
覆盖的面要比“
elasticsearch "
更广泛,所以它在其他文档中出现的次数会比较 高,也就是 IDF
高。换句话说,介绍
elasticsearch
的文章大概率会提到全文检索。 但介绍全文检索的文章则不一定会提到 elasticsearch
,比如一篇介绍
MongoDB 的文章大概率会提到全文检索,但是这样的文章与“elasticsearch
全文检索”的 相关度不高。 可见,在使用 TF/IDF
计算评分时必须要用到词项在文档中出现的频率,即 词频。默认情况下文档 text
类型字段在编入索引时都会记录词频。 Elasticsearch 中的
classic
算法实际上是使用
Lucene
的实用评分函数
( Practical Scoring Function) ,这个评分函数结合了布尔模型、
TF/IDF
和向量空间模型来共
同计算分值。该算法是早期
Elatisearch
运算相关度的算法,现在已经改为
BM25 了。
BM25
BM25
是
Best Match25
的简写,由于最早应用于一个名为
Okapi
的系统中, 所以很多文献中也称之为 Okapi BM25
。
BM25
算法被认为是当今最先进的相关 度算法之 ,Elasticsearch
文档字段的默认相关度算法就是采用
BM25
,它属于概 率模型,依据贝叶斯公式,经过一系列的严格推导以后,得出了一个关于 IDF
的 公式
![](https://i-blog.csdnimg.cn/blog_migrate/d0b6abd16605dbd81bf2e59c1ec8e40b.png)
词频饱和度
所谓词频饱和度指的是当词频超过一定数量之后,它对相关度的影响将趋于 饱和。换句话说,词频 10
次的相关度比词频
1
次的分值要大很多,但
100
次与 10 次之间差距就不会那么明显了。在
BM25
算法中,控制词频饱和度的参数是 k1,默认值为
1.2
。参数
k1
的值越小词频对相关度的影响就会越快趋于饱和,而 值越大词频饱和度变化越慢。举例来说,如果将
k1
,设置为
1
,词频达到
10
时就会趋于饱和
;
而当
k1
设 置为 100
时词频在
100
时才会趋于饱和。一般来说
k1
的取值范围为
[1.2, 2.0]
长度归一化
一般来说, 查询条件中的词项出现在较短的文本中,比出现在较长的文本 中对结果的相关性影响更大。 举例来说,如果一篇文章的标题中包含 elasticsearch,
那么这篇文章是专门 介绍elasticsearch
的可能性比只在文章内容中出现
elasticsearch
的可能要高很多。 但这种比较其实是建立在两个不同的字段上,而在实际检索时往往是针对相同的 字段做比较。 比如在两篇文章的标题中都出现了 elasticsearch, 那么哪一篇文章的相关度 更高呢? BM25 针对这种情况对文本长度做了所谓的归一化处理,即考虑当前文档字 段的文本长度与所有文档的字段平均长度的比值,而这个比值就是长度归一化因 素。为了控制长度归一化对相关度的影响,在长度归一化中加了一个控制参数 b。 这个值的取值范围为[0.0, 1.0],
取值
0.0
时会禁用归一化,而取值
1.0
则会完全启
用归一化,默认值为
0. 75
。
相关度解释
相关度算法可通过
text
或
keyword
类型字段的
similarity
参数修改,也就是 说相关度算法不针对整个文档而是针对单个字段,它的默认值是 BM25
。 当然 Elasticsearch
相关度评分比这里介绍的内容要复杂得多,可以通过在查 询时添加 explain
参数查看评分解释。例如
:
POST /kibana_sample_data_logs/_search
{
"query": {
"match": {
"message": "chrome"
}
},
"explain": true
}
"query": {
"match": {
"message": "chrome"
}
},
"explain": true
}
除此之外,通过
_explain
接口也可以实现类似的功能,不同的是
_explain
接 口查看的是单个文档与检索条件的相关度评分解释。例如:
POST /kibana_sample_data_logs/_explain/ni-ao3IBOcvNJ8V9P3JN
{
"query": {
"match": {
"message": "chrome"
}
}
}
"query": {
"match": {
"message": "chrome"
}
}
}
我们可以大致了解下
Elasticsearch
是如何进行评分的:
![](https://i-blog.csdnimg.cn/blog_migrate/0ef1fb6856a010ac3908b21161c0ffbd.png)
相关度权重
在一些情况下需要将某些字段的相关度权重提升,以增加这些字段对检索结 果相关性评分的影响。比如,同时使用对文章标题 tile
字段和文章内容
content 字段做检索,tile
字段化相关性评分中的权重应该比
content
字段高一些, 这时就可以将 tile
字段的相关度评分权重提高。 所以相关度权重提升一般都是在多个查询条件时设置。提升相关度权重有多
种办法,下面分别来看一下。
boost
参数
boost
参数可以在创建索引时直接设置给字段,也可以在执行检索时动态更 改。如果不做更改,boost
参数的默认值为
1
。但并非所有类型的字段都可以设 置 boost
,在创建索引时设置
boost
参数并不是一个好的方法,因为这个参数在
索引创建以后就不能再更改而降低了灵活性,所以在
Elasticsearch
版本
5
中就已 经被废止。 所以更好的方式是检索时提升查询条件的相关度权重,几乎前面介绍的所有 DSL 查询都支持通过
boost
参数设置查询条件的相关度权重。例如
:
POST /kibana_sample_data_flights/_search
{
"query": {
"range": {
"AvgTicketPrice": {
"gte": 1000,
"lte": 1200,
"boost": 2
}
}
}
}
"query": {
"range": {
"AvgTicketPrice": {
"gte": 1000,
"lte": 1200,
"boost": 2
}
}
}
}
通过
boost
参数设置查询条件的相关度权重主要用于多查询条件时调整每个 查询条件在相关度计算中的权重。
组合查询与相关度组合
相关性问题不仅要解决单个查询条件的相关度计算,还要考虑如何将多个查 询条件产生的相关度组合起来。而相关度组合问题主要出现在组合查询中,所以 接下来我们就要了解下组合查询及相关度组合问题。 组合查询可以将通过某种逻辑将子查询组合起来,实现对多个字段与多个查 询条件的任意组合。组合查询组合的子查询不仅可以是基于词项或基于全文的子 查询,也可以是另一个组合查询。 单纯从组合查询的使用上来看,组合查询并不复杂,复杂的是组合多个子查 询相关度的逻辑,这也是它们的核心区别之一。
bool
组合查询
bool
组合查询将一组布尔类型子句组合起来, 形成个大的布尔条件。 通过 SQL 语言查询数据时,如果一条数据不满足
where
子句的查询条件,这条记录 将不会作为结果返回。 但 Elasticsearch
的
bool
组合查询则不同,在它的子句中,一些子句的确会决
定文档是否会作为结果返回,而另一些子句则不决定文档是否可以作为结果,但 会影响到结果的相关度。 bool组合查询可用的布尔类型子句包括
must
、filter、
should
和
must _not
四 种,它们接收参数值的类型为数组。must
查询结果中必须要包含的内容,影响相关度
filter
查询结果中必须要包含的内容,不会影响相关度
should
查询结果非必须包含项,包含了会提高分数,影响相关度
must_not
查询结果中不能包含的内容,不会影响相关度 可见,filter
和
must _not
单纯只用于过滤文档,而它们对文档相关度没有 任何影响。换句话说,这两种子句对查询结果的排序没有作用。在这四种子句中, should 子句的情况有些复杂。首先它的执行结果影响相关度,但在是否过滤结 果上则取决于上下文。当 should
子句与
must
子句或
filter
子句同时出现在子句 中时,should
子句将不会过滤结果。也就是说,在这种情况下,即使
should
子 句不满足,结果也会返回。例如:
POST /kibana_sample_data_logs/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"message": "firefox"
}
}
],
"should": [
{
"term": {
"geo. src": "CN"
}
},
{
"term": {
"geo. dest": "CN"
}
}
]
}
}
}
"query": {
"bool": {
"must": [
{
"match": {
"message": "firefox"
}
}
],
"should": [
{
"term": {
"geo. src": "CN"
}
},
{
"term": {
"geo. dest": "CN"
}
}
]
}
}
}
只有
message
字段包含
firefox
词项的日志文档才会被返回,而
geo
的
src
字 段和 dest
字段是否为
CN
只影响相关度,当然相关度越高的肯定排在前面,可以 通过下面的例子观察到:
POST /kibana_sample_data_logs/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"message": "firefox"
}
}
],
"should": [
{
"term": {
"geo.src": "CN"
}
},
{
"term": {
"geo.dest": "CN"
}
}
]
}
},
"sort": [
{
"_score": {
"order": "asc"
}
}
]
}
"query": {
"bool": {
"must": [
{
"match": {
"message": "firefox"
}
}
],
"should": [
{
"term": {
"geo.src": "CN"
}
},
{
"term": {
"geo.dest": "CN"
}
}
]
}
},
"sort": [
{
"_score": {
"order": "asc"
}
}
]
}
但是如果在查询条件中将
must
子句删除,那么
should
子句就至少要满足有 一条。should 子句需要满足的个数由
query
的
minimum_ should_match
参数决定, 默认情况下它的值为 1
。 布尔查询在计算相关性得分时,采取了匹配越多分值越高的策略。由于 filter 和 must_not
不参与分值运算,所以文档的最后得分是
must
和
should
子句的相 关性分值相加后返回给用户。同时还可以试试:
POST /kibana_sample_data_logs/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"message": "firefox"
}
}
],
"should": [
{
"term": {
"geo. src": "CN"
}
},
{
"term": {
"geo. dest": "CN"
}
}
],
"filter": {
"term": {
"extension": "zip"
}
}
}
}
}
"query": {
"bool": {
"must": [
{
"match": {
"message": "firefox"
}
}
],
"should": [
{
"term": {
"geo. src": "CN"
}
},
{
"term": {
"geo. dest": "CN"
}
}
],
"filter": {
"term": {
"extension": "zip"
}
}
}
}
}
可以发现,最终返回的结果中又过滤了一次,
extension
的值为
zip
的才会返 回。
dis_max
组合查询
dis_max
查询也是种组合查询, 只是它在计算相关性度时与
bool
查询不同。
dis_max
查询在计算相关性分值时,会在子查询中取最大相关性分值为最终相关 性分值结果,而忽略其他子查询的相关性得分。dis_max
查询通过
queries
参数 接收对象的数组。例如:
POST /kibana_sample_data_logs/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {
"message": "firefox"
}
},
{
"term": {
"geo. src": "CN"
}
},
{
"term": {
"geo. dest": "CN"
}
}
]
}
}
}
"query": {
"dis_max": {
"queries": [
{
"match": {
"message": "firefox"
}
},
{
"term": {
"geo. src": "CN"
}
},
{
"term": {
"geo. dest": "CN"
}
}
]
}
}
}
在多数情况下,完全不考虑其他字段的相关度可能并不合适,所以可以使用 tie_ breaker 参数设置其他字段参与相关度运算的系数。这个系数会在运算最终 相关度时乘以其他字段的相关度,再加上最大得分就得到最终的相关度了。所以 一般来说,tie_ breaker
应该小于
1,
默认值为
0
。例如在示例的返回结果中,即使 文档 message
和
geo
字段都满足查询条件它也不定会排在最前面。可添加 tie_breaker 参数并设置为
0.7
。
POST /kibana_sample_data_logs/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {
"message": "firefox"
}
},
{
"term": {
"geo. src": "CN"
}
},
{
"term": {
"geo. dest": "CN"
}
}
],
"tie_breaker": 0.7
}
}
}
"query": {
"dis_max": {
"queries": [
{
"match": {
"message": "firefox"
}
},
{
"term": {
"geo. src": "CN"
}
},
{
"term": {
"geo. dest": "CN"
}
}
],
"tie_breaker": 0.7
}
}
}
在添加了
tie _breaker
参数后,相关度非最高值字段在参与最終相关度结果 时的权重就降低为 0.7
。但它们对结果排序会产生影响,完全满足条件的文档将 排在结果最前面。
constant_score
查询
constant_score
查询返回结果中文档的相关度为固定值,这个固定值由
boost 参数设置,默认值为 1.0
。
constant score
查询只有两个参数
filter
和
boost,
前者 与 bool
组合查询中的
filter
完全相同,仅用于过滤结果而不影响分值。
POST /kibana_sample_data_logs/_search
{
"query": {
"constant_score": {
"filter": {
"match": {
"geo.src": "CN"
}
},
"boost": 1.3
}
}
}
"query": {
"constant_score": {
"filter": {
"match": {
"geo.src": "CN"
}
},
"boost": 1.3
}
}
}
由于示例中通过
boost
参数设置了相关度,所以满足查询条件文档的
score 值将都是 1.3
,
match_all
查询也可以当成是一种特殊类型的
constant_score
查询, 它会返回索引中所有文档,面每个文档的相关度都是 1.0
。它的作用和
boost
参 数类似,组合查询时调整子查询在相关度计算中的权重。
boosting
查询
boosting
查询通过
positive
子句设置满足条件的文档,这类似于
bool
查询中 的 must
子句,只有满足
positive
条件的文档才会被返回。
boosting
查询通过 negative 子句设置需要排除文档的条件, 这类似于
bool
查询中的
must _not
子 旬。但与 bool
查询不同的是,
boosting
查询不会将满足
negative
条件的文档从 返回结果中排除,而只是会拉低它们的相关性
POST /kibana_sample_data_logs/_search
{
"query": {
"boosting": {
"positive": {
"term": {
"geo.src": "CN"
}
},
"negative": {
"term": {
"geo. dest": "CN"
}
},
"negative_boost": 0.2
}
},
"sort": [
{
"_score": "asc"
}
]
}
"query": {
"boosting": {
"positive": {
"term": {
"geo.src": "CN"
}
},
"negative": {
"term": {
"geo. dest": "CN"
}
},
"negative_boost": 0.2
}
},
"sort": [
{
"_score": "asc"
}
]
}
在示例中,参数
negative_ boost
设置了一个系数,当满足
negative
条件时 相关度会乘以这个系数作为最终分值,所以这个值应该小于 1
而大于等于
0
。, 如果 geo_src
为
CN
的文档相关度为
1.6
,那么
geo. Dest
字段也是
CN
的文档相关 度就需要再乘以 0.2,
所以最终相关度为
0. 32
。
function_score
查询
function_score
查询提供了一组计算查询结果相关度的不同函数,通过为查 询条件定义不同打分函数实现文档检索相关性的自定义打分机制。查询条件通过 function_score 的
query
参数设置,而使用的打分函数则使用
functions
参数设置
例如
:
POST /kibana_sample_data_logs/_search
{
"query": {
"function_score": {
"query": {
"query_string": {
"fields": [
"message"
],
"query": "(firefox 6.0a1) OR (chrome 11.0.696.50)"
}
},
"functions": [
{
"weight": 2
},
{
"random_score": {
}
}
],
"score_mode": "max",
"boost_mode": "avg"
}
}
}
"query": {
"function_score": {
"query": {
"query_string": {
"fields": [
"message"
],
"query": "(firefox 6.0a1) OR (chrome 11.0.696.50)"
}
},
"functions": [
{
"weight": 2
},
{
"random_score": {
}
}
],
"score_mode": "max",
"boost_mode": "avg"
}
}
}
function_score
查询在运算相关度时,首先会通过
functions
指定的打分函数 算出每份文档的得分。如果指定了多个打分函数,它们打分的结果会根据 score_mode 参数定义的模式组合起来。 以示例为例,functions
参数定义了两个打分函数,
random_score
函数会在 0-1 之间产生一个随机数, 而
weight
函数则会以指定的值为相关性分值。由于 score_mode 参数设置的值为
max
,即从所有评分函数运算结果中取最大值,而 weight 值为
2
,它将永远大于
random_ score
产生的值,所以评分函数最终给出 的分值也将永远是 2
。 score_mode 包括以下几个选项
multiply
、
sum
、
avg
、
first
、
max
、
min,
通过 名称很容易判断它们的含义,分别是在所有评分函数的运算结果中取它们的乘积、 和、平均值、首个值、最大值和最小值。 打分函数运算的相关性评分会与 query
参数中查询条件的相关度组合起来, 组合的方式通过 boost_mode
参数指定,它的默认值与
score_mode
一样都是 multiply。
boost_mode
参数的可选值与
score_mode
也基本一致,但没有
first
而 多了一个 replace
,代表使用评分函数计算结果代替查询分值。 可见 function_score
是一种在运算相关度上非常灵活的组合查询,这种灵活 性主要体现在它提供了一组打分函数,以及组合这些打分函数的灵活方式。打分 函数包括 weight
、
random_score
、
field_value_factor
以及衰减函数等等。在这
些函数中下面再来简单介绍一下其他打分函数。
field_value_factor
函数
field_value_factor
函数在计算相关度时允许加入某一字段作为干扰因子,比
如:
POST /kibana_sample_data_flights/_search
{
"query": {
"function_score": {
"query": {
"bool": {
"must": [
{
"match": {
"OriginCountry": "CN"
}
},
{
"match": {
"DestCountry": "US"
}
}
]
}
},
"field_value_factor": {
"field": "AvgTicketPrice",
"factor": 0.001,
"modifier": "reciprocal",
"missing": 1000
}
}
}
}
"query": {
"function_score": {
"query": {
"bool": {
"must": [
{
"match": {
"OriginCountry": "CN"
}
},
{
"match": {
"DestCountry": "US"
}
}
]
}
},
"field_value_factor": {
"field": "AvgTicketPrice",
"factor": 0.001,
"modifier": "reciprocal",
"missing": 1000
}
}
}
}
这个示例实际上是将相关度按票价由低到高的次序做了权重的提升,票价越 低最终的相关度越高。这相当是找出所有从中国到美国的航班,并按票价由低到 高的次序排序。 在示例中,field_value_factor
打分函数通过
field
参数设置了干扰字段为AvgTicketPrice,而
factor
则是为干扰字段设置的调整因子,它会与字段值相乘后 再参与接下来的运算。Missing
表示如果字段值是丢失的,默认被使用的值。 modifier 参数就有些复杂了,它代表了干扰字段与调整因子相乘的结果如何参与 相关度运算。在示例中给出的是 reciprocal,
代表取倒数
1/x
。 modifier 可用运算方法除了
reciprocal
以外还有很多
:
none:
不做其他运算
;
log:
取对数
log1p:
加
1
后再取对数,目的是为了防止字段值为
0-1
时计算结果为负数
log2p:
加
2
后再取对教
ln:
自然对数
ln1p
:加
1
后取自然对数
ln1p
:加
2
后取自然对数
square:
取平方
sqrt
:取平方根
Reciprocal
:代表取倒数。
衰减函数
衰减函数是一组通过递减方式计算相关度的函数,它们会从指定的原始点开 始对相关度做衰减,离原始点距离越远相关度就越低。衰减函数中的原始点是指 某字段的具体值,由于要计算其他文档与该字段值的距离,所以要求衰减函数原 始点的字段类型必须是数值、日期或地理坐标中的一种。 举例来说,如果在 2019
年
3
月
25
日前后系统运行出现异常,所以对这个日 期前后的数据比较感兴趣,就可以按如下形式发送请求:
POST kibana_sample_data_flights/_search
{
"query": {
"function_score": {
"query": {
"match": {
"OriginCityName": "Beijing"
}
},
"gauss": {
"timestamp": {
"origin": "2019-03-25",
"scale": "7d",
"offset": "1d",
"decay": 0.3
}
}
}
}
}
"query": {
"function_score": {
"query": {
"match": {
"OriginCityName": "Beijing"
}
},
"gauss": {
"timestamp": {
"origin": "2019-03-25",
"scale": "7d",
"offset": "1d",
"decay": 0.3
}
}
}
}
}
在示例中使用的衰减函数为高斯函数,定义原始点使用的字段为
timestmp, 而具体的原始点则通过 origin
参数定义在了
2019
年
3
月
25
日。
offset
参教定义 了在 1
天的范围内相关度不衰减,也就是说
2019
年
3
月
24-26
日相关度不衰减。 scale 参数和
decay
参数则共同决定了衰减的程度,前者定义了衰减的跨度范围, 而后者则定义了衰减多少。以示例中的设置为例,代表的含义是 7
天后的文档相 关度衰减至 0.3
倍。 在衰减函数除了高斯函数 gauss
以外,还有线性函数
linear
和指数函数
exp
两种。它们在使用上与高斯函数完全相同。如果将这几种衰减函数以图形画出来 就会发现,它们在衰减的平滑度上有着比较明显的区别。
![](https://i-blog.csdnimg.cn/blog_migrate/2bfcca4575baf750cd5fc85a9c0b3d4a.png)
单查询条件下的相关度组合
组合查询一般由多个查询条件组成,所以在计算相关度时都要考虑以何种方 式组合相关度。而很多的查询都只针对一个字段设置查询条件,所以只有相关度 权重提升问题而没有相关度组合问题。但有些查询,比如 multi_match
查询可以 针对多个字段设置查询条件,所以它们在计算相关度时也需要考虑组合多个相关 度的问题。 multi_match 查询具有一个
type
参数,用于指定针对多字段检索时的执行逻 辑及相关度组合方法。type
参数有
5
个可选值,即
best_fields
、
most_fields
、 cross_ fields、
phrase
和
phrase_preix
。 例如
:
POST /kibana_sample_data_flights/_search
{
"query": {
"multi_match": {
"query": "CN",
"fields": [
"originCountry^2",
"DestCountry"
],
"type": "best_fields"
}
}
}
"query": {
"multi_match": {
"query": "CN",
"fields": [
"originCountry^2",
"DestCountry"
],
"type": "best_fields"
}
}
}
best_fields
、
phrase
与
phrase_prefix
类型
best_fields
类型在执行时会将与字段匹配的文档都检索出来,但在计算相关 度时会取得分最高的作为整个查询的相关度。
例如在示例中,第一个查询通过
"OrignCountny^2"
的形式将
OriginCountry
字 段的相关度权重提升到 2
,所以这个字段相关度会高于
DestCountry
字段。 在 best_fields
类型下执行检索时,
DestCountry
字段对最终相关度就不会再 有影响。通过查看返回结果也可以看到,OriginCountry
字段为
CN
的文档相关度 都相同,即使 DestCountry
字段也是
CN
,文档的相关度也不会提升。
best_fields
适用于用户希望匹配条件全部出现在一个字段中的情况,比如在 文章标题和文章内容中同时检索 elasticseareh
和
logstash
时,如果在文章标题或 是文章内容中同时出现了两个词项,该文章在相关度就会高于其他文章。比如说:
有两份文档:
文档
1
:
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
文档
2
:
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
用户输入了
"brown fox"
来搜索文档,文档
2
匹配的更好一些,因为它的
body 字段同时包含了用户寻找的两个单词。
大家是否还记得前面的
dis_max
查询(
dis_max
查询在计算相关性分值时, 会在子查询中取最大相关性分值为最终相关性分值结果,而忽略其他子查询的相 关性得分),在相关度计算上 dis_max
查询是不是与
best_fields
类型很像
?
事实 上, best_fields
类型的查询在执行时会转化为
dis_max
查询,例如示例在执行 时会转化为
POST /kibana_sample_data_flights/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {
"OriginCountry": {
"query": "CN",
"boost": 2
}
}
},
{
"match": {
"DestCountry": "CN"
}
}
]
}
}
}
"query": {
"dis_max": {
"queries": [
{
"match": {
"OriginCountry": {
"query": "CN",
"boost": 2
}
}
},
{
"match": {
"DestCountry": "CN"
}
}
]
}
}
}
dis_max
有一个参数
tie_breaker,
可以设置非最高值相关度参与最终相关度 运算的系数,multi_match
中使用
best_fields
类型时也可以使用这个参数。 phrase 与
phrase_ pre
f
ix
类型在执行逻辑上与
best_fields
完全相同,只是在 转换为 dis_max
时
queries
查询中的子查询会使用
phrase
或
phrse_prefix
而不是 match。
most_fields
类型
most_fields
类型在计算相关度时会将所有相关度累加起来,然后再除以相关 度的个数以用到它们的平均值作为最终的相关度。
还是以
best_fields
中的那个示例检索为例,如果将
type
替换为
most_fields
,
POST /kibana_sample_data_flights/_search
{
"query": {
"multi_match": {
"query": "CN",
"fields": [
"originCountry^2",
"DestCountry"
],
"type": "most_fields"
}
}
}
"query": {
"multi_match": {
"query": "CN",
"fields": [
"originCountry^2",
"DestCountry"
],
"type": "most_fields"
}
}
}
它会将
OriginCountry
和
DestCountry
两个字段匹配
CN
时计算出的相关度累 加,然后再用累加和除以 2
作为最终的相关度。所以只有当两个字段都匹配了 CN,最终的相关度才会更高。这在效果上相当于将出发地和目的地都是中国的 文档排在了最前面,所以适用于希望检索出多个字段中同时都包含相同词项的检 索。在实现上,most_fields
类型的查询会被转化为
bool
查询的
should
子句,上 面的示例实际被转化为:
POST /kibana_sample_data_flights/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"OriginCountry": {
"query": "CN",
"boost": 2
}
}
},
{
"match": {
"DestCountry": "CN"
}
}
]
}
}
}
"query": {
"bool": {
"should": [
{
"match": {
"OriginCountry": {
"query": "CN",
"boost": 2
}
}
},
{
"match": {
"DestCountry": "CN"
}
}
]
}
}
}
cross_fields
类型
如果查询条件中设置了多个词项,
best_fields
类型和
most_ fields
类型都支 持通过 operator
参数设置词项之间的逻辑关系,即
and
和
or
。 但它们在设置 operator 时是针对字段级别的面不是针对词项级别的,来看一个例子
:
POST /kibana_sample_data_logs/_search
{
"query": {
"multi_match": {
"query": "firefox success",
"fields": [
"message",
"tags"
],
"type": "best_fields",
"operator": "and"
}
}
}
"query": {
"multi_match": {
"query": "firefox success",
"fields": [
"message",
"tags"
],
"type": "best_fields",
"operator": "and"
}
}
}
示例设置的查询条件为
firefox
和
success
两个词项,而匹配字段也是两个 messape 和
tags
。当
operator
设置为
and
时, 在
best_fields
类型下这意昧着两 个字段中需要至少有一个同时包含 firefox
和
success
两个词项,而这样的日志 文档并不存在。 而在 cross_fields 类型则可以理解为将文档的中
message
和
tags
字段组合为 一个大的字段,然后在大的字段中搜索词条 firefox
和
success
。这样的话只要 firefox 和
success
在大字段中均出现过,就认为这个文档符合条件。
今天分享到此结束,下一篇我们分析es聚合索引,敬请期待。