作者:刘晓国
当你负责搜索引擎时,不用多说,你应该充分了解有关搜索相关性的尽可能多的详细信息。 虽然大多数人不需要学习每条信息,但需要了解搜索。 你至少应该对 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。没有一成不变的规则适用所有的场景。
参考:
【1】https://www.youtube.com/watch?v=CCTgroOcyfM&ab_channel=OfficialElasticCommunity