Lucene 的实用评分函数

对于多词查询,Lucene 使用 布尔模型(Boolean model)TF/IDF 以及 向量空间模型(vector space model) ,然后将它们组合到单个高效的包里以收集匹配文档并进行评分计算。

一个多词查询

  1. GET /my_index/doc/_search
  2. {
  3. "query": {
  4. "match": {
  5. "text": "quick fox"
  6. }
  7. }
  8. }

会在内部被重写为:

  1. GET /my_index/doc/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "should": [
  6. {"term": { "text": "quick" }},
  7. {"term": { "text": "fox" }}
  8. ]
  9. }
  10. }
  11. }

bool 查询实现了布尔模型,在这个例子中,它会将包括词 quickfox 或两者兼有的文档作为查询结果。

只要一个文档与查询匹配,Lucene 就会为查询计算评分,然后合并每个匹配词的评分结果。这里使用的评分计算公式叫做 实用评分函数(practical scoring function) 。看似很高大上,但是别被吓到——多数的组件都已经介绍过,下一步会讨论它引入的一些新元素。

  1. score(q,d) = (1)
  2. queryNorm(q) (2)
  3. · coord(q,d) (3)
  4. · ( (4)
  5. tf(t in d) (5)
  6. · idf(t (6)
  7. · t.getBoost() (7)
  8. · norm(t,d) (8)
  9. ) (t in q) (4)

<1> score(q,d) 是文档 d 与查询 q 的相关度评分。

<2> queryNorm(q)查询归一化 因子 (新)。

<3> coord(q,d)协调 因子 (新)。

<4> 查询 q 中每个词 t 对于文档 d 的权重和。

<5> tf(t in d) 是词 t 在文档 d 中的 词频

<6> idf(t) 是词 t逆向文档频率

<7> t.getBoost() 是查询中使用的 boost>>。

<8> norm(t,d)字段长度归一值 ,与 索引时字段层 boost (如果存在)的和(新)。

上节已介绍过 scoretfidf 。现在来介绍 queryNormcoordt.getBoostnorm

我们会在本章后面继续探讨 查询时的权重提升 的问题,但是首先需要了解查询归一化、协调和索引时字段层面的权重提升等概念。

查询归一因子

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

[TIP]

尽管查询归一值的目的是为了使查询结果之间能够相互比较,但是它并不十分有效,因为相关度评分 _score 的目的是为了将当前查询的结果进行排序,比较不同查询结果的相关度评分没有太大意义。


这个因子是在查询过程的最前面计算的,具体的计算依赖于具体查询,一个典型的实现如下:

  1. queryNorm = 1 / sumOfSquaredWeights (1)

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

TIP: 相同查询归一化因子会被应用到每个文档,不能被更改,总而言之,可以被忽略。

查询协调

协调因子coord )可以为那些查询词包含度高的文档提供奖励,文档里出现的查询词越多,它越有机会成为好的匹配结果。

设想查询 quick brown fox ,每个词的权重都是 1.5 。如果没有协调因子,最终评分会是文档里所有词权重的总和。例如:

  • 文档里有 fox -> 评分: 1.5
  • 文档里有 quick fox -> 评分: 3.0
  • 文档里有 quick brown fox -> 评分: 4.5

协调因子将评分与文档里匹配词的数量相乘,然后除以查询里所有词的数量,如果使用协调因子,评分会变成:

  • 文档里有 fox -> 评分: 1.5 * 1 / 3 = 0.5
  • 文档里有 quick fox -> 评分: 3.0 * 2 / 3 = 2.0
  • 文档里有 quick brown fox -> 评分: 4.5 * 3 / 3 = 4.5

协调因子能使包含所有三个词的文档比只包含两个词的文档评分要高出很多。

回想将查询 quick brown fox 重写成 bool 查询的形式:

  1. GET /_search
  2. {
  3. "query": {
  4. "bool": {
  5. "should": [
  6. { "term": { "text": "quick" }},
  7. { "term": { "text": "brown" }},
  8. { "term": { "text": "fox" }}
  9. ]
  10. }
  11. }
  12. }

bool 查询默认会对所有 should 语句使用协调功能,不过也可以将其禁用。为什么要这样做?通常的回答是——无须这样。查询协调通常是件好事,当使用 bool 查询将多个高级查询如 match 查询包裹的时候,让协调功能开启是有意义的,匹配的语句越多,查询请求与返回文档间的重叠度就越高。

但在某些高级应用中,将协调功能关闭可能更好。设想正在查找同义词 jumpleaphop 时,并不关心会出现多少个同义词,因为它们都表示相同的意思,实际上,只有其中一个同义词会出现,这是不使用协调因子的一个好例子:

  1. GET /_search
  2. {
  3. "query": {
  4. "bool": {
  5. "disable_coord": true,
  6. "should": [
  7. { "term": { "text": "jump" }},
  8. { "term": { "text": "hop" }},
  9. { "term": { "text": "leap" }}
  10. ]
  11. }
  12. }
  13. }

当使用同义词的时候(参照: 同义词 ),Lucene 内部是这样的:重写的查询会禁用同义词的协调功能。大多数禁用操作的应用场景是自动处理的,无须为此担心。

索引时字段层权重提升

我们会讨论 查询时的权重提升,让字段 权重提升 就是让某个字段比其他字段更重要。当然在索引时也能做到如此。实际上,权重的提升会被应用到字段的每个词,而不是字段本身。

将提升值存储在索引中无须更多空间,这个字段层索引时的提升值与字段长度归一值(参见 字段长度归一值 )一起作为单个字节存于索引, norm(t,d) 是前面公式的返回值。

[WARNING]

我们不建议在建立索引时对字段提升权重,有以下原因:

  • 将提升值与字段长度归一值合在单个字节中存储会丢失字段长度归一值的精度,这样会导致 Elasticsearch 不知如何区分包含三个词的字段和包含五个词的字段。

  • 要想改变索引时的提升值,就必须重新为所有文档建立索引,与此不同的是,查询时的提升值可以随着每次查询的不同而更改。

  • 如果一个索引时权重提升的字段有多个值,提升值会按照每个值来自乘,这会导致该字段的权重急剧上升。

查询时赋予权重 是更为简单、清楚、灵活的选择。


了解了查询归一化、协同和索引时权重提升这些方式后,可以进一步了解相关度计算最有用的工具:查询时的权重提升。