Elasticsearch:如何在提高跨索引搜索相关性的同时返回更多相关的文档

news2024/11/14 19:52:51

在 Elasticsearch 的搜索中,经常遇到的情况是,我们创建一个 data view 或者 index pattern 跨多个索引,这样我们可以对它们进行统一的搜索。我们有遇到这样的情况:完全匹配的文档的分数反而低于部分匹配的文档,这是为什么呢?

例子展示

例子一

我们先看一下如下的一个例子:

POST /_bulk
{"index": {"_index": "my_index"}}
{"name": "Vincent van Gogh"}
{"index": {"_index": "my_index"}}
{"name": "Rembrandt van Rijn"}
{"index": {"_index": "my_index"}}
{"name": "Frans Hals"}
{"index": {"_index": "my_index"}}
{"name": "Johann Adam Ackermann"}
{"index": {"_index": "my_index"}}
{"name": "Piet Mondriaan"}
{"index": {"_index": "my_index"}}
{"name": "Claude Monet"}
{"index": {"_index": "my_index"}}
{"name": "Jackson Pollock"}
{"index": {"_index": "my_index"}}
{"name": "Andy Warhol"}
{"index": {"_index": "my_index"}}
{"name": "Frida Kahlo"}
{"index": {"_index": "my_index"}}
{"name": "Johannes Vermeer"}
{"index": {"_index": "my_index"}}
{"name": "Leonardo da Vinci"}
{"index": {"_index": "my_index"}}
{"name": "Pieter Breugel"}
{"index": {"_index": "my_index"}}
{"name": "Johann Sebastian Bach"}
{"index": {"_index": "my_index"}}
{"name": "Johann Christoph Bach"}
{"index": {"_index": "my_index"}}
{"name": "Johann Ambrosius Bach"}
{"index": {"_index": "my_index"}}
{"name": "Clara Schumann"}

在上面索引  my_index,我把所有的文档放到这个索引里。我们进行如下的搜索:

GET my_index/_search?filter_path=**.hits
{
  "query": {
    "match": {
      "name": "johann sebastian bach"
    }
  }
}

这里搜索的 “johann sebastian bach” 是其中一个文档里的 name。上面搜索返回的结果是:

{
  "hits": {
    "hits": [
      {
        "_index": "my_index",
        "_id": "gRoPQ4YB2XodIZsbxfzo",
        "_score": 4.8769255,
        "_source": {
          "name": "Johann Sebastian Bach"
        }
      },
      {
        "_index": "my_index",
        "_id": "ghoPQ4YB2XodIZsbxfzo",
        "_score": 2.6585994,
        "_source": {
          "name": "Johann Christoph Bach"
        }
      },
      {
        "_index": "my_index",
        "_id": "gxoPQ4YB2XodIZsbxfzo",
        "_score": 2.6585994,
        "_source": {
          "name": "Johann Ambrosius Bach"
        }
      },
      {
        "_index": "my_index",
        "_id": "eBoPQ4YB2XodIZsbxfzo",
        "_score": 1.214482,
        "_source": {
          "name": "Johann Adam Ackermann"
        }
      }
    ]
  }
}

很显然这个是我们希望的结果。文档含有 “johann sebastian bach” 排在前一名。这个完全是符合我们的搜索的习惯,因为这个搜索结果最匹配我们的搜索内容。

例子二

我们现在使用另外一种方法来展示。这次,我们把上面的文档不是写入到同一个索引中,而是把它们分别写入到两个索引中:

POST /_bulk
{"index": {"_index": "painters"}}
{"name": "Vincent van Gogh"}
{"index": {"_index": "painters"}}
{"name": "Rembrandt van Rijn"}
{"index": {"_index": "painters"}}
{"name": "Frans Hals"}
{"index": {"_index": "painters"}}
{"name": "Johann Adam Ackermann"}
{"index": {"_index": "painters"}}
{"name": "Piet Mondriaan"}
{"index": {"_index": "painters"}}
{"name": "Claude Monet"}
{"index": {"_index": "painters"}}
{"name": "Jackson Pollock"}
{"index": {"_index": "painters"}}
{"name": "Andy Warhol"}
{"index": {"_index": "painters"}}
{"name": "Frida Kahlo"}
{"index": {"_index": "painters"}}
{"name": "Johannes Vermeer"}
{"index": {"_index": "painters"}}
{"name": "Leonardo da Vinci"}
{"index": {"_index": "painters"}}
{"name": "Pieter Breugel"}
{"index": {"_index": "composers"}}
{"name": "Johann Sebastian Bach"}
{"index": {"_index": "composers"}}
{"name": "Johann Christoph Bach"}
{"index": {"_index": "composers"}}
{"name": "Johann Ambrosius Bach"}
{"index": {"_index": "composers"}}
{"name": "Clara Schumann"}

如上所示,我们把前一部分的文档写入到 painters 里,二后面的一些文档写入到 composers 这个索引中。

现在,为了说明问题,让我们搜索名为“Johann Sebastian Bach” 的人:

GET /painters,composers/_search?filter_path=**.hits
{
  "query": {
    "match": {
      "name": "johann sebastian bach"
    }
  }
}

上面搜索的结果为:

{
  "hits": {
    "hits": [
      {
        "_index": "painters",
        "_id": "uBoWQ4YB2XodIZsbJfyz",
        "_score": 1.9334917,
        "_source": {
          "name": "Johann Adam Ackermann"
        }
      },
      {
        "_index": "composers",
        "_id": "wRoWQ4YB2XodIZsbJfyz",
        "_score": 1.8485742,
        "_source": {
          "name": "Johann Sebastian Bach"
        }
      },
      {
        "_index": "composers",
        "_id": "whoWQ4YB2XodIZsbJfyz",
        "_score": 0.6877716,
        "_source": {
          "name": "Johann Christoph Bach"
        }
      },
      {
        "_index": "composers",
        "_id": "wxoWQ4YB2XodIZsbJfyz",
        "_score": 0.6877716,
        "_source": {
          "name": "Johann Ambrosius Bach"
        }
      }
    ]
  }
}

从上面的搜索的结果中我们可以看出来,排名第一的是 "Johann Adam Ackermann",而第二名才是我们真正想要的结果 "Johann Sebastian Bach"。这个完全颠覆了我们对搜索的认知。哇,这是怎么回事? 与我们的预期相反,Bach 并不是最重要的搜索结果。 虽然我们的作曲家索引中有 “Johann Sebastian Bach”(得分 ~1.8485742)的字面匹配,但画家 “Johann Adam Ackermann” 只匹配我们搜索查询的一小部分,得分更高(~1.9334917)!


请解释一下

一如既往,我们可以向 Elasticsearch 寻求解释:

GET /painters,composers/_search
{
  "explain": true, 
  "query": {
    "match": {
      "name": "johann sebastian bach"
    }
  }
}

这给了我们一个关于正在发生的事情的提示:与我们人类不同,Elasticsearch 不知道 “Johann Sebastian Bach” 这个名字是一个连贯的单元,因此它单独搜索每个术语。

  1. 首先,分词器将查询分为三个词:johann OR sebastian OR bach。
  2. 然后,Elasticsearch 分别搜索每个术语。
  3. 最后,它通过合并每个术语的分数来计算总分。

所以 Elasticsearch 默认运行 OR 查询。 也就是说,至少有一个搜索词必须匹配,但不一定全部匹配。 这解释了为什么 Johann Adam Ackermann 包含在结果中,即使只有一个词('Johann')匹配我们的查询。

逆向文件频率在起作用

如果你对 inverse document frequency 还不是很清楚的话,请阅读我之前的文章 “Elasticsearch:分布式计分”。但这还没有回答为什么 Acermann 的排名高于 Bach 的问题。 是什么让 Ackermann 更具相关性? 这与 Elasticsearch 计算相关性的方式有关:它依赖于 TF/IDF 算法。 IDF(逆向文档频率)部分让我们很头疼:对于一个给定的搜索词,它出现在越多的文档中,它被认为越不相关。

因此,出现在许多文档中的术语具有较低的权重。 一般来说,这是有道理的:如果你搜索 “the well-tempered keyboard”,你不会对所有包含常见术语(如 “the”)的文档感兴趣,而只对少数提到键盘的文档感兴趣,最好是对 well-tempered。

如果你查看上面的数据,你会发现这两个索引加起来包含四位 Johann 和三位 Bach。 所以这仍然使 Bach 成为更独特、更相关的术语,不是吗? 不幸的是不是,因为:

每个字段都有自己的倒排索引,因此,对于 TF/IDF 而言,字段的值就是文档的值。

也就是说,我们需要在字段层面进行区分,而不是(仅)在索引层面。 (即使两个索引中的字段都称为 name,但它们属于两个不同的索引这一事实使它们成为两个字段。)在这种情况下,我们在 painters.name 字段中只有一个 Johann (Ackermann),在 composers.name 中只有三个,这确实将 painers 的相关性提高到了 composers(Johann Sebastian Bach)之上。

解决方案 1:仅匹配完整结果

正如我们在上面看到的,Elasticsearch 默认使用 OR 组合术语。 那么,一个明显的解决方案是告诉 Elasticsearch 匹配所有搜索词。 你可以通过将运算符更改为 AND 来实现:

GET /painters,composers/_search
{
  "query": {
    "match": {
      "name": {
        "query": "johann sebastian bach",
        "operator": "and"
      }
    }
  }
}

就是这样,我们只得到一个结果,它是 johann sebastian bach。 完毕?

好吧,如果用户将 Bach 与其他著名作曲家混淆,而是搜索 johann van bach 怎么办? 该查询现在返回零结果(因为在 Bach 的名字中找不到 van),这对我们的用户来说有点太苛刻了。

解决方案 2:支持完整结果

我们可以通过用 minimum_should_match 替换自定义 operator 来解决这个问题:

GET /painters,composers/_search
{
  "query": {
    "match": {
      "name": {
        "query": "johann sebastian bach",
        "minimum_should_match": "2<75%"
      }
    }
  }
}

2<75% 表示

  • 如果你只提供两个搜索词(例如,johann bach),则它们必须全部匹配 (johann AND bach);
  • 但如果你提供两个以上的术语(例如,johann van bach),则只有 75%(向下舍入)必须匹配,因此这归结为(johann AND van)或(van AND bach)或(johann AND bach)。

现在我们的搜索结果只包含三位 Bach,Johann Sebastian 安排名最高:

{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 2,
    "successful": 2,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 3,
      "relation": "eq"
    },
    "max_score": 1.8485742,
    "hits": [
      {
        "_index": "composers",
        "_id": "wRoWQ4YB2XodIZsbJfyz",
        "_score": 1.8485742,
        "_source": {
          "name": "Johann Sebastian Bach"
        }
      },
      {
        "_index": "composers",
        "_id": "whoWQ4YB2XodIZsbJfyz",
        "_score": 0.6877716,
        "_source": {
          "name": "Johann Christoph Bach"
        }
      },
      {
        "_index": "composers",
        "_id": "wxoWQ4YB2XodIZsbJfyz",
        "_score": 0.6877716,
        "_source": {
          "name": "Johann Ambrosius Bach"
        }
      }
    ]
  }
}

但是,这会从搜索结果中删除画家 Johann Ackermann,我们可能希望将其作为 “johann” 的部分匹配项返回。

解决方案 3:以正确的方式支持完整的结果

因此,将运算符更改为 AND(解决方案 1)和提供 minimum_should_match(解决方案 2)都过于严格。 一个更好、更灵活的解决方案是支持完整结果,但仍然包括部分匹配的结果。

解决这个问题的方法是利用 should 子句在复合 bool 查询中的工作方式。 关键字应该类似于常规的 OR,但在一个重要方面有所不同:匹配的子句越多,文档的相关性就越高。

这使得将我们喜欢的结果指定为一组 should 子句变得非常自然。 我们首先将原始查询包装在 bool 查询中:

GET /painters,composers/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "name": "johann sebastian bach"
          }
        }
      ]
    }
  }
}

虽然这还没有改变我们的搜索结果,但它开辟了添加更多 should 子句的途径。 因此,在此基础上,如果文档包含与查询顺序相同的术语组合,我们可以认为该文档更相关。 用 Elasticsearch 的说法,这是一个短语匹配。 只需为此添加一个 should 子句:

GET /painters,composers/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "name": "johann sebastian bach"
          }
        },
        {
          "match_phrase": {
            "name": "johann sebastian bach"
          }
        }
      ]
    }
  }
}

最后,我们可以将 minimum_should_match 查询添加回我们的复合查询:

GET /painters,composers/_search?filter_path=**.hits
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "name": "johann sebastian bach"
          }
        },
        {
          "match_phrase": {
            "name": "johann sebastian bach"
          }
        },
        {
          "match": {
            "name": {
              "query": "johann sebastian bach",
              "minimum_should_match": "2<75%"
            }
          }
        }
      ]
    }
  }
}

这给了我们正在寻找的东西:

{
  "hits": {
    "hits": [
      {
        "_index": "composers",
        "_id": "wRoWQ4YB2XodIZsbJfyz",
        "_score": 5.5457225,
        "_source": {
          "name": "Johann Sebastian Bach"
        }
      },
      {
        "_index": "painters",
        "_id": "uBoWQ4YB2XodIZsbJfyz",
        "_score": 1.9334917,
        "_source": {
          "name": "Johann Adam Ackermann"
        }
      },
      {
        "_index": "composers",
        "_id": "whoWQ4YB2XodIZsbJfyz",
        "_score": 1.3755432,
        "_source": {
          "name": "Johann Christoph Bach"
        }
      },
      {
        "_index": "composers",
        "_id": "wxoWQ4YB2XodIZsbJfyz",
        "_score": 1.3755432,
        "_source": {
          "name": "Johann Ambrosius Bach"
        }
      }
    ]
  }
}

结论

所有搜索都是 precision 和 recall 之间的权衡。 为了在两者之间取得良好的平衡,你可以使用 should 子句以增加特异性来描述您想要的结果。 这首先为你提供最佳结果,同时保持较长的搜索结果,但相关结果较少(但仍然)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/341361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Synchronized和Lock的区别

在分布式开发中&#xff0c;锁是控制线程安全的重要方式。Java提供了两种锁机制synchronized 和 Lock。 1、特性区别 Synchronized是Java内置的线程同步关键字&#xff1b; Lock是JUC包下面的一个接口&#xff0c;它有很多实现类&#xff0c;比如ReentrantLock就是它的一个实…

内存优化 · 基础论 · 初识 Android 内存优化

【小木箱成长营】内存优化系列文章&#xff1a; 内存优化 工具论 常见的 Android 内存优化工具和框架 内存优化 方法论 揭开内存优化神秘面纱 内存优化 实战论 内存优化实践与应用 Tips: 关注微信公众号小木箱成长营&#xff0c;回复"内存优化"可免费获得内存优…

Linux驱动开发(二)

一、驱动流程 驱动需要以下几个步骤才能完成对硬件的访问和操作&#xff1a; 模块加载函数 module_init注册主次设备号 <应用程序通过设备号找到设备>驱动设备文件 <应用程序访问驱动的方式> 1、手动创建 &#xff08;mknod&#xff09;2、程序自动创建file_oper…

Synchronized 原理

基本特点(只考虑 JDK 1.8): 1. 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁.2. 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁.3. 实现轻量级锁的时候大概率用到的自旋锁策略4. 是一种不公平锁5. 是一种可重入锁6. 不是读写锁 加锁工作过程 JVM 将 s…

【Kafka】【三】安装Kafka服务器

Kafka基本知识 Kafka介绍 Kafka是最初由Linkedin公司开发&#xff0c;是⼀个分布式、⽀持分区的&#xff08;partition&#xff09;、多副本的 &#xff08;replica&#xff09;&#xff0c;基于zookeeper协调的分布式消息系统&#xff0c;它的最⼤的特性就是可以实时的处理 …

蓝牙安全(AES-CCM)

目录 AES-CCM CCM规范加密过程 CCM规范解密认证过程 formatting函数 counter generation函数 蓝牙AES-CCM加密流程 参考文献 AES-CCM Advanced Encryption Standard-Counter with Cipher Block Chaining-Message Authentication Code 自蓝牙4.1起蓝牙的加密算法开始采…

RabbitMQ-其他问题

一、幂等性问题&#xff1a;消费者在消费MQ中的消息时&#xff0c;MQ已把消息发送给消费者&#xff0c;消费者在给MQ返回ACK时网络中断&#xff0c;故MQ未收到确认消息&#xff0c;该消息会重新发送给其他消费者&#xff0c;或者在网络重连后再次发送给消费者&#xff0c;但实际…

第三章虚拟机的克隆,快照,迁移删除

1.虚拟机的克隆 如果你已经安装了一台linux操作系统&#xff0c;你还想再更多的&#xff0c;没有必要再重新安装&#xff0c;你只需要克 隆就可以&#xff0c;看演示。 方式1&#xff0c;直接拷贝一份安装好的虚拟机文件,再用虚拟机打开这个文件方式2&#xff0c;使用vmware的…

企业三要素核验API接口,你了解多少?

企业三要素核验API接口是指哪些要素&#xff1f;企业三要素是一种有关企业实名认证的应用程序接口也称API&#xff0c;企业的名称、统一社会信用代码和法人代表姓名统称企业三要素。企业三要素核验API接口的资源来自国家工商总局数据库&#xff0c;通过数据库资料三个要素进行核…

字母板上的路径 题解,力扣官方出来挨打(小声)

字母板上的路径 我们从一块字母板上的位置 (0, 0) 出发&#xff0c;该坐标对应的字符为 board[0][0]。 在本题里&#xff0c;字母板为board [“abcde”, “fghij”, “klmno”, “pqrst”, “uvwxy”, “z”]&#xff0c;如下所示。 我们可以按下面的指令规则行动&#xff1a…

Dubbo基本原理和用法讲解

Dubbo基本原理和用法讲解 序言&#xff1a;学习一项新技术&#xff0c;一般从是什么、为什么、怎么用三个方面进行学习。本篇文章也不例外&#xff0c;笔者将从Dubbo是什么&#xff1f;、为什么会产生Dubbo技术&#xff1f;、如何在项目中使用Dubbo技术。最后&#xff0c;笔者…

基于springboot+vue的宠物商城系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

Fluent Python 笔记 第 6 章 使用一等函数实现设计模式

虽然设计模式与语言无关&#xff0c;但这并不意味着每一个模式都能在每一门语言中使用。1996 年&#xff0c;Peter Norvig 在题为“Design Patterns in Dynamic Languages”(http://norvig.com/design- patterns/)的演讲中指出&#xff0c;Gamma 等人合著的《设计模式:可复用面…

序列化和反序列化~如何实现自定义协议?jsoncpp的使用

目录 序列化反序列化的概念 为什么要进行序列化和反序列化&#xff1f; 自定义协议实现业务 jsoncpp实现序列化和反序列化 序列化&#xff1a; 反序列化&#xff1a; 自定义协议jsoncpp实现简易计算器服务整体逻辑 Server.cc Client.cc 运行结果 序列化反序列化的概念…

重建control遗漏数据文件,reseltogs报ORA-1555错误处理----惜分飞

又一客户,误删除oracle redo导致数据库无法正常启动,自己尝试重建ctl,结果遗漏部分oracle数据文件并且尝试过resetlogs,导致部分文件resetlogs scn不一致.导致重建ctl失败Fri Feb 10 12:41:20 2023CREATE CONTROLFILE REUSE DATABASE "orcl"RESETLOGS NOARCHIVELOG M…

GO 中的 init 函数

前言 go 语言中有一个非常神奇的函数 init ,它可以在所有程序执行开始前被执行&#xff0c;并且每个 package 下面可以存在多个 init 函数&#xff0c;我们一起来看看这个奇怪的 init 函数。 init 特性 init 函数在 main 函数之前执行&#xff0c;并且是自动执行&#xff1b…

Docker网络实现原理

目录 1 Docker网络实现原理 1.2 为容器创建端口映射 方法一&#xff1a;随机映射端口&#xff08;从32768开始&#xff09; 方法二&#xff1a;指定映射端口 1.3 查看容器的输出和日志信息 二 Docker的网络模式 2.1 Docker的网络模式&#xff08;41&#xff09; 2.2 查看…

【大数据趋势】2月12日 货币发动机牵着港股和A股走,历史不会简单重演,但是会用类似的方法让人再踏入同一条河流

行情核心源头之一 : 离岸人民币和美元趋势历史对比&#xff0c;预示着一个阶段底部正在形成中 历史总是很容易忘记&#xff0c;应该很少有人记得18年发生了什么。还是让大数据程序来对比一下。【红色标记1】RSI预示着價格強度的动能情况&#xff0c;同样是达到了一个高点&…

2021 WAIC 世界人工智能大会参会总结

前言 2021 年世界人工智能大会&#xff08;WAIC&#xff09;于2021年7月7日至10日在上海世博展览馆举办&#xff0c;本届大会继续秉持「智联世界」的理念&#xff0c;以「众智成城」为主题&#xff0c;促进全球人工智能创新思想、技术、应用、人才和资本的集聚和交流&#xff…

JS逆向案例分享----prototype的妙用

方向不对&#xff0c;努力白费。爬虫也是如此。今天分享的案例很能说明问题。目标&#xff1a;某集团公司采购信息aHR0cHM6Ly9lYy5taW5tZXRhbHMuY29tLmNuL29wZW4vaG9tZS9wdXJjaGFzZS1pbmZv浏览器抓包&#xff0c;请求载荷里就一个加密参数param全局搜索“param:”&#xff0c;有…