对于一个搜索引擎来说,对检索出来的数据进行排序是非常重要的功能。全文本数据的检索通常无法用是否相等来得出结果,而是用相关性来决定最后的返回结果。

相关性是指搜索内容和结果的相关性,是用来描述文档与查询语句的匹配程度的。通过计算相关性,可以得出一个相关性评分,然后根据评分对结果集进行排序,返回最符合的数据给用户。所以打分最终的目标是为了对结果进行排序,找出最匹配的结果集展示给用户。

那查询语句与文档的相关性该如何计算呢?或者说用什么来作为相关性的依据呢?今天我们就来学习 Lucene 的相关性算分模型。

一、设计我们自己的相关性算分模型

在不了解 Lucene 的相关性算分模型是如何实现之前,如果给你来设计相关性算分的模型,你有没有思路?

要找出与查询语句相关性最高的文档集合,是不是可以有这样的一种想法:把查询语句分词,如果每个词项在相关的文档中出现的频率越高,相关性就越高?然后把每个词项在文档的出现频率相加得到评分是不是可以作为相关性评分?如下表:

词项(Term) 文档 ID 列表
linux 2、4、5
java 2、4
in 1、2、3、4、5、6、8、9、10、11

如果我们搜索 “java in linux” 的话,将这个查询语句分词后可以命中文档 2、4、5。那么按照我们上述的步骤计算评分的话,操作如下:

  1. 求出 linux、java 这 2 个词项分别在 2、4、5 中出现的频率。
  2. 将对应的频率进行相加, 然后得出最终的评分如下:
  1. sorce(doc 2) = F(java) + F(linux)
  2. sorce(doc 4) = F(java) + F(linux)
  3. sorce(doc 5) = F(java) + F(linux)

其中频率 F(linux) 的含义是: “linux” 这个词在文档中出现的次数除以文档的总词数。需要注意的是,一些停用词(the、in这些)本身没有意义,而且它们在文章中出现的频率非常高,所以它们的评分是不应该被考虑的!

在我们设计的算法模型中,数据的相关性由词项在文档中出现的频率高低来决定。而现实中,很多著名的算法模型都是基于概率统计的,例如:TF-IDF、BM25。

二、TF-IDF 算分模型

TF-IDF (Term Frequency-inverse Document Frequency) 是信息检索领域非常重要的发明,TF-IDF 是一种关键词统计方法,用来反映一个词在一个文档集合或者资料库中的重要程度。其通常用作信息检索、文本挖掘、用户建模的加权因子。现代的搜索引擎很多都是依赖于 TF-IDF,但都进行了微调和优化。

TF-IDF 模型的思想其实就是说了这么一件事:如果一个词在一篇文章中出现的次数越多,那么这个词对于这篇文章来说就越重要。但同时如果这个词在整个语料库中出现的次数越多,那么其就不那么重要了,例如停用词就会在整个语料库中频繁出现,所以反而不那么重要了。这样做可以提高关键词与文章的相关性,并且减少了常用词对结果的影响。换句话来说就是:一个词的重要程度跟它在一篇文章中出现的频率成正比,跟它在语料库中出现的频率成反比。

1. TF-IDF 中的概念:TF、DF、IDF

TF-IDF 模型的公式中,主要包含:TF、DF、IDF三个概念,下面来看看这三个概念的定义和它们的计算过程。

  • TF (Term Frequency On Per Document)表示一个词在一篇文档中出现的频率。TF 的计算公式为:

TF公式.png 每个词项在每篇文档中都有自己的 TF 值,这个 TF 值越大说明这个词项对这篇文档来说越重要。回看一开始我们自己设计的模型的计算公式:F(linux) 就是 “linux” 这个词项的 TF 啦。

  • DF(Document Frequency)表示词项在语料库中出现的频率。其计算公式为:

DF公式.png 如果词项的DF 越大,表示语料库中包含对应词项的文档就越多,这个词项的重要程度也就越低

  • IDF(Inverse Document Frequency)表示逆文档频率,其可以通过 DF 计算得到,可以简单地理解为 DF 的倒数。其计算公式为:

IDF公式.png

如上公式,词项的 IDF = log(1/DF),那么如果词项的 IDF 越高,说明词项在语料库中越稀有,所以 IDF 代表了词项在语料库中的稀有性

在熟知了 TF、DF、IDF 这三个概念后,我们来看看 TF-IDF 算分的公式:

  1. tf-idf(termA) = TF(termA) * IDF(termA)

如上公式,可以得出 tf-idf(termA) 与 TF(termA) 正相关,与 IDF(termA) 也正相关。说白了就是,一个词项在一篇文档中出现得越多,其对于这篇文档来说越重要,同时这个词项在整个语料库中的稀有性影响着它对某篇文档的重要程度。tf-idf 本质上是对 TF 进行了加权计算,而这个权重就是 IDF。

那回到 “java in linux” 这个查询,我们可以这样来计算每个匹配文档的得分:

  1. sorce(D) = TF(java) * IDF(java) + TF(linux) * IDF(linux)

如上公式,其中 D 为某个匹配的文档,sorce(D) 的值为各个词项的 TF-IDF 值的和。

2. Lucene 的 TF-IDF 算分公式

Lucene公式.png

如上图所示的是 Lucene 的 TF-IDF 算分公式,公式中的各个项都很容易理解,图中也做了注释,下面对几个项进行解析,其他不在赘述。

  • norm(t,d), 是文档长度的加权因子,在索引文档时候就确定了的参数,其由文档的长度(也就是字段的内容长度)来确定,它可以提高短文档的分数。
  • coord(q,d),将查询内容分词后,如果匹配的文档出现分词后的词项越多,coord(q,d) 越大。
  • queryNorm(q), 是一个归一化因子,使得查询之间的分数具有可比性。
  • tf(t in d),Lucene 中 tf 与标准 TF-IDF 中的 TF 计算公式不一样,其值为 t 在文档 d 中出现次数的平方根。
  • idf(t),Lucene 中逆文档频率与上面的 IDF 公式也有点不一样,idf(t) = 1 + log((文档总数 + 1) / (匹配的文档数 + 1 ))。

需要注意的是,我在查看新版本的 Lucene 的文档时发现,在 7.0.0 后的版本中上述公式已经不包含 coord(q,d) * queryNorm(q) 了。TF-IDF 在 Lucene 中的实现文档可以参考:Lucene-TFIDF

三、BM25 算分模型

BM 是 Best Matching (最佳匹配) 的缩写。在信息检索的领域,Okapi BM25 是搜索引擎用来评估文档相关性以进行排序的函数,这个方法是上世纪七八十年代在概率检索的框架下被提出的,Okapi 是第一个使用这种方法的信息获取系统的名称。

BM25 是基于词频的,它不会考虑多个搜索词项在文档里靠不靠近,只会考虑它们各自在文档中出现的次数。BM 25 是一系列的评分函数,它们彼此之间有些形式和参数个数的差异。最常用的形式之一如下:

BM25.png

如上图,公式中的各项同样都很容易理解,下面对比较特别的几项进行介绍。

  • k1 和 b,它们是自由参数,k1 的取值范围为 [1.2, 2.0],k1 默认为1.2,而 b = 0.75 。
  • |D| / avgdl,文档越短的话此值越小,所以当 tf(t in d) 相等的情况下,公式后半部分取值越高。其值反映了词项在匹配文档中的重要性,对短篇文档更有利。
  • idf(t),为词项 t 的逆文档频率,但其计算方式跟 TF-IDF 中介绍的公式有点不一样,普通版的计算公式为: 旧版BM25_IDF.png 如上图所示,其中 N 为语料库中文档的总数,n(t) 为包含词项 t 的文档个数,遗憾的是,当含有词项的文档数量大于语料库的一半时,此公式的 idf(t) 为负。所以一般会对此公式进行优化如下: BM25_IDF.png 通过进行加一操作,可以避免求对数后的结果出现负数的情况。

下面来对比一下 Lucene TF-IDF、BM25 优化版、BM25 版的 IDF 公式,语料库中共 100 个文档,匹配的文档个数从 1 到 100,图中横坐标为匹配的文档个数,纵坐标为 IDF 公式计算出来的值。

多个版本的IDF比较.png

上面我们说过 IDF 的作用是代表了词项在语料库中的稀有性,也是一个词项的权重,所以这个权重不应该为负的。并且当所有的文档出现了某个词项,那么这个词项在语料库中就不稀有了,其权重应无限接近于 0 才是合理的。相比之下,优化版的 BM25 中的 IDF 公式更符合我们的需求。

四、TF-IDF 与 BM25 的比较

相对于 TF-IDF 来说,BM25 降低了 TF 对算分的影响力。当 TF 无限增加时,TF-IDF 模型的算分也会无限增加,而 BM25 则会趋于一个值。如果不考虑文档长度的影响(|D|/avgdl = 1 了),BM25 的 TF 算分的公式为:(tf * (k1 + 1)/ (tf + k1)) ,参考 BM25 算分公式的后半部分。

TF-IDF与BM25比较.png

如上图,当 TF 无限大的时候,BM25 的 TF 算分结果会无限逼近于(k1 + 1),虽然 TF 越大意味着这个词项相关性更大,但很快其影响力就下降了,收益递减。而 Lucene 的 TF-IDF 中词频的相关性会一直增加,算分也一直增加。

经过这么一比较,其实可以发现,k1 的作用是用来调节词频的影响力的。如果 k1 = 0,那么公式的后半部分 (tf * (k1 + 1)/ (tf + k1))= 1,即不再考虑词频的影响了。而 k1 为较大值时,(tf * (k1 + 1)/ (tf + k1))会与 tf 保持线性增长。

回过头来看,我们似乎还不知道 b 这个参数的意义到底是什么。我们令 L = |D|/avgdl,那么公式的分母部分可以表示为:tf(t in d) + k1 * (1 - b + b * L),可以看到,b 控制了文档篇幅对于得分的影响程度,当 b = 0 的时候,文档长度将不影响算分,而当 b = 1 的时候,将不起调节作用。

而 k1 和 b 都是可以用户自己设定的,如下实例,可以在创建Mapping的时候指定一个 similarity 的k1 和 b 值,并应用到每个字段中:

  1. PUT test_similarity_index
  2. {
  3. "settings": {
  4. "index": {
  5. "similarity": {
  6. "my_similarity": {
  7. "type": "BM25",
  8. "k1": 1.3,
  9. "b": 0.76
  10. }
  11. }
  12. }
  13. },
  14. "mappings": {
  15. "properties" : {
  16. "title" : { "type" : "text", "similarity" : "my_similarity" }
  17. }
  18. }
  19. }

五、总结

本文为你介绍了数据相关性的原理,我们从自己设计相关性算分模型出发,学习了经典的 TF-IDF 、BM25 算分模型。

TF-IDF 模型中主要有3个部分:

  1. TF 是词项在文档中出现的频率,代表了这个词项对文档的重要程度。其中 Lucene 的 TF 实现为 t 在文档 d 中出现次数的平方根。
  2. DF 词项在整个语料库中出现的频率,DF 越大词项的重要程度也就越低。
  3. IDF 也叫逆文档频率,代表了词项在语料库中的稀有程度,其在计算得分时作为 TF 的权重。

在 ES 5.0 的版本后,ES 默认使用了 BM25 的算分模型,相对 TF-IDF 来说,其优化点在于降低 TF 对算分结果的影响力,并且引入了可以自定义的参数:k1 和 b,其中 k1 用来调节词频的影响力的,而 b 则控制了文档篇幅对于得分的影响程度。

好了,在学习了 ES 的算分模型后,大家可以结合之前学习的数据操作、查询的 API 来做一些实践啦。

可以使用 explain API 来查看算分的过程:

  1. POST your_index/_search
  2. {
  3. "explain": true, # 开启 explain
  4. "query": .....
  5. }