ElasticSearch的match fuzzy查询参数详解

fuzzy在es中可以理解为模糊查询,搜索本身很多时候是不精确的,很多时候我们需要在用户的查询词中有部分错误的情况下也能召回正确的结果,但是计算机无法理解自然语言,因此我们只能通过一些算法替代语言理解能力实现类似的事情,前缀查询的实现比较简单但效果很难令人满意,就模糊查询而言es的fuzzy实现了一种复杂度和效果比较折中的查询能力。

字符的相似度-编辑距离

编辑距离是对两个字符串差异长度的量化,及一个字符至少需要处理多少次才能变成另一个字符,比如lucene和lucece只差了一个字符他们的编辑距离是1。

  • 莱文斯坦距离(Levenshtein distance)

编辑距离的一种,指两个字符串之间,由一个转成另一个所需的最少编辑操作次数。
允许的编辑包括:

  1. 将一个字符替换成另一个字符
  2. 插入一个字符
  3. 删除一个字符
  • Damerau–Levenshtein distance

莱文斯坦距离的一个扩展版 ,将相邻位置的两个字符的互换当做一次编辑,而在经典的莱文斯坦距离计算中位置互换是2次编辑。

ElasticSearch支持经典的Levenshtein距离和Damerau-Levenshtein距离,在es中对模糊查询的支持有两种方式match query和fuzzy query。

match query

使用方式如下所示:

GET index_name/_search
{
  "query": {
    "match": {
      "name": {
        "query": "elastic search",
        "fuzziness": 0,
        "prefix_length": 0,
        "max_expansions": 50,
        "transpositions": true
      }
    }
  }
}

下面对他支持的参数进行一些介绍:

  • fuzziness

本次查询允许的最大编辑距离,默认不开启模糊查询,相当于fuzziness=0。

支持的格式

  • 可以是数字(0、1、2)代表固定的最大编辑距离

  • 自动模式,AUTO:[low],[high]的格式,含义为:

    • 查询词长度在[0-low)范围内编辑距离为0(即强匹配)
    • [low, high)范围内允许编辑一次
    • >high允许编辑2次

也可以只写AUTO代表默认的自动模式,相当于AUTO:3,6

  • prefix_length

控制两个字符串匹配的最小相同的前缀大小,也即是前n个字符不允许编辑,必须与查询词相同,默认是0,大于0时可以显著提升查询性能,需要注意的是这里的 prefix_length作用在分词后的 term 级,也就是作用在每个分词的词根上而不是整个查询词上,对于上面的例子 elastic search 来说就是需要 elastic 和 search 都会严格匹配前两个字符来召回,是不是很意外。

  • max_expansions

这个参数比较迷惑,查询了相当的文档都对这个参数模糊不清,通过对Lucene源码的debug跟踪得出以下结论:

  1. Lucene的fuzzy 查询是通过query改写实现的,查询时会将倒排索引词典中的term集合和查询term做编辑距离计算,获取topN个距离最小的term作为query改写的词,这N个词会组成一组或查询,即改写为boolean query (改写词作为term query放在should clause)
  2. max_expansions影响的是这里的或查询的元素数目,实际上这个数目会被两个参数影响,另外一个是indices.query.bool.max_clause_count,最终取indices.query.bool.max_clause_countmax_expansions的最小值
  3. 倒排中的term总数会影响fuzzy查询的性能,term越多性能越差,文档总数不是影响fuzzy查询性能的关键因素。
  4. 值得注意的是分词后的term本身也算一次max_expansions计数,也就是说 max_expansions=1 时相当于不扩展(分词term级精确查询)
  5. max_expansions 是作用在分片级参数,如果分片数比较多的话,查询结果看起来和max_expansions设置数目不一致也是正常的
  • transpositions

将相邻位置字符互换算作一次编辑距离,如 ab -> ba,即使用Damerau–Levenshtein距离算法,默认开启,设置 transpositions=false将使用经典莱文斯坦距离算法。

  • minimum_should_match 的作用机制
  1. minimum_should_match作用在分词后的term级,即分词后的term无论是通过精确查找或者模糊查找命中算且算一次计数,对于同一个term扩展出来的term1、term2不做重复计数。
  2. 当operator 为 and 时,minimum_should_match >0 时会导致查不到结果,这是因为minimum_should_match的计算方法是should clause命中的个数,operator为and时无should clause命中数永远不会 >0,所以无结果。参见BooleanWeight.java#L390
  3. minimum_should_match为百分比时ES的计算方法
float calc = (shouldClauseCount * percent) * (1 / 100f); // shouldClauseCount为分词后的词根个数,percent为传参的百分比
result = calc < 0 ? shouldClauseCount + (int) calc : (int) calc; // 

Fuzzy query

fuzzy query用法和match基本一致,参数也包含fuzzinessprefix_lengthmax_expansionstranspositions,唯一的不同点是Fuzzy query的查询不分词。使用方式如下:

GET /test-mapping/_search
{
  "query": {
    "fuzzy": {
      "name": {
        "value": "elastic",
        "fuzziness": 0,
        "prefix_length": 0,
        "max_expansions": 50,
        "transpositions": true
      }
    }
  }
}

排序

默认为相关性算分倒序,fuzzy查询过程会改写模糊词查询term权重,编辑距离越大权重越小
query改写过程权重调整细节为:

  1. 强匹配权重为1.0
  2. 非强匹配权重算法为 1.0 - (编辑距离 / min(倒排term长度, 查询term长度))

权重调整源码见FuzzyTermsEnum.java#L232
相关性算法默认为bm25,内建几种可供选择的算法及自定义,参见similaritySimilarity module

一个调试小技巧
可以通过将字段的mapping设置为similarity= boolean来查看模糊查询的扩展词的权重,这样查询结果中的打分就是命中该词扩展词的权重

PUT my_index
{
  "mappings": {
    "properties": {
      "fuzzy_field": {
        "type": "text",
        "similarity": "boolean" 
      }
    }
  }
}

参考文档

  1. 编辑距离算法实现 http://blog.notdot.net/2010/07/Damn-Cool-Algorithms-Levenshtein-Automata
  2. lucene fuzzy查询优化 http://blog.mikemccandless.com/2011/03/lucenes-fuzzyquery-is-100-times-faster.html
  3. ES模糊搜索介绍 fuzzy search
  4. https://elastic-search-in-action.medcl.com/3.site_search/3.3.search_box/fuzzy_query/
  5. https://livebook.manning.com/book/taming-text/chapter-4/3
  6. 用Lucene实现fuzzy search
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351