当你负责搜索引擎时,不用多说,你应该充分了解有关搜索相关性的尽可能多的详细信息。 虽然大多数人不需要学习每条信息,但需要了解搜索。 你至少应该对 recall (查全率)和 precision (精度)有基本的了解。 本文将重点介绍与搜索相关性的 precision 和 recall。
什么是相关性?
- 您是否能够找到所需的所有文档?
- 返回了多少无关的文件?
- 文件排名如何?
Precision vs. Recall
precsion 和 recall 是搜索相关性的两个基本指标。 给定特定查询和搜索引擎返回的文档集(结果集),这些度量的定义如下:
- precision 是结果集中相关文档的百分比。
- 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 可以通过撒大网以获得更多的结果。
- 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