0


ElasticSearch(七)— 相关性检索和组合查询

一、 相关性评分

全文检索与数据库查询的一个显著区别, 就是它并不一定会根据查询条件
做完全精确的匹配。除了模糊查询以外,全文检索还会根据查询条件给文档的相关性打分并排序,将那些与查询条件相关性高的文档排在最前面。

相关性( Relevance)或相似性(Similarity)是指两个事物间相互关联的程度,在检索领城特指检索请求与检索结果之间的相关程度。在 Elaticsearch 返回的每条结果中都会包含一个_ score 字段,这个字段的值就是当前文档匹配检索请求的相关性评分,我们也可以称为相关度。

解决相关性问题的核心是计算相关度的算法和模型,相关度算法和模型是全文检索引章最重要的技术之一。相关度算法和相关度模型并非完全相同的概念,相关度模型可以认为是具有相同理论基础的算法集合。所以在实际应用时都是指定到具体的相关度算法,相关度模型则是从理论层面对相关度算法的归类。

1.1 相关度模型

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)分为相关文档®和不相关文档(NR)两类,使用P(R|D)代表文档属于相关文档的概率,而使用 P(NR|D) 表示文档属于不相关文档的概率,则当 P(R|D)>P(NR|D)时认为这个文档与用户查询相关。根据贝叶斯公式将 P(R|D)>P(NR|D)转换为对某个比值的计算。在此基础上再进行一此转换,就可以得到不同的相关度算法。

语言模型
语言模型最早并不是应用于全文检索领域,而是应用于语音识别、机器翻译、拼写检查等领域。在全文检索中,语言模型为每个文档建立不同的计算模型,用以判断由文档生成某一查询条件的概率是多少,而这个概率的值就可以认为是相关度。可见,语言模型的与其他检索模型正好相反,其他检索模型都是从查询条件查找满足条件的文档,而语言模型则是根据文档推断可能的查询条件。

1.2 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了。

1.3 BM25

BM25 是 Best Match25 的简写,由于最早应用于一个名为 Okapi 的系统中,所以很多文献中也称之为 Okapi BM25。BM25 算法被认为是当今最先进的相关度算法之一,Elasticsearch 文档字段的默认相关度算法就是采用 BM25,它属于概率模型,依据贝叶斯公式,经过一系列的严格推导以后,得出了一个关于 IDF 的公式:

在这里插入图片描述
同时在这个基础上,最终的公式上加入了对 TF、当前文档的长度、词频饱
和度、长度归一化等因素的考虑:
在这里插入图片描述
词频饱和度
所谓词频饱和度指的是当词频超过一定数量之后,它对相关度的影响将趋于
饱和。换句话说,词频 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。

1.4 向量化搜索与全文检索的区别

传统的信息检索技术通常使用TF-IDF、BM25这类稀疏表示来检索文档,然而这些方法都建立在用户查询与相关文档存在词汇重叠的基础之上。然而现实世界并不会这么理想,稀疏向量查询通常会存在棘手的词汇空缺或语义鸿沟问题(lexical/semantic gap)。

然而真实场景下用户的提问通常非常口语化,标准问句和真实查询的字面匹配分数常常很低,这就不仅仅是单纯的词汇或句法差异了,而是更高层次的风格上的差异,甚至可以理解为两种不同的语言,这实际上也是当前的搜索引擎在正式检索文档前必须对用户查询进行复杂的修正、解析、理解,而不是直接计算TF-IDF的原因。

将用户查询浓缩为关键词的过程是非常复杂的,有没有直接将用户查询与文档进行匹配的方式呢?这正是稠密向量查询想要达到的效果,也就是将查询和文档映射到同一个低维向量空间,通过计算余弦相似度来检索相关文档,关于稠密向量表示的探索可以追溯到经典的潜在语义分析(LSA),2013年的DSSM首次将深度学习方法引入了稠密向量检索,目前,以SentenceBERT为代表的语义检索模型在很多数据集上超越了基于稀疏向量的检索方法。

二、 组合查询与相关度组合

相关性问题不仅要解决单个查询条件的相关度计算,还要考虑如何将多个查
询条件产生的相关度组合起来。而相关度组合问题主要出现在组合查询中,所以接下来我们就要了解下组合查询及相关度组合问题。

组合查询可以将通过某种逻辑将子查询组合起来,实现对多个字段与多个查
询条件的任意组合。组合查询组合的子查询不仅可以是基于词项或基于全文的子查询,也可以是另一个组合查询。

单纯从组合查询的使用上来看,组合查询并不复杂,复杂的是组合多个子查
询相关度的逻辑,这也是它们的核心区别之一。

2.1 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 indexname/_search{"query":{"bool":{"must":[{"match":{"message":"firefox"}}],
            "should":[{"term":{"geo. src":"CN"}},
            {"term":{"geo. dest":"CN"}}]}}}

只有 message 字段包含 firefox 词项的日志文档才会被返回,而 geo 的 src 字段和 dest 字段是否为 CN 只影响相关度,当然相关度越高的肯定排在前面,可以通过下面的例子观察到:

POST indexname/_search{"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 子句的相关性分值相加后返回给用户。

2.2 dis_max 组合查询

dis_max 查询也是种组合查询, 只是它在计算相关性度时与 bool 查询不同。dis_max 查询在计算相关性分值时,会在子查询中取最大相关性分值为最终相关性分值结果,而忽略其他子查询的相关性得分。dis_max 查询通过 queries 参数接收对象的数组。例如:

POST  indexname/_search{"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 indexname/_search{"query":{"dis_max":{"queries":[{"match":{"message":"firefox"}},
            {"term":{"geo. src":"CN"}},
            {"term":{"geo. dest":"CN"}}],
            "tie_breaker":0.7}}}

在添加了 tie _breaker 参数后,相关度非最高值字段在参与最終相关度结果
时的权重就降低为 0.7。但它们对结果排序会产生影响,完全满足条件的文档将排在结果最前面。

2.3 constant_score 查询

constant_score 查询返回结果中文档的相关度为固定值,这个固定值由 boost参数设置,默认值为 1.0。constant_score 查询只有两个参数 filter 和 boost, 前者与 bool 组合查询中的 filter 完全相同,仅用于过滤结果而不影响分值。

POST indexname/_search{"query":{"constant_score":{"filter":{"match":{"geo.src":"CN"}},
            "boost":1.3}}}

由于示例中通过 boost 参数设置了相关度,所以满足查询条件文档的 score
值将都是 1.3,match_all 查询也可以当成是一种特殊类型的 constant_score 查询,它会返回索引中所有文档,面每个文档的相关度都是 1.0。它的作用和 boost 参数类似,组合查询时调整子查询在相关度计算中的权重。

2.4 boosting 查询

boosting 查询通过 positive 子句设置满足条件的文档,这类似于 bool 查询中的 must 子句,只有满足 positive 条件的文档才会被返回。boosting 查询通过negative 子句设置需要排除文档的条件, 这类似于 bool 查询中的 must _not 子旬。但与 bool 查询不同的是,boosting 查询不会将满足negative 条件的文档从返回结果中排除,而只是会拉低它们的相关性

POST indexname/_search{"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。

2.5 function_score 查询

function_score 查询提供了一组计算查询结果相关度的不同函数,通过为查
询条件定义不同打分函数实现文档检索相关性的自定义打分机制。查询条件通过function_score 的 query 参数设置,而使用的打分函数则使用functions 参数设置。例如:

POST indexname/_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"}}}

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 以及衰减函数等等。在这些函数中下面再来简单介绍一下其他打分函数。

2.6 衰减函数

衰减函数是一组通过递减方式计算相关度的函数,它们会从指定的原始点开
始对相关度做衰减,离原始点距离越远相关度就越低。衰减函数中的原始点是指某字段的具体值,由于要计算其他文档与该字段值的距离,所以要求衰减函数原始点的字段类型必须是数值、日期或地理坐标中的一种。
举例来说,如果在 2019 年 3 月 25 日前后系统运行出现异常,所以对这个日期前后的数据比较感兴趣,就可以按如下形式发送请求:

POST indexname/_search{"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两种。它们在使用上与高斯函数完全相同。如果将这几种衰减函数以图形画出来就会发现,它们在衰减的平滑度上有着比较明显的区别。

在这里插入图片描述

三、单查询条件下的相关度组合

组合查询一般由多个查询条件组成,所以在计算相关度时都要考虑以何种方
式组合相关度。而很多的查询都只针对一个字段设置查询条件,所以只有相关度权重提升问题而没有相关度组合问题。但有些查询,比如 multi_match 查询可以针对多个字段设置查询条件,所以它们在计算相关度时也需要考虑组合多个相关度的问题。

multi_match 查询具有一个 type 参数,用于指定针对多字段检索时的执行逻辑及相关度组合方法。type 参数有 5 个可选值,即 best_fields、 most_fields、cross_ fields、phrase 和 phrase_preix。

3.1 best_fields、phrase 与 phrase_prefix 类型

best_fields 类型在执行时会将与字段匹配的文档都检索出来,但在计算相关度时会取得分最高的作为整个查询的相关度。

best_fields 适用于用户希望匹配条件全部出现在一个字段中的情况,比如在文章标题和文章内容中同时检索 elasticseareh 和 logstash 时,如果在文章标题或是文章内容中同时出现了两个词项,该文章在相关度就会高于其他文章。事实上,best_fields 类型的查询在执行时会转化为 dis_max 查询。

dis_max 有一个参数 tie_breaker, 可以设置非最高值相关度参与最终相关度
运算的系数,multi_match 中使用 best_fields 类型时也可以使用这个参数。
phrase 与 phrase_ prefix 类型在执行逻辑上与 best_fields 完全相同,只是在转换为 dis_max 时 queries 查询中的子查询会使用 phrase 或phrse_prefix 而不是match。

3.2 most_fields 类型

most_fields 类型在计算相关度时会将所有相关度累加起来,然后再除以相关度的个数以用到它们的平均值作为最终的相关度。

POST indexname/_search{"query":{"multi_match":{"query":"CN",
            "fields":["originCountry^2",
            "DestCountry"],
            "type":"most_fields"}}}

它会将 OriginCountry 和 DestCountry 两个字段匹配 CN 时计算出的相关度累加,然后再用累加和除以 2 作为最终的相关度。所以只有当两个字段都匹配了CN,最终的相关度才会更高。这在效果上相当于将出发地和目的地都是中国的文档排在了最前面,所以适用于希望检索出多个字段中同时都包含相同词项的检索。在实现上,most_fields 类型的查询会被转化为 bool 查询的 should 子句,上面的示例实际被转化为:

POST indexname/_search{"query":{"bool":{"should":[{"match":{"OriginCountry":{"query":"CN",
                        "boost":2}}},
            {"match":{"DestCountry":"CN"}}]}}}

3.3 cross_fields 类型

如果查询条件中设置了多个词项,best_fields 类型和 most_ fields 类型都支
持通过 operator 参数设置词项之间的逻辑关系,即 and 和 or。 但它们在设置operator 时是针对字段级别的面不是针对词项级别的,来看一个例子:

POST indexname/_search{"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可以对json格式的数据进行各种精准查询,全文搜索查询、相关性查询的操作。所以,可以得出以下结论:
当我们的数据是单表数据,且只做普通查询时,可以选择mongodb作为数据库。
当我们的数据是单表数据,且需要全文检索,或者数据量很大,很多,需要做统计分析时,考虑用es作为数据存储引擎。
当我们的数据之间有关联性,存在表与表联查时,再考虑使用关系型数据库。


本文转载自: https://blog.csdn.net/qq1309664161/article/details/140732907
版权归原作者 敲代码的小小酥 所有, 如有侵权,请联系我们删除。

“ElasticSearch(七)— 相关性检索和组合查询”的评论:

还没有评论