Easysearch、Elasticsearch 还是 Opensearch,是个问题

​2. ES之从零开始 | 纠错(Did You Mean)

Elasticsearch | 作者 Ricky_Lau | 发布于2020年08月10日 | | 阅读数:5431

2. ES之从零开始 - 纠错(Did You Mean)

纠错的另一种说法叫拼写检查(Spell Check),降低由于输入错误导致的不好检索体验。 本文将会分享此类问题的ES解决方案,致力于提升用户的体验。

简单的约定(Simple Convention)

词纠错(Term Suggester)

Elasticsearch通过直接读取Lucene的词库(Term Dictionary)数据并通过莱文斯坦距离(Levenshtein Distance)算法来提供纠错功能。

  1. 模拟拼写错误,发送纠错请求

    • analyzer, 默认采用指定字段的search analzyer, 为了把搜索词当成一个整体,采用了keywrok analyzer.
    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "有钱公司",
      "term": {
        "field": "name",
        "analyzer": "keyword"
      }
    }
    }
    }
  2. 根据拼写错误的关键词,获取纠错词

    • options 纠错词列表,获取词库中满足条件的词(默认的最大编辑距离为2,max_edits: 2)
    • score, 编辑距离越短得分越高。
    • freq, lucene 词库中出现的频率。
    {
    "my_suggestion" : [
      {
        "text" : "有钱公司",
        "offset" : 0,
        "length" : 4,
        "options" : [
          {
            "text" : "有限公司",
            "score" : 0.75,
            "freq" : 421
          }
        ]
      }
    ]
    }
  3. 中文不像英文单词那么长,2个字以前都是有出错概率的

    • 期望输入太平,结果输成了太苹
    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "太苹",
      "term": {
        "field": "name",
        "analyzer": "keyword"
      }
    }
    }
    }
  4. Demo 3的纠错请求结果

    {
    "my_suggestion" : [
      {
        "text" : "太苹",
        "offset" : 0,
        "length" : 2,
        "options" : [ ]
      }
    ]
    }
    
  5. 原来是两个参数限制了结果的返回

    • min_word_length, 表示只有当关键词长度大于等于该值时,才会进行纠错
    • suggest_mode, 默认只有当搜索词在词库中不存在时,再会进行纠错
      • missing, 搜索词不存在于词库中,执行搜索逻辑(默认)
      • popular, 只返回频率大于原始词的纠错词
      • always, 始终返回任何命中的纠错词
    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "太苹",
      "term": {
        "field": "name",
        "suggest_mode": "always",
        "min_word_length": 2,
        "analyzer": "keyword"
      }
    }
    }
    }
  6. 两个字纠错结果

    {
    "my_suggestion" : [
      {
        "text" : "太苹",
        "offset" : 0,
        "length" : 2,
        "options" : [
          {
            "text" : "太原",
            "score" : 0.5,
            "freq" : 1
          },
          {
            "text" : "太平",
            "score" : 0.5,
            "freq" : 1
          },
          {
            "text" : "太极",
            "score" : 0.5,
            "freq" : 1
          }
        ]
      }
    ]
    }
  7. suggest_mode 之always

    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "太平",
      "term": {
        "field": "name",
        "suggest_mode": "always",
        "min_word_length": 2,
        "analyzer": "keyword"
      }
    }
    }
    }
  8. suggest_mode 之always 结果

    • 返回了所有命中的纠错词
    {
    "my_suggestion" : [
      {
        "text" : "太平",
        "offset" : 0,
        "length" : 2,
        "options" : [
          {
            "text" : "太平洋",
            "score" : 0.5,
            "freq" : 2
          },
          {
            "text" : "太原",
            "score" : 0.5,
            "freq" : 1
          },
          {
            "text" : "太极",
            "score" : 0.5,
            "freq" : 1
          }
        ]
      }
    ]
    }
  9. suggest_mode 之popular

    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "太平",
      "term": {
        "field": "name",
        "suggest_mode": "popular",
        "min_word_length": 2,
        "analyzer": "keyword"
      }
    }
    }
    }
  10. suggest_mode 之popular结果

    • 太平在词库中 出现过1次
    • popular 应该返回频率大于1的纠错词
    {
    "my_suggestion" : [
      {
        "text" : "太平",
        "offset" : 0,
        "length" : 2,
        "options" : [
          {
            "text" : "太平洋",
            "score" : 0.5,
            "freq" : 2
          }
        ]
      }
    ]
    }
  11. 通过编辑距离控制纠错词列表

    • max_edits, 通过编辑距离在词库中获取建议词,该值只能为1和2,设置其它值则会报错,默认距离为2.
      • 编辑距离为1时,召回搜索词与词库中只错一个字符的词。
    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "太平",
      "term": {
        "field": "name",
        "suggest_mode": "always",
        "min_word_length": 2,
        "analyzer": "keyword",
        "max_edits": 1
      }
    }
    }
    }
  12. 通过编辑距离控制纠错词列表结果

    {
    "my_suggestion" : [
      {
        "text" : "太平",
        "offset" : 0,
        "length" : 2,
        "options" : [
          {
            "text" : "太平洋",
            "score" : 0.5,
            "freq" : 2
          },
          {
            "text" : "太原",
            "score" : 0.5,
            "freq" : 1
          },
          {
            "text" : "太极",
            "score" : 0.5,
            "freq" : 1
          }
        ]
      }
    ]
    }
  13. 纠错词的排序与返回个数

    • size, 每个词返回的最大纠错词
    • sort, 返回纠错词的顺序
      • score, 三层排序,先根据编辑距离得分倒序,再根据纠错词频率倒序,最后纠错词的字典序
      • frequency, 三层排序,先根据纠错词频率倒序,再根据编辑距离得分倒序,最后纠错词的字典序
    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "太平",
      "term": {
        "field": "name",
        "suggest_mode": "always",
        "min_word_length": 2,
        "analyzer": "keyword",
        "size": 2,
        "sort": "score"
      }
    }
    }
    }
  14. 纠错词的排序与返回个数结果

    {
    "my_suggestion" : [
      {
        "text" : "太平",
        "offset" : 0,
        "length" : 2,
        "options" : [
          {
            "text" : "太平洋",
            "score" : 0.5,
            "freq" : 2
          },
          {
            "text" : "太原",
            "score" : 0.5,
            "freq" : 1
          }
        ]
      }
    ]
    }
    }
  15. 编辑距离与得分

    • internal, 距离越小得分越高。
    • jaro_winkler JW算法 ,在internal 基本上也考虑了通用的前缀的加权逻辑。
    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "太平",
      "term": {
        "field": "name",
        "suggest_mode": "always",
        "min_word_length": 2,
        "analyzer": "keyword",
        "string_distance": "jaro_winkler"
      }
    }
    }
    }
  16. 编辑距离与得分结果

    • 太平洋和太原编辑距离都是1
      • internal 都是0.5分,相同的编辑距离
      • jaro_winkler 太平洋得分要远高于太原,因为太平洋在词库中有一个通用的前缀太平
    {
    "my_suggestion" : [
      {
        "text" : "太平",
        "offset" : 0,
        "length" : 2,
        "options" : [
          {
            "text" : "太平洋",
            "score" : 0.9111111,
            "freq" : 2
          },
          {
            "text" : "太原",
            "score" : 0.6666667,
            "freq" : 1
          },
          {
            "text" : "太极",
            "score" : 0.6666667,
            "freq" : 1
          }
        ]
      }
    ]
    }
  17. 通过纠错词最小文档阀值限制返回的个数

    • min_doc_freq, 纠错词最小文档阀值,类型可为数值和百分比。
    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "福地",
      "term": {
        "field": "name",
        "suggest_mode": "always",
        "min_word_length": 2,
        "analyzer": "keyword",
        "size": 20,
        "min_doc_freq":3
      }
    }
    }
    }
  18. 通过纠错词频率限制控制返回的个数

    {
    "my_suggestion" : [
      {
        "text" : "福地",
        "offset" : 0,
        "length" : 2,
        "options" : [
          {
            "text" : "福建",
            "score" : 0.5,
            "freq" : 4
          }
        ]
      }
    ]
    }
  19. 第一个字错了,可以纠正么?

    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "大平洋",
      "term": {
        "field": "name",
        "suggest_mode": "always",
        "min_word_length": 2,
        "analyzer": "keyword"
      }
    }
    }
    }
  20. 第一个字错了,在上述语法中无法正常纠正

    {
    "my_suggestion" : [
      {
        "text" : "大平洋",
        "offset" : 0,
        "length" : 3,
        "options" : [ ]
      }
    ]
    }
  21. 第一个字错了的正确纠正DSL

    • prefix_length, 只纠正满足前缀条件的搜索词,默认值为1
    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "大平洋",
      "term": {
        "field": "name",
        "suggest_mode": "always",
        "min_word_length": 2,
        "analyzer": "keyword",
        "prefix_length": 0
      }
    }
    }
    }
  22. 第一个字错了的正确纠正结果

    {
    "my_suggestion" : [
      {
        "text" : "大平洋",
        "offset" : 0,
        "length" : 3,
        "options" : [
          {
            "text" : "太平洋",
            "score" : 0.6666666,
            "freq" : 2
          },
          {
            "text" : "平洋",
            "score" : 0.5,
            "freq" : 2
          }
        ]
      }
    ]
    }
  23. suggest计算是分片级的,而最终的TOP K 可能会出现频率统计精度丢失,可以通过shard_size来修正

    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "大平洋",
      "term": {
        "field": "name",
        "suggest_mode": "always",
        "min_word_length": 2,
        "analyzer": "keyword",
        "prefix_length": 0,
        "shard_size": 10
      }
    }
    }
    }
  24. 公司名称纠错

    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "华侨集团有限公司",
      "term": {
        "field": "name",
        "suggest_mode": "always"
      }
    }
    }
    }
  25. 公司名称纠错结果

    • 通常公司名称都是分词字段,使用默认 search_analyzer或者 keyword analyzer 都无法针对完整公司名纠错。
    {
    "my_suggestion" : [
      {
        "text" : "华侨",
        "offset" : 0,
        "length" : 2,
        "options" : [ ]
      },
      {
        "text" : "集团",
        "offset" : 2,
        "length" : 2,
        "options" : [ ]
      },
      {
        "text" : "有限公司",
        "offset" : 4,
        "length" : 4,
        "options" : [ ]
      }
    ]
    }

短语纠错(Phrase Suggester)

多个词组合成一个整体进行拼写错误纠错。

  1. 短语纠错 查询

    • direct_generator, 短语纠错词生成器,生成器主要是用于多个词组合并输出得分。
      • 生成器,内部的参数与词纠错一致。
    • max_errors, 只允许纠错 max_errors个词
    • highlight, 纠错词高亮
    • collate, 短语纠错不能保证命中文档,通过collate指定查询语句来标识是否命中文档。
      • prune, 返回结果中标识是否命中
    GET top500_company_names/_search
    {
    "suggest": {
    "my_suggestion": {
      "text": "中国石头化工集团有限公司",
      "phrase": {
        "field": "name",
        "size": 3,
        "direct_generator": [
          {
            "field": "name",
            "size": 1,
            "max_edits": 1,
            "min_word_length": 2,
            "suggest_mode": "always"
          }
        ],
        "max_errors": 1,
        "highlight": {
          "pre_tag": "<em>",
          "post_tag": "</em>"
        },
        "collate": {
          "query": {
            "source": {
              "match": {
                "{{field_name}}": "{{suggestion}}"
              }
            }
          },
          "params": {
            "field_name": "name"
          },
          "prune": true
        }
      }
    }
    }
    }
  2. 短语纠错 结果

    • collate_match, 标识是否命中文档
    {
    "my_suggestion" : [
      {
        "text" : "中国石头化工集团有限公司",
        "offset" : 0,
        "length" : 12,
        "options" : [
          {
            "text" : "中国 石油 化工 集团 有限公司",
            "highlighted" : "中国 <em>石油</em> 化工 集团 有限公司",
            "score" : 4.7643356E-5,
            "collate_match" : true
          }
        ]
      }
    ]
    }

小结

  • 在ES中,仅返回当搜索组不在词库中,且词库中存在与搜索词小于2个编辑距离的词作为候选。
  • 短语纠错词列表默认是不检查纠错后的短语能否命中文档的。
  • 在短语纠错中,collate 是必要的,否则无法保障纠错后的词能命中结果。

[尊重社区原创,转载请保留或注明出处]
本文地址:http://elasticsearch.cn/article/14056


1 个评论

你好,可以咨询下推荐的问题吗 我使用了这样的推荐查询,但是无返回结果
{
"size": 0,
"suggest": {
"revise_suggestion": {
"text": "机蛋搞",
"phrase": {
"field": "phrase_title",
"size": 30,
"real_word_error_likelihood": 0.95,
"max_errors": 0.5,
"gram_size": 2,
"direct_generator": [
{
"field": "phrase_title",
"suggest_mode": "always",
"min_word_length": 1
}
],
"collate": {
"query": {
"match": {
"{{field_name}}": "{{suggestion}}"
}
},
"params": {
"field_name": "phrase_title"
},
"prune": "true"
}
}
}
}
}
其实我想推荐出“鸡蛋糕”的

要回复文章请先登录注册