Elasticsearch:理解搜索中的 precision 及 recall

当你负责搜索引擎时,不用多说,你应该充分了解有关搜索相关性的尽可能多的详细信息。 虽然大多数人不需要学习每条信息,但需要了解搜索。 你至少应该对 recall (查全率)和 precision (精度)有基本的了解。 本文将重点介绍与搜索相关性的 precision 和 recall。

什么是相关性?

  • 您是否能够找到所需的所有文档?
  • 返回了多少无关的文件?
  • 文件排名如何?

Precision vs. Recall

precsion 和 recall 是搜索相关性的两个基本指标。 给定特定查询和搜索引擎返回的文档集(结果集),这些度量的定义如下:

  • precision 是结果集中相关文档的百分比。
  • recall 是结果集中返回的相关文档的百分比。

precision 的定义是检索到的相关文档数除以总计检索到的文档数。 recall 是指检索到的相关文档数除以相关文档总数。

Elasticsearch 的目标是达到最佳 recall,这意味着执行搜索时,仅(和所有)相关文档被检索。 还需要检索尽可能多的相关文档,这意味着你通常需要使用最简单的过滤器和查询来优化 recall。

根据 precision 及 recall 的定义

上面有几个名字,我们这里来解释一下:

  • true positive:它表示的是真正的相关的搜索结果
  • false postive:它表示的是在搜索时返回的不相关的结果
  • false negative:表示的是在搜索时应该返回的结果,但是没有被正确返回
  • true negative:表示的是真正完全不相干的结果

我们可以通过上面的计算公式来计算出 precision 及 recall。

 

提高 precision 和 recall 的技巧

我们在实际的使用中,可以通过如下的方法来提高 Recall 及 Precision:

  • Recall 可以通过撒大网以获得更多的结果。
    • 我们可以通过 fuzziness,regex,wildcard 以及 should 来提高查询的面以获得更多的结果。但是这样的做法是可能返回一些很多不相干的文档,从而使得 precision 更差
    • 使用 should 从句而不是 must,或者使用 or 而不是 and 可以提高 recall
  • Precision:我们可以通过更加精准的搜索来提高搜索的精度
    • 比如完全匹配(比如 term query)的方法,或者通过 match_phrase 等方法。这些方法可能造成 recall 很差,因为我们把搜索的范围变小了,从而导致返回的结果很少 
    • 使用 must 而不是 should 来提高搜索的精确度

在实际的使用中,有很多的方法可能会提高 precision,也可能会提高 recall。那么我们有没有两全的办法呢?

 

例子

假如我们现在有如下的几个文档:

POST my_index/_bulk
{ "index" : { "_id" : "1" }}
{ "content" : "Elastic Stack is very useful" }
{ "index" : { "_id" : "2" }}
{ "content" : "I do not like stack though Elastic is nice" }
{ "index" : { "_id" : "3" } }
{ "content" : "Elastic and its stack are good" }
{ "index" : { "_id" : "4" } }
{ "content" : "What is stack?" }

假如我们想寻找的是 Elastic  Stack 这样的相关的内容。上面的第一条是最相关的,而且第三条也是很相关的。第二条可能不相关,极有可能是谈论完全不相关的内容。第四条也可能不相关。我们进行如下的搜索:

GET my_index/_search
{
  "query": {
    "match": {
      "content": "ELastic Stack"
    }
  }
}

上面的搜索将返回所有的文档,因为在默认的情况下,它返回所有含有 Elastic 及 Stack 的所有文档。

    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.5363642,
        "_source" : {
          "content" : "Elastic Stack is very useful"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.4988708,
        "_source" : {
          "content" : "Elastic and its stack are good"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.41238922,
        "_source" : {
          "content" : "I do not like stack though Elastic is nice"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 0.22667006,
        "_source" : {
          "content" : "What is stack?"
        }
      }
    ]

显然这种搜索的 recall 是非常高的。我们可以通过如下的方法来提高精度:

GET my_index/_search
{
  "query": {
    "match": {
      "content": {
        "query": "Elastic Stack",
        "operator": "and"
      }
    }
  }
}

也就是说,必须同时含有 Elastic 及 Stack 的文档才可以被搜索到。显然我们把网缩小了。返回的结果是:

    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.5363642,
        "_source" : {
          "content" : "Elastic Stack is very useful"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.4988708,
        "_source" : {
          "content" : "Elastic and its stack are good"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.41238922,
        "_source" : {
          "content" : "I do not like stack though Elastic is nice"
        }
      }
    ]

对于多个搜索词来说,我们也可以使用如下的方法:

GET my_index/_search
{
  "query": {
    "match": {
      "content": {
        "query": "Elastic Stack Otherword",
        "minimum_should_match": 2
      }
    }
  }
}

上面表它可以搜索上面三个单词 Elastic,Stack 以及 Otherword 中的两个匹配就可以了,虽然这个搜索也是使用 or 的关系。通过这样的方法,我们可以把网撒的很大,但是也做了一点现在,需要至少匹配两个单词。

这次精度提高了,但是它可能还不是我们所需要的。我们可以看一下 id 为 2 的文档,它极有可能不是我们想要的文档。为了更进一步提高精度,我们可以做如下的搜索:

GET my_index/_search
{
  "query": {
    "match_phrase": {
      "content": {
        "query": "Elastic Stack",
        "slop": 0
      }
    }
  }
}

上面需要搜索的内容是说,我们想查询按照  Elastic 及 Stack 的顺序来查询,必须是  Elastic 在前,Stack 在后,然后这两个词之前的距离还不能超过2个单词。返回的结果是:

    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.4880792,
        "_source" : {
          "content" : "Elastic Stack is very useful"
        }
      }
    ]

这次的返回结果显然只有一个,是精度最高的一次搜索。但是我们极有可能漏掉了一些非常相关的文档,比如文档 id 为 3 的文档。这个搜索的缺点是 recall 很低。那么我们在实际的使用该如何在提高 recall 的情况下,同时也提供相关性呢?

依据我们上面的技巧,我们可以做如下的改进:

GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "content": "Elastic Stack"
          }
        }
      ],
      "should": [
        {
          "match_phrase": {
            "content": {
              "query": "Elastic Stack",
              "slop": 2
            }
          }
        },
        {
          "match": {
            "content": {
              "query": "Elastic Stack",
              "operator": "and"
            }
          }
        }
      ]
    }
  }

在上面,我们通过第一个 match 撒了一张大网,把所有含有 Elastic 及 Stack 的文档都收入搜索的范围,这样提供了 recall。接着我们针对我们自己的业务需求来进行对分数来进行定制。对于 match_phrase 搜索来说,Elastic 以及 Stack 之间不超过两个单词距离的文档进行加分,同时对同时含有两个单词的文档进行加分。这样我们可以保证这些文档排在前面以提供他们的准确性。上面搜索的结果是:

    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.6090926,
        "_source" : {
          "content" : "Elastic Stack is very useful"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.2345327,
        "_source" : {
          "content" : "Elastic and its stack are good"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.82477844,
        "_source" : {
          "content" : "I do not like stack though Elastic is nice"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 0.22667006,
        "_source" : {
          "content" : "What is stack?"
        }
      }
    ]

上面显示,文档 1 及 3 具有高的相关性。它们排在搜索结果的前面。

 

总结

在实际的搜索场景中,我们一定要了解自己的业务搜索场景,并对你的业务场景进行定制自己的搜索。我在搜索的时候既要注意搜索文档的 recall,同时也要考虑搜索的 precision。没有一成不变的规则适用所有的场景。