2. ES之从零开始 - 纠错(Did You Mean)
纠错的另一种说法叫拼写检查(Spell Check),降低由于输入错误导致的不好检索体验。 本文将会分享此类问题的ES解决方案,致力于提升用户的体验。
简单的约定(Simple Convention)
- ES
- Version
- 7.8.0
- Analyzer(index、query)
- Ansj
- Version
-
测试数据
- 2020年中国营收Top500公司的名称
- ES纠错测试数据构建
词纠错(Term Suggester)
Elasticsearch通过直接读取Lucene的词库(Term Dictionary)数据并通过莱文斯坦距离(Levenshtein Distance)算法来提供纠错功能。
-
模拟拼写错误,发送纠错请求
- analyzer, 默认采用指定字段的search analzyer, 为了把搜索词当成一个整体,采用了keywrok analyzer.
GET top500_company_names/_search { "suggest": { "my_suggestion": { "text": "有钱公司", "term": { "field": "name", "analyzer": "keyword" } } } }
-
根据拼写错误的关键词,获取纠错词
- options 纠错词列表,获取词库中满足条件的词(默认的最大编辑距离为2,max_edits: 2)
- score, 编辑距离越短得分越高。
- freq, lucene 词库中出现的频率。
{ "my_suggestion" : [ { "text" : "有钱公司", "offset" : 0, "length" : 4, "options" : [ { "text" : "有限公司", "score" : 0.75, "freq" : 421 } ] } ] }
-
中文不像英文单词那么长,2个字以前都是有出错概率的
- 期望输入
太平
,结果输成了太苹
GET top500_company_names/_search { "suggest": { "my_suggestion": { "text": "太苹", "term": { "field": "name", "analyzer": "keyword" } } } }
- 期望输入
-
Demo 3的纠错请求结果
{ "my_suggestion" : [ { "text" : "太苹", "offset" : 0, "length" : 2, "options" : [ ] } ] }
-
原来是两个参数限制了结果的返回
- 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" } } } }
-
两个字纠错结果
{ "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 } ] } ] }
-
suggest_mode 之always
GET top500_company_names/_search { "suggest": { "my_suggestion": { "text": "太平", "term": { "field": "name", "suggest_mode": "always", "min_word_length": 2, "analyzer": "keyword" } } } }
-
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 } ] } ] }
-
suggest_mode 之popular
GET top500_company_names/_search { "suggest": { "my_suggestion": { "text": "太平", "term": { "field": "name", "suggest_mode": "popular", "min_word_length": 2, "analyzer": "keyword" } } } }
-
suggest_mode 之popular结果
- 太平在词库中 出现过1次
- popular 应该返回频率大于1的纠错词
{ "my_suggestion" : [ { "text" : "太平", "offset" : 0, "length" : 2, "options" : [ { "text" : "太平洋", "score" : 0.5, "freq" : 2 } ] } ] }
-
通过编辑距离控制纠错词列表
- 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 } } } }
- max_edits, 通过编辑距离在词库中获取建议词,该值只能为1和2,设置其它值则会报错,默认距离为2.
-
通过编辑距离控制纠错词列表结果
{ "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 } ] } ] }
-
纠错词的排序与返回个数
- 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" } } } }
-
纠错词的排序与返回个数结果
{ "my_suggestion" : [ { "text" : "太平", "offset" : 0, "length" : 2, "options" : [ { "text" : "太平洋", "score" : 0.5, "freq" : 2 }, { "text" : "太原", "score" : 0.5, "freq" : 1 } ] } ] } }
-
编辑距离与得分
- 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" } } } }
-
编辑距离与得分结果
- 太平洋和太原编辑距离都是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 } ] } ] }
- 太平洋和太原编辑距离都是1
-
通过纠错词最小文档阀值限制返回的个数
- 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 } } } }
-
通过纠错词频率限制控制返回的个数
{ "my_suggestion" : [ { "text" : "福地", "offset" : 0, "length" : 2, "options" : [ { "text" : "福建", "score" : 0.5, "freq" : 4 } ] } ] }
-
第一个字错了,可以纠正么?
GET top500_company_names/_search { "suggest": { "my_suggestion": { "text": "大平洋", "term": { "field": "name", "suggest_mode": "always", "min_word_length": 2, "analyzer": "keyword" } } } }
-
第一个字错了,在上述语法中无法正常纠正
{ "my_suggestion" : [ { "text" : "大平洋", "offset" : 0, "length" : 3, "options" : [ ] } ] }
-
第一个字错了的正确纠正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 } } } }
-
第一个字错了的正确纠正结果
{ "my_suggestion" : [ { "text" : "大平洋", "offset" : 0, "length" : 3, "options" : [ { "text" : "太平洋", "score" : 0.6666666, "freq" : 2 }, { "text" : "平洋", "score" : 0.5, "freq" : 2 } ] } ] }
-
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 } } } }
-
公司名称纠错
GET top500_company_names/_search { "suggest": { "my_suggestion": { "text": "华侨集团有限公司", "term": { "field": "name", "suggest_mode": "always" } } } }
-
公司名称纠错结果
- 通常公司名称都是分词字段,使用默认 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)
多个词组合成一个整体进行拼写错误纠错。
-
短语纠错 查询
- 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 } } } } }
- direct_generator, 短语纠错词生成器,生成器主要是用于多个词组合并输出得分。
-
短语纠错 结果
- 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
本文地址: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"
}
}
}
}
}
其实我想推荐出“鸡蛋糕”的
{
"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"
}
}
}
}
}
其实我想推荐出“鸡蛋糕”的