es 基于match_phrase/fuzzy的模糊匹配原理及使用

[版权声明]:本文章由danvid发布于http://danvid.cnblogs.com/,如需转载或部分使用请注明出处数据库

  

  在业务中常常会遇到相似数据库的"like"的模糊匹配需求,而es基于分词的全文检索也是有相似的功能,这个就是短语匹配match_phrase,但每每业务需求都不是那么简单,他想要有like的功能,又要容许有必定的容错(就是我搜索"东方宾馆"时,"广州花园宾馆酒店"也要出来,这个就不是单纯的"like"),下面就是我须要解析的问题(在此吐槽一下业务就是这么变态。。)api

  描述一个问题时首先须要描述业务场景:假设es中有一索引字段name存储有如下文本信息:elasticsearch

doc[1]:{"name":"广州东方宾馆酒店"}性能

doc[2]:{"name":"广州花园宾馆酒店"}spa

doc[3]:{"name":"东方公园宾馆"}code

需求要求在输入:"东方宾馆"的时候doc[1]排最前面doc[3]排第二doc[2]排第三,对于这个需求从简单的全文检索match来讲,doc[3]:{"name":"东方公园宾馆"}应该是第一位(注意:为了简化原理分析,分词咱们使用standard即按单个字分词)blog

   业务分析:显然对于上面的业务场景若是单独使用match的话,显然是不合适,由于按照standard分词,doc[3]的词条长度要比doc[1]的词条长度短,而词频又是都出现了[东][方][宾][馆]4个词,使用match匹配的话就会吧doc[3]排到最前面,显然业务但愿把输入的文字顺序匹配度最高的数据排前面,由于我确实要找的是"广州东方宾馆酒店"而不是"东方公园宾馆"你不能把doc[3]给我排前面,OK业务逻辑好像是对的那么怎么解决问题;索引

  解决问题前介绍一哈match_phrase原理(match的原理我就不说了本身回去看文档),简单点说match_phrase就是高级"like"。api以下:文档

GET test_index/_search
{
    "query": {
        "match_phrase" : {
            "message" : {
                "query" : "东方宾馆",
                "slop" : 2
            }
        }
    }
}

es在给文本分词的时候,除了分词以外还有一个词条标记,就是position,例如我按照standard对以上三个doc的name进行分词会变成这样:get

doc[1]:广[0],州[1],东[2],方[3],宾[4],馆[5],酒[6],店[7];

doc[2]:广[0],州[1],花[2],园[3],宾[4],馆[5],酒[6],店[7];

doc[3]:东[0],方[1],公[2],园[3],宾[4],馆[5];

query文本分词:东[0],方[1],宾[2],馆[3];

使用match_phrase时:

1.es会先过滤掉不符合的query条件的doc,即doc[2]中没有"东方"两个词汇,会被过滤掉

2.es会根据分词的position对分词进行过滤和评分,这个是就slop参数,默认是0,意思是查询分词只须要通过距离为0的转换就能够变成跟doc同样的文档数据,例如:对于doc[1]来讲slop就是0了,对于doc[3]slop就是2,即"宾"和"馆"最大位移这两个分词只须要最多移动2个位置就能够变成"东方宾馆"(反过来也同样,query的文本中的"宾"和"馆"只须要移动2个位置就能够变成"东方**宾馆"),用数学的理解就是doc[3]的宾[4]-东[0]=4,query文本中的宾[2]-东[0]=2,那么转换距离slop就是4-2=2,同理doc[3]的馆[5]-东[0]=5,query的是3,slop结果也是2,那么"宾"和"馆"最大的slop就是2,则query时设置slop等于2就能把doc[3]匹配出来,当设置为0时就是咱们数据库的"like"

  原理解析完了,就知道使用match只能匹配相关度即tf/idf,而分词之间的位置关系却没法保证,而match_phrase能保证分词间的邻近关系,那么就能够利用二者优点,结合搜索进行评分

GET test_index/_search
{
  "query": {
    "bool": {
      "must": {
        "match": { 
          "name": {
            "query": "东方宾馆"
          }
        }
      },
      "should": {
        "match_phrase": { 
          "name": {
            "query": "东方宾馆",
            "slop":  0
          }
        }
      }
    }
  }
}

这样就的结果就是至关于match_phrase帮match进行了相关度分数的加分,固然你也能够经过修改slop的参数来进行步控制分数,这个就根据用户需求来了;

    性能问题:其实使用match_phrase性能是要比单纯的全文检索性能低的,由于他要计算位置嘛,那么想提升性能能够经过先使用match进行过滤数据,而后利用rescore api对已经match的结果进行加分,这样就减小了部分没必要要的非match过滤:

GET test_index/_search
{
  "query": {
    "match": {
      "name":"东方宾馆"
    }
  },
"rescore": { "window_size": 30, "query": { "rescore_query": { "match_phrase": { "name": { "query": "东方宾馆", "slop": 0 } } } } } }
#window_size 是每一分片进行从新评分的顶部文档数量这个只要大于你可能分页的总数*每页的数量便可(pageNumber*pageSize)实际上不须要这么多由于翻页不可能很深,这个根据业务调整便可。

总结及注意点:

1.rescore其实跟bool结合同样是评分的相加,评分不在这里细说了;

2.虽然能够提升相关度评分,可是仍是存在可能match很低+一个很低的match_phrase结果没有单独只匹配了一个match的分数高的状况,可是这是很极限了,也是符合相关度评分原理的;

3.因为match_phrase是在搜索阶段进行的计算,会影响搜索效率,听说比term查询慢20倍,因此不要进行大文本量的字段搜索,尽可能进行标题,名字这种类型的搜索才使用这个;

4.本文章没有讨论在文本数据重复时的状况,即文本中有多个"[东][方][宾][馆]"和query文本中有多个"[东][方][宾][馆]"分词的状况,可是原理是同样的仍是取距离转换的最小值;

5.文中使用了standard分词,实际上可能会用不一样的分词器,可是建议使用match_phrase时使用标准的一个个分词,这样是方便进行邻近搜索的控制的,若是使用ik等分词,执行match_phrase时分词是不可控的,因此结果也是不可控。match使用ik,match_phrase用standard结合一块儿使用也是能够的;

6.邻近搜索效率较低,其实能够经过增长词库的方式进行单纯使用match匹配效率是最高的,前提是你知道客户会搜索什么,这又是另外一个研究话题了

 

更新[2019-05-22]:

补充:短语匹配match_phrase必需要知足下面的要求才能认定和["东方宾馆"]这个词条匹配(以standard分析器为例)

1.搜索的词必须有且仅有["东","方","宾","馆"]这几个词(对于中文是字)的一个或者多个,若是有其余的词(对于中文是字)是不会匹配到的,slop不是彻底等同于莱文斯坦距离,能够理解成字符的偏移

2.查询词中偏移量应该跟文档中词的偏移量同样,或者在slop的误差范围内,就是上文解析的意思。

 

这里讲解一下fuzzy和match_phrase的区别

1.fuzzy是词/项级别的模糊匹配,match_phrase是基于短语级别的

例如对于英文(standard分析器)来讲"dog cat bird"来讲"dog"就是一个词/词项,而"dog cat"就是一个短语,所以做用范围不同

2.fuzzy是基于莱文斯坦距离的,因此fuzzy是能够容错的例如你输入"dcg" 你也能够匹配到"dog cat bird",可是这里注意的是你的查询只能是单词条的查询,不能"dcg cat",若是你须要查询短语里面的拼写错误,可使用match的fuzziness参数,match_phrase是不容许出现不存在的词条的。

下面是对于fuzzy和match_phrase和match 添加fuzziness参数进行对比

文档内容是{"name":"dog cat bird"} 分析器是standard
--------------------------------------------------------------------------------------------------
1.使用拼写错误的"cot"可使用fuzzy匹配可是,若是是下面这种,短语是不能够的,输入应当是词条,而不是短语

GET test_save/_search { "query": { "fuzzy": { "name":{ "value": "bird cot", "fuzziness": 1 } } } }
--------------------------------------------------------------------------------------------------
2.这里能够匹配到由于match先分解成bird 和 cot 其中bird能够匹配到,同时cot也是能够匹配到,只不过度数要比输入"bird cat"要低
GET test_save
/_search { "query": { "match": { "name":{ "query": "bird cot", "fuzziness":1 } } } }
-----------------------------------------------------------------------------------------------
3.这里因为cot和文本中的cat不是同一个词,因此是没法匹配到的
GET test_save
/_search { "query": { "match_phrase": { "name": { "query": "bird cot", "slop": 1 } } } }

以上是对于英文的单词的解析,对于中文其实也是同样,只是因为中文若是使用standard一个词项就是一个字,所以使用所以分词后的数据对于fuzzy模糊匹配来讲意义不大,可是可使用keyword进行

GET test_save/_search
{
  "query": {
    "fuzzy": {
      "name.keyword":{
        "value": "东日宾馆",
        "fuzziness": 1
      }
    }
  }
}
这样是能够匹配到"东方宾馆"的数据的,可是没法匹配"广州东方宾馆"由于莱文斯坦距离已经不止1了

其实短语匹配应该叫临近查询更适合些

 

以上就是对模糊查询和短语匹配的解析和补充~

 

[说明]:elasticsearch版本5.6.4

相关文章
相关标签/搜索