概要

本篇介绍怎样在全文字段中搜索到最相关的文档,包含手动控制搜索的精准度,搜索条件权重控制。

手动控制搜索的精准度

搜索的两个重要维度:相关性(Relevance)和分析(Analysis)。

相关性是评价查询条件与结果的相关程度,并对相关程度进行排序,一般使用TF/IDF方法。

分析是指将索引文档与查询条件规范化的一个过程,目的是建立倒排索引时,尽可能地提升召回率。

match查询原理

匹配查询match是核心查询语法,它的主要应用场景就是全文搜索,我们举一个示例:

GET /music/children/_search
{
"query": {
"match": {
"name": "wake"
}
}
}

Elasticsearch执行的步骤:

  1. 检索字段类型:match的字段name为text类型,是一个analyzed的字段,那么查询条件的字符串也应该被analyzed。
  2. 分析查询字符串:将查询字符串"wake"传入分词器中(与mapping的分词器一致),因为只有一个单词,所以match最终执行的是单个底层的term查询。
  3. 查找匹配文档:用term倒排索引中查找wake然后获取一组包含该词的文档。
  4. 为每个文档评分:用term查询计算每个文档相关度评分,即TF、IDF、length norm算法。

得到的结果如下:

{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.2876821,
"hits": [
{
"_index": "music",
"_type": "children",
"_id": "2",
"_score": 0.2876821,
"_source": {
"id": "a810fad4-54cb-59a1-9b7a-82adb46fa58d",
"author": "John Smith",
"name": "wake me, shark me",
"content": "don't let me sleep too late, gonna get up brightly early in the morning",
"language": "english",
"tags": "enlighten",
"length": 55,
"isRelease": true,
"releaseDate": "2019-12-21"
}
}
]
}
}

因为样本数据的问题,暂时只有一条文档匹配。

搜索name中包含"you"或"sunshine"的文档

GET /music/children/_search
{
"query": {
"match": {
"name": "you sunshine"
}
}
}

搜索name中包含"you"和"sunshine"的文档

GET /music/children/_search
{
"query": {
"match": {
"name": {
"query": "you sunshine",
"operator": "and"
}
}
}
}

搜索精准度控制的第一步:使用and关键字。如果希望所有搜索关键字都要匹配,可以用and来实现。

搜索"you"、"my"、"sunshine"、"teeth" 4个关键字中,至少包含3个的文档

GET /music/children/_search
{
"query": {
"match": {
"name": {
"query": "you my sunshine teeth",
"minimum_should_match": "75%"
}
}
}
}

搜索精准度控制的第二步:指定至少匹配其中的多少个关键字,才能作为结果返回

bool组合多个搜索条件

用bool组合,可以完成更加个性化的搜索需求,例如我们查找名称包含"sunshine",但不包含"teeth",允许出现"you"、"my"关键字,示例如下:

GET /music/children/_search
{
"query": {
"bool": {
"must": { "match": { "name": "sunshine" }},
"must_not": { "match": { "name": "teeth" }},
"should": [
{ "match": { "name": "my" }},
{ "match": { "name": "you" }}
]
}
}
}

should对相关度评分计算的影响

以上面的bool为例子,我们只讨论匹配的文档,按自己的理解,对文档进行粗略排名:

  1. 最符合搜索条件的

文档中同时包含should中的"my"、"you"两个关键字。

  1. 很符合搜索条件的

文档中包含should中的"my"或"you"两个关键字的其中一个。

  1. 符合搜索条件的

文档中不包含should中的"my"和"you"。

像must not这种硬性条件,不匹配都不会出现在结果集里,主要起到排除文档作用,不参与评分计算。但should也能影响相关度评分,匹配得越多,评分就越高。

bool查询会为每个文档计算相关度评分_score ,再将所有匹配的 must 和 should 语句的分数 _score 求和,最后除以 must 和 should 语句的总数。

这里不详细讲解评分计算的具体细节和分数,了解should对其有影响即可。

should匹配原则

如果查询条件中有must存在,那么should匹配的数量不作要求;如果没有must,则should必须要匹配一个,要不然全是should条件的,所有文档都能匹配,就失去了搜索的意义。

如果带上minimum_should_match,那么就能做更精细的控制,可以指定必须要匹配几个should,才能返回结果集,如下两个示例是等同的:

GET /music/children/_search
{
"query": {
"match": {
"name": {
"query": "you my sunshine teeth",
"minimum_should_match": "75%"
}
}
}
}
GET /music/children/_search
{
"query": {
"bool": {
"should": [
{ "match": { "name": "you" }},
{ "match": { "name": "my" }},
{ "match": { "name": "sunshine" }},
{ "match": { "name": "teeth" }}
],
"minimum_should_match": 3
}
}
}

多词查询的底层原理

上一节当中我们提到的多词match的查询,Elasticsearch会将多词的term查询转换为bool查询,or查询使用should替代, and查询使用must,我们回顾一下上一节的示例:

or查询

下面两个查询是等价的

GET /music/children/_search
{
"query": {
"match": {
"name": "you sunshine"
}
}
} GET /music/children/_search
{
"query": {
"bool": {
"should": [
{"term": {"name": "you"}},
{"term": {"name": "sunshine"}}
]
}
}
}

and查询

下面两个查询也是等价的

GET /music/children/_search
{
"query": {
"match": {
"name": {
"query": "you sunshine",
"operator": "and"
}
}
}
} GET /music/children/_search
{
"query": {
"bool": {
"must": [
{"term": {"name": "you"}},
{"term": {"name": "sunshine"}}
]
}
}
}

minimum_should_match语法

下面两个查询仍然是等价的

GET /music/children/_search
{
"query": {
"match": {
"name": {
"query": "you my sunshine teeth",
"minimum_should_match": "75%"
}
}
}
} GET /music/children/_search
{
"query": {
"bool": {
"should": [
{ "match": { "name": "you" }},
{ "match": { "name": "my" }},
{ "match": { "name": "sunshine" }},
{ "match": { "name": "teeth" }}
],
"minimum_should_match": 3
}
}
}

minimum_should_match的值可以改,也会根据实际的条件来换算,比如我写个75%,但实际的搜索词条就只有3个,那么minimum_should_match的值就会变成66.6%,即至少需要匹配2条。

查询语句权重控制

bool查询里的多条件并列,默认权重是一样的,但实际的搜索当中,我们可能会特别某一些关键词给予特殊的关注,希望匹配关注度高的文档排序能靠前一些,boost语法可以帮助我们实现这一需求。

boost默认是1,可以在查询语句中自行设置,如下示例,我希望sunshine的权重要大一些:

GET /music/children/_search
{
"query": {
"bool": {
"must": {
"match": {
"name": {
"query": "sunshine",
"boost": 2
}
}
},
"should": [
{
"match": {
"name": "my"
}
},
{
"match": {
"name": "you"
}
}
]
}
}
}

在查询结果里可以看到,符合条件的文档的_score值变得更高一些。boost值的设置不是简单的线性增长,boost设置为2不表示_score也会简单的翻一倍,boost值在计算时有归一化处理,但具体的计算数值及过程不在此篇作详细的解释。

评分计算的小问题

我们的演示环境是单node多shard模式的,有些示例发现评分会出现特别高的现象,给人感觉不是很准确,这是为什么呢?

不准确的原因

我们简单回顾一下评分计算的几个算法:TF、IDF、Length Norm,其中IDF本意上是在所有的document中,搜索关键词的出现次数,实际上IDF默认在本地shard执行的次数统计,一个shard只包含部分数据,不能代表所有数据,这样做比较高效,但会带来误差,也是造成结果不准确的原因。

演示环境中出现较大偏差,还有一个原因是演示数据相对较少,如果只有一个document,并且该document符合查询条件,那么IDF的分数就会变得很高。

解决办法

  1. 生产环境数据量相对较大,默认使用_id进行路由,在概率分布下,其实每个shard的数据基本上比较均匀,不用太担心演示环境的这种情况。
  2. 演示环境可以将primary shard设置为1,只有一个shard,那IDF的值肯定是对的。
  3. 演示环境进行搜索时,带上search_type=dfs_query_then_fetch参数,会将local IDF取出来计算global IDF。注意性能问题,生产上禁用。

小结

本篇主要介绍了全文搜索几种手动控制精度的方式:逻辑操作符变换、should命中率设置、权重调整等手段,最后对评分计算的小问题进行的简单描述,谢谢。

专注Java高并发、分布式架构,更多技术干货分享与心得,请关注公众号:Java架构社区

可以扫左边二维码添加好友,邀请你加入Java架构社区微信群共同探讨技术

Elasticsearch系列---深入全文搜索的更多相关文章

  1. ElasticSearch 2 (14) - 深入搜索系列之全文搜索

    ElasticSearch 2 (14) - 深入搜索系列之全文搜索 摘要 在看过结构化搜索之后,我们看看怎样在全文字段中查找相关度最高的文档. 全文搜索两个最重要的方面是: 相关(relevance ...

  2. elasticsearch系列四:搜索详解(搜索API、Query DSL)

    一.搜索API 1. 搜索API 端点地址 从索引tweet里面搜索字段user为kimchy的记录 GET /twitter/_search?q=user:kimchy 从索引tweet,user里 ...

  3. elasticsearch中文分词+全文搜索demo

    本文假设你已经搭建好elasticsearch服务器,并在上面装了kibana和IK中文分词组件 elasticsearch+kibana+ik的安装,之前的文章有介绍,可参考. mapping介绍: ...

  4. Elasticsearch系列---结构化搜索

    概要 结构化搜索针对日期.时间.数字等结构化数据的搜索,它们有自己的格式,我们可以对它们进行范围,比较大小等逻辑操作,这些逻辑操作得到的结果非黑即白,要么符合条件在结果集里,要么不符合条件在结果集之外 ...

  5. elasticsearch系列五:搜索详解(查询建议介绍、Suggester 介绍)

    一.查询建议介绍 1. 查询建议是什么? 查询建议,为用户提供良好的使用体验.主要包括: 拼写检查: 自动建议查询词(自动补全) 拼写检查如图: 自动建议查询词(自动补全): 2. ES中查询建议的A ...

  6. Elasticsearch系列---多字段搜索

    概要 本篇介绍一下multi_match的best_fields.most_fields和cross_fields三种语法的场景和简单示例. 最佳字段 bool查询采取"more-match ...

  7. ElasticSearch 2 (18) - 深入搜索系列之控制相关度

    ElasticSearch 2 (18) - 深入搜索系列之控制相关度 摘要 处理结构化数据(比如:时间.数字.字符串.枚举)的数据库只需要检查一个文档(或行,在关系数据库)是否与查询匹配. 布尔是/ ...

  8. ElasticSearch 2 (17) - 深入搜索系列之部分匹配

    ElasticSearch 2 (17) - 深入搜索系列之部分匹配 摘要 到目前为止,我们介绍的所有查询都是基于完整术语的,为了匹配,最小的单元为单个术语,我们只能查找反向索引中存在的术语. 但是, ...

  9. ElasticSearch 2 (16) - 深入搜索系列之近似度匹配

    ElasticSearch 2 (16) - 深入搜索系列之近似度匹配 摘要 标准的全文搜索使用TF/IDF处理文档.文档里的每个字段或一袋子词.match 查询可以告诉我们哪个袋子里面包含我们搜索的 ...

随机推荐

  1. python tricks 01

    01: 考察range/sort/lambda 对以下数据进行排序 原数据: [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5] 目标数据: [0, -1, 1, -2, 2 ...

  2. python中的变量引用小结

    python的变量都可以看成是内存中某个对象的引用.(变量指向该内存地址存储的值) 1.python中的可更改对象和不可更改对象 python中的对象可以分为可更改(mutable)对象与不可更改(i ...

  3. springboot-security 登录 403

    之前一直使用shiro,刚开始使用security,大佬还请不要吐槽 security默认开启csrf防护,所谓csrf也就是伪请求.我们只需要把他关闭就好(因为我们的系统是在自己内网使用,不会有外部 ...

  4. Linux基础篇八:VIM

    新知识: 普通模式光标跳转: G     ##光标跳转到末端  (shift +g) gg   ##光标跳转到开端 Ngg 15gg  ##光标跳转到当前文本中的15行 $     ##光标移动到当前 ...

  5. mui webview 预加载

    所谓的预加载技术就是在用户尚未触发页面跳转时,提前创建目标页面,这样当用户跳转时,就可以立即进行页面切换,节省创建新页面的时间,提升app使用体验.mui提供两种方式实现页面预加载. 方式一:通过mu ...

  6. python实现个人信息随机生成

    """ 生成随机姓名.电话号码.身份证号.性别.应行卡号.邮箱 """ import random from firstname impor ...

  7. 多对多表创建、forms组件、cookie与session

    多对多表的三种创建方式 1.全自动(较为推荐) 优势:不需要你手动创建第三张表 不足:由于第三张表不是你手动创建的,所以表字段是固定的无法扩展 class Book(models.Model): ti ...

  8. android全功能音乐播放器、基于MVP-Clean + Weex + RxJava2 + Retrofit + Dagger2 + MTRVA的综合应用、图片滤镜处理等源码

    Android仿微信朋友圈查看图片下拽返回. Android图片滤镜处理,相机滤镜处理效果源码 Android自定义View源码:一个水平的进度条 基于MVP-Clean + Weex + RxJav ...

  9. android采用MVP漫画APP、适配刘海屏、小黄车主界面、录音波浪动画、综合APP等源码

    Android精选源码 一款采用MVP架构的仿完整漫画APP源码 Android适配刘海屏幕 基于Xmpp协议的即时通讯社交软件(客户端+服务端) Android小黄车(ofo)app主页菜单效果 一 ...

  10. JVM如何判断对象能否被回收

    •写在前面说起Java和C++,很容易想到让人疯狂的指针,Java使用了内存动态分配和垃圾回收技术,让我们从C++的各种指针问题中摆脱出来,更加专心于业务逻辑,不过如果我们需要深入了解java的JVM ...