• ElasticSearch之搜索词提示Sug


    一.sug概述

    对提供的搜索词(suggest text)返回相关的提示词

    二.四种suggester

    通用option字段及含义

    option

    含义

    text搜索词,因为一个search可以存在多个suggester,所以可以设置全局text,也可对每个suggester单独设置,单独设置时以单独为准
    field返回提示词的字段
    analyzer搜索词text分词器,默认和field的分词器相同
    size每个text返回size个提示词
    sort

    对于text的每个分出来的词(suggest text term),返回的提示词如何排序

    • score 先按相似度,相似度相同再按词频
    • frequency 先词频再相似度
    suggest_mode

    用来控制①返回什么提示词②对哪些搜索词的分词返回提示词

    • missing 针对②,只对索引中没有的 搜索词分词 返回提示词 【默认】
    • popular 针对①,只返回比搜索词分词在文档里出现频率高的提示词
    • always ①②,对所有搜索词分词返回所有匹配提示词

    1.Term suggester

    term sug提供提示词的依据是和搜索词的编辑距离,直观理解就是根据相似程度。ES会先对搜索词进行分词处理(分词器可以手动设定,默认和要搜索字段的分词器相同),然后对每一个分出来的词返回相似词。

    term sug的option字段及含义

    option

    含义

    lowercase_terms把搜索词分词小写
    max_edits提示词与搜索词分词的最大编辑距离,取值范围[1,2],默认2
    prefix_length至少匹配的前缀长度(TODO:汉字长度咋算),默认1
    min_word_length返回提示词的最小长度,默认4
    shard_size

    每个shard返回到coordinate node 的提示词数,增大时提高返回精准度,具体参考

    terms分组计数精确度问题中关于shard_size 和 size的说明。

    max_inspections乘shard_size的因子,加大时在各分片上监测更多的拼写矫正结果,性能换精度,默认5。估计也用不到
    min_doc_freq大于1的整数或百分比值,表示提示词最少在几篇,或百分之多少的文档中出现。默认为0,不生效。
    max_term_freq大于1的整数或百分比值,表示提示词最多出现在几篇或百分之多少的文档里。设置此值的背景是需要拼写检查的场景,对于某些频率特别高的词,打搜索词时出错概率低,不返回提示词。
    string_distance五种计算编辑距离的算法......

    Term suggester示例

    1、数据准备

    1. curl -u $user:passoword -H "Content-Type:application/json" -XPUT "http://$ip:$port/blogs" -d '{"settings":{"index":{"number_of_shards":2,"number_of_replicas":1}}}'
    2. curl -u $user:passoword -H "Content-Type:application/json" -XPUT "http://$ip:$port/blogs/_mapping/_doc?pretty" -d '{"properties":{"body":{"type":"text"}}}'
    3. curl -u $user:passoword -H "Content-Type:application/json" -XPOST "http://$ip:$port/blogs/_doc/_bulk?pretty" --data-binary @test.data
    4. { "index" : { "_index" : "blogs"} }
    5. { "body":"Lucene is cool"}
    6. { "index" : { "_index" : "blogs"} }
    7. { "body": "Elasticsearch builds on top of lucene"}
    8. { "index" : { "_index" : "blogs"} }
    9. { "body": "Elasticsearch rocks"}
    10. { "index" : { "_index" : "blogs"} }
    11. { "body": "Elastic is the company behind ELK stack"}
    12. { "index" : { "_index" : "blogs"} }
    13. { "body": "elk rocks"}
    14. { "index" : { "_index" : "blogs"} }
    15. { "body": "elasticsearch is rock solid"}

    suggest就是一种特殊类型的搜索,DSL内部的"text"指的是api调用方提供的文本,也就是通常用户界面上用户输入的内容。这里的lucne是错误的拼写,模拟用户输入错误。 "term"表示这是一个term suggester。 "field"指定suggester针对的字段,另外有一个可选的"suggest_mode"。 范例里的"missing"实际上就是缺省值

    1. curl -u $user:passoword -H "Content-Type:application/json" -XPOST "http://$ip:$port/blogs/_doc/_search?pretty" -d '{
    2. "suggest":{
    3. "my-suggestion":{
    4. "text":"lucne rocks",
    5. "term":{
    6. "suggest_mode":"missing",
    7. "field":"body"
    8. }
    9. }
    10. }
    11. }'
    12. 结果:
    13. {
    14. "took":79,
    15. "timed_out":false,
    16. "_shards":{
    17. "total":2,
    18. "successful":2,
    19. "skipped":0,
    20. "failed":0
    21. },
    22. "hits":{
    23. "total":0,
    24. "max_score":0,
    25. "hits":[
    26. ]
    27. },
    28. "suggest":{
    29. "my-suggestion":[
    30. {
    31. "text":"lucne",
    32. "offset":0,
    33. "length":5,
    34. "options":[
    35. {
    36. "text":"lucene",
    37. "score":0.8,
    38. "freq":2
    39. }
    40. ]
    41. },
    42. {
    43. "text":"rocks",
    44. "offset":6,
    45. "length":5,
    46. "options":[
    47. {
    48. "text":"rock",
    49. "score":0.75,
    50. "freq":1
    51. }
    52. ]
    53. }
    54. ]
    55. }
    56. }
    57. 在返回结果里"suggest" -> "my-suggestion"部分包含了一个数组,每个数组项对应从输入文本分解出来的token(存放在"text"这个key里)以及为该token提供的建议词项(存放在options数组里)。
    58. 示例里返回了"lucne""rock"2个词的建议项(options)
    59. #suggest_mode换成popular再试试?

    两个term的相似性是如何判断的? ES使用了一种叫做Levenstein edit distance的算法,其核心思想就是一个词改动多少个字符就可以和另外一个词一致。

    2.Phrase suggester

    Phrase suggester在Term suggester的基础上,会考量多个term之间的关系,比如是否同时出现在索引的原文里,相邻程度,以及词频等等。

    1. curl -u $user:$password -H "Content-Type:application/json" -XPOST "http://$ip:$port/blogs/_doc/_search?pretty" -d '{
    2. "suggest":{
    3. "my-suggestion":{
    4. "text":"lucne and elasticsear rock",
    5. "phrase":{
    6. "field":"body",
    7. "highlight":{
    8. "pre_tag":"<em>",
    9. "post_tag":"</em>"
    10. }
    11. }
    12. }
    13. }
    14. }'
    15. # 结果
    16. {
    17. "took":30,
    18. "timed_out":false,
    19. "_shards":{
    20. "total":2,
    21. "successful":2,
    22. "skipped":0,
    23. "failed":0
    24. },
    25. "hits":{
    26. "total":0,
    27. "max_score":0,
    28. "hits":[
    29. ]
    30. },
    31. "suggest":{
    32. "my-suggestion":[
    33. {
    34. "text":"lucne and elasticsear rock",
    35. "offset":0,
    36. "length":26,
    37. "options":[
    38. {
    39. "text":"lucene and elasticsearch rock",
    40. "highlighted":"<em>lucene</em> and <em>elasticsearch</em> rock",
    41. "score":0.012031202
    42. },
    43. {
    44. "text":"lucne and elasticsearch rocks",
    45. "highlighted":"lucne and <em>elasticsearch rocks</em>",
    46. "score":0.009254823
    47. },
    48. {
    49. "text":"lucne and elasticsearch rock",
    50. "highlighted":"lucne and <em>elasticsearch</em> rock",
    51. "score":0.008044719
    52. },
    53. {
    54. "text":"lucene and elasticsear rock",
    55. "highlighted":"<em>lucene</em> and elasticsear rock",
    56. "score":0.007966585
    57. },
    58. {
    59. "text":"lucne and elasticsear rocks",
    60. "highlighted":"lucne and elasticsear <em>rocks</em>",
    61. "score":0.0073081385
    62. }
    63. ]
    64. }
    65. ]
    66. }
    67. }

    options直接返回一个phrase列表,由于加了highlight选项,被替换的term会被高亮。因为lucene和elasticsearch曾经在同一条原文里出现过,同时替换2个term的可信度更高,所以打分较高,排在第一位返回。Phrase suggester有相当多的参数用于控制匹配的模糊程度,需要根据实际应用情况去挑选和调试。

    3.Completion suggester

    主要针对的应用场景就是"Auto Completion"。此场景下用户每输入一个字符的时候,就需要即时发送一次查询请求到后端查找匹配项,在用户输入速度较高的情况下对后端响应速度要求比较苛刻。因此实现上它和前面两个Suggester采用了不同的数据结构,索引并非通过倒排来完成,而是将analyze过的数据编码成FST和索引一起存放。对于一个open状态的索引,FST会被ES整个装载到内存里的,进行前缀查找速度极快。但是FST只能用于前缀查找,这也是Completion Suggester的局限所在。

    1. #需要重新建立索引数据
    2. PUT /blogs_completion/
    3. {
    4. "mappings": {
    5. "tech": {
    6. "properties": {
    7. "body": {
    8. "type": "completion"
    9. }
    10. }
    11. }
    12. }
    13. }
    14. POST _bulk/?refresh=true
    15. { "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
    16. { "body": "Lucene is cool"}
    17. { "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
    18. { "body": "Elasticsearch builds on top of lucene"}
    19. { "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
    20. { "body": "Elasticsearch rocks"}
    21. { "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
    22. { "body": "Elastic is the company behind ELK stack"}
    23. { "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
    24. { "body": "the elk stack rocks"}
    25. { "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
    26. { "body": "elasticsearch is rock solid"}
    27. ##查找
    28. POST blogs_completion/_search?pretty
    29. { "size": 0,
    30. "suggest": {
    31. "blog-suggest": {
    32. "prefix": "elastic i",
    33. "completion": {
    34. "field": "body"
    35. }
    36. }
    37. }
    38. }
    39. #结果
    40. {
    41. "took": 33,
    42. "timed_out": false,
    43. "_shards": {
    44. "total": 5,
    45. "successful": 5,
    46. "skipped": 0,
    47. "failed": 0
    48. },
    49. "hits": {
    50. "total": 0,
    51. "max_score": 0,
    52. "hits": []
    53. },
    54. "suggest": {
    55. "blog-suggest": [
    56. {
    57. "text": "elastic i",
    58. "offset": 0,
    59. "length": 9,
    60. "options": [
    61. {
    62. "text": "Elastic is the company behind ELK stack",
    63. "_index": "blogs_completion",
    64. "_type": "tech",
    65. "_id": "AWh05W0vrgjL5DnrjuWz",
    66. "_score": 1,
    67. "_source": {
    68. "body": "Elastic is the company behind ELK stack"
    69. }
    70. }
    71. ]
    72. }
    73. ]
    74. }
    75. }

    值得注意的一点是Completion Suggester在索引原始数据的时候也要经过analyze阶段,取决于选用的analyzer不同,某些词可能会被转换,某些词可能被去除,这些会影响FST编码结果,也会影响查找匹配的效果。

    比如我们删除上面的索引,重新设置索引的mapping,将analyzer更改为"english"

    1. PUT /blogs_completion/
    2. {
    3. "mappings": {
    4. "tech": {
    5. "properties": {
    6. "body": {
    7. "type": "completion",
    8. "analyzer": "english"
    9. }
    10. }
    11. }
    12. }
    13. }
    14. #bulk api索引同样的数据后,执行下面的查询:
    15. POST blogs_completion/_search?pretty
    16. { "size": 0,
    17. "suggest": {
    18. "blog-suggest": {
    19. "prefix": "elastic i",
    20. "completion": {
    21. "field": "body"
    22. }
    23. }
    24. }
    25. }
    26. #结果
    27. {
    28. "took": 7,
    29. "timed_out": false,
    30. "_shards": {
    31. "total": 5,
    32. "successful": 5,
    33. "skipped": 0,
    34. "failed": 0
    35. },
    36. "hits": {
    37. "total": 0,
    38. "max_score": 0,
    39. "hits": []
    40. },
    41. "suggest": {
    42. "blog-suggest": [
    43. {
    44. "text": "elastic i",
    45. "offset": 0,
    46. "length": 9,
    47. "options": []
    48. }
    49. ]
    50. }
    51. }
    52. #居然没有匹配结果了,多么费解!原来我们用的english analyzer会剥离掉stop word,而is就是其中一个,被剥离掉了!
    53. #用analyze api测试一下:
    54. POST _analyze?analyzer=english
    55. {
    56. "text": "elasticsearch is rock solid"
    57. }
    58. #结果
    59. {
    60. "tokens": [
    61. {
    62. "token": "elasticsearch",
    63. "start_offset": 0,
    64. "end_offset": 13,
    65. "type": "<ALPHANUM>",
    66. "position": 0
    67. },
    68. {
    69. "token": "rock",
    70. "start_offset": 17,
    71. "end_offset": 21,
    72. "type": "<ALPHANUM>",
    73. "position": 2
    74. },
    75. {
    76. "token": "solid",
    77. "start_offset": 22,
    78. "end_offset": 27,
    79. "type": "<ALPHANUM>",
    80. "position": 3
    81. }
    82. ]
    83. }

    FST只编码了这3个token,并且默认的还会记录他们在文档中的位置和分隔符。 用户输入"elastic i"进行查找的时候,输入被分解成"elastic"和"i",FST没有编码这个“i” , 匹配失败。


    好吧,如果你现在还足够清醒的话,试一下搜索"elastic is",会发现又有结果,why?  因为这次输入的text经过english analyzer的时候is也被剥离了,只需在FST里查询"elastic"这个前缀,自然就可以匹配到了。

    其他能影响completion suggester结果的,还有诸如"preserve_separators","preserve_position_increments"等等mapping参数来控制匹配的模糊程度。以及搜索时可以选用Fuzzy Queries,使得上面例子里的"elastic i"在使用english analyzer的情况下依然可以匹配到结果。

    因此用好Completion Sugester并不是一件容易的事,实际应用开发过程中,需要根据数据特性和业务需要,灵活搭配analyzer和mapping参数,反复调试才可能获得理想的补全效果。

    回到篇首Google搜索框的补全/纠错功能,如果用ES怎么实现呢?我能想到的一个的实现方式:

    1. 在用户刚开始输入的过程中,使用Completion Suggester进行关键词前缀匹配,刚开始匹配项会比较多,随着用户输入字符增多,匹配项越来越少。如果用户输入比较精准,可能Completion Suggester的结果已经够好,用户已经可以看到理想的备选项了。 
    2. 如果Completion Suggester已经到了零匹配,那么可以猜测是否用户有输入错误,这时候可以尝试一下Phrase Suggester。
    3. 如果Phrase Suggester没有找到任何option,开始尝试term Suggester。

    精准程度上(Precision)看: Completion >  Phrase > term, 而召回率上(Recall)则反之。从性能上看,Completion Suggester是最快的,如果能满足业务需求,只用Completion Suggester做前缀匹配是最理想的。 Phrase和Term由于是做倒排索引的搜索,相比较而言性能应该要低不少,应尽量控制suggester用到的索引的数据量,最理想的状况是经过一定时间预热后,索引可以全量map到内存。

    Suggesters | Elasticsearch Guide [8.3] | Elastic

    4.Context Suggester

    https://www.elastic.co/guide/en/elasticsearch/reference/5.2/suggester-context.html#

  • 相关阅读:
    深度讲解TS:这样学TS,迟早进大厂【11】:类型断言
    Github 2024-06-15Rust开源项目日报Top10
    web网页设计期末课程大作业:美食餐饮文化主题网站设计——HTML+CSS+JavaScript美食餐厅网站设计与实现 11页面
    TiDB 6.0 新特性
    SVN安装教程
    如何看待unity新的收费模式
    设计模式3-分类
    Pandas知识点-详解聚合函数agg
    TLR4-IN-C34-C2-COO,一种结合了TLR4抑制剂TLR4-IN-C34的连接器
    《嵌入式 – GD32开发实战指南》第13章 DAC
  • 原文地址:https://blog.csdn.net/spirit_8023/article/details/125528333