ElasticSearch 内部原理:倒排索引及相关度计算

2,361 阅读9分钟

"这就是 Elasticsearch 脱颖而出的地方:Elasticsearch 鼓励你去探索与利用数据,而不是因为查询数据太困难,就让它们烂在数据仓库里面。 Elasticsearch 将成为你最好的朋友。"

Elasticsearch官方文档里面这一句话,喊出了Elasticsearch的野心。ES是目前全文搜索领域广受好评的框架之一,当我们惊叹于ES杰出的快速检索能力和全文搜索能力时,我们不难对其背后的原理发出疑问,为什么它在搜索领域能做得比起关系型数据库好那么多呢?

全文搜索引擎胜在快速和高效的查询大批量非结构化的文本—文档或者其他包好自由结构文本的记录,并且返回这些基于用于搜索匹配的结果文档,他们可以根据具体的数值或者字段去进行快速高效的排序、分类等。

全文搜索系统通常依赖某些类型的索引来执行查询。 最常见的是反向索引,它有效地列出了每个文档中的每个术语 ——— 每个单词,数字等, 以及哪些文档包含该术语的指示(以及如果搜索短语或其他邻近操作被支持的位置)。 每个字段可能有一个单独的索引,或者所有字段可以包含在单个索引中。

Elasticsearch 使用一种称为 倒排索引 的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。

而其之上,每个ES分片都包含若干Lucene实例。

ES的内部索引根基 —— Lucene

Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。

Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境里Lucene是一个成熟的免费开源工具。就其本身而言,Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库。人们经常提到信息检索程序库,虽然与搜索引擎有关,但不应该将信息检索程序库与搜索引擎相混淆。

Lucene的读写流程如图所示

1.analysis模块:

主要负责词法分析及语言处理,即分词。通过它形成最终存储的最小单位term。

2.index模块:

主要负责索引的创建工作。

3.store模块:

主要负责索引的读写工作。主要是一些文件的IO,及平台无关的处理。

4.queryParser:

负责语法分析,把我们的查询语句生成Lucene底层可以识别的条件。

5.search模块:

主要负责对索引的搜索工作。

6.similarity模块:

主要负责对结果进行相关性评分和排序的实现。

那么数据在Lucene中是如何进行存储的呢?

先介绍Lucene中几个基本的术语:

Term(单词):一段文本经分词器分词后的最小单位。

词典:是Term的集合,常驻于内存中。

倒排表:一个文档由多个词组成,倒排表记录某个词在哪些文档中出现过。

正向信息:原始的文档消息。

段(segment):索引中最小的独立存储单元,一个索引只能由一个段或多个段组成。段具有不变性,一旦写入,只能进行读操作而不能进行写操作。

lucene的底层存储结构如下图所示,主要由词典和倒排表组成。其中的词典是Term的集合。而每个Term指向文档链表的集合,称为倒排表。词典和倒排表是分两部分存储的,倒排表不仅存储了文档编号(UID),还存储了包括词频等消息。

Lucene 的检索方式

  1. 单个单词查询

    指对一个term进行查询,则只需在词典中找到词语,再在倒排表中获取对应的文档链表即可。

  2. AND查询

    指对多个集合求交集。

  1. OR查询

    指对多个集合求并集。

  1. NOT查询

    指对多个集合进行差集。

由此可见,通过上述四种查询方式,我们不难发现,由于Lucene 是以倒排表的形式存储的,所以在 Lucene 的查找过程中只需在词典中找到这些 Term,根据 Term获得文档链表,然后根据具体的 查询条件对链表进行交、并、差等操作,就可以准确地查到我们想要的结果,相对于在关系型 数据库中的"like"查找要做全表扫描来说。这种思路是非常高效的。虽然在索引创建时要做很 多工作,但这种一次生成、多次使用的思路也是非常高明的。

Lucene 相关度打分

我们在前面了解到,Lucene的查询过程是:

首先在词典中查找每个term,根据term获得每个term所在的文档链表:然后根据文档编号链表进行和、并、差集操作,链表合并后的数据便是我们要找的数据。

那全文搜索中关键的结果相关度打分,Lucene是如何帮我们做到的呢?

Lucene早起版本依靠的是基于向量空间的算法,而目前的版本依靠的是基于概论的算法BM25

文本相关度的影响因子

  1. tf( term frequency):

    某个词在文档中出现的次数,其值越大,就可认为这篇文档描述的内容与该内容越接近,相关度得分就越高。

    在Lucene中的计算公式为:

    tf(tind) = \sqrt {Term在文档中出现的次数}

  2. idf(inverse document frequency ):

    逆向指标,表示在整个文档集合中包含某个词的文档数量越少,这个词便越重要。

    在Lucene中的计算公式为:

    idf(t)=1+log(docCount/(docFreq+1))

其中,docCount表示索引中的文档总数,docFreq表示包含Term t的文档数,分母中 docFreq+1是为了防止分母为0。

  1. length:

    这也是一个逆向的指标,表示在同等条件下,搜索词所在文档的长度越长,搜索词和文档的相似度就越低;文档的长度越短,相似度就越高。例如"lucene"出现在一篇包含10个字的文档中和一篇包含10000个字的文档中,那么我们可以认为10个字的那篇文章与"lucene"更相关。

Lucene为了更好地调节相关度得分,增加了以下几种boost 值。

  1. term boost:

    查询在语句中每个词的权重,可以在查询中设定某些词更重要。

  2. document boost:

    文档的权重,在创建索引时设置某些文档比其他文档更重要。比如我 国某大型搜索引擎网站可以将其域名下网站的 boost 值设置得比其他网站的大一些,当 有查询过来时,其域名下的网站就会有更好的排名。

  3. field boost:

    域的权重,就是字段的权重,表明某些字段比其他字段更重要。比如,在 有标题和正文的网站中,命中标题要比命中正文重要得多。

基于概率的BM25算法

BM25 算法是根据BIM(Binary independent Model,二元独立模型)算法改进而来的,二元独立模型做了两个假设,

  • 二元假设,指一个词和文档的关系只有两种:1,相关;2,不相关。不考虑其他因素。
  • 词的独立性假设,指文档里词和词之间没有任何关联,任意一个词在文档中的分布概率 不依赖于其他单词或者文档。

而BM25算法是在BIM算法的基础上添加了词的权重和两个经验参数,到目前为止是很优 秀的排名算法。现在Elasticsearch 默认的打分算法已经由原来的向量空间模型变成了BM25。 BM25的计算公式主要分为两大部分:W是第i个词的权重;R(q,d)是每个词和文档的相关度值,其中q代表查询语句中的第i个词,d代表相关的文档。

一个包含n个词的查询语句和文档d的 BM25算法公式为:

Score(Q,d)=\sum_{i}^nWi*R(q_i,d)

其中 Wi的值是一个词的idf值,公式如下,这个公式是由BIM模型推理得出的:

idf(q_i)=log\frac{N-n(q_i)+0.5}{n(q_i)+0.5}

其中,N是文档的总数,n(q_i)是包含该词的文档数,0.5是为了避免n()为0,导致分母为零。

由公式可知n(q_i)越小,分子部分就越大,分母部分就越小,所以idf值就越大,log用于 让idf的值受n(q_i)的影响更加平滑。该公式虽然与向量空间模型的idf算法不太一样,但同样 体现了一个词的重要程度和其出现在文档中的数量成反比这一思想。

R(q_i,d)=\frac{f_i*(k_1+1)}{f_i+K}*\frac{qf_i*(k_2+1)}{qf_i+k_2}

其中

K=k_1*(1-b+b*\frac{dl}{avgdl})

f_i表示第i个词在文档中出现的次数,qf_i表示第i个词在查询语句出现的次数。同向量空间模型中的思路一样,在一个查询语句中很少会有一个词出现多次,所以我们认 为qf_i永远为1,这样qf(k_2+1)/(qf+k_2)就等于1了。

最终,BM25 算法的公式如下:

Score = (Q,d) =\sum_{i}^nIDF(q_i)*\frac{f_i*(k_1+1)}{f_i+k_1*(1-b+b*\frac{dl}{avgdl})}

对其中的参数说明如下:

  • IDF(q_i):词的重要程度和其出现在文档中的数量成反比,包含该词的文档数越多,idf 值就越小。

  • f_i:表示第i个词在文档中出现的次数,f的值越大,得分就越高。

  • dl:表示文档的长度,文档越长,得分就越低

  • avgdl:表示文档的平均长度,随着索引的增、删、改,这个值是实时变化的。

  • k_1:表示调节因子,调节词频对得分的影响,k越大,表示词频对得分的影响越大,在 k=0的极限情况下,词频特征失效。默认值为1.2。

  • b:表示调节因子,调节字段长度对得分的影响,b越大,表示对文档长度的惩罚越大, 在 k=0的极限情况下,忽略文档长度的影响。默认值为0.75。

参考文献:

《可伸缩服务架构:框架与中间件》

《ElasticSearch技术解析与实战》