1. 借鉴
极客时间 阮一鸣老师的Elasticsearch核心技术与实战
Elasticsearch 5.x Suggester详解
店名大全 -_-||
官方文档 search-suggesters
详解编辑距离(Edit Distance)及其代码实现
Edit Distance 算法实现及其设计原理
leetcode edit-distance
elasticsearch suggest实现搜索词自动补全
Elasticsearch之建议器suggester
Elasticsearch搜索Suggest功能优化
【Elasticsearch】7.2 自动补全、纠错、拼音搜索
elasticsearch使用more_like_this实现基于内容的推荐
Field datatypes(2)
elasticsearch补全功能之只补全筛选后的部分数据context suggester
Elasticsearch学习笔记5: suggest实现搜索补全
Phrase Suggester
lucene底层数据结构——FST,针对field使用列存储,delta encode压缩doc ids数组,LZ4压缩算法
关于Lucene的词典FST深入剖析
FST
Completion Suggester.md
2. 开始
Suggester,顾名思义,即用户输入后,进行自动补全或者纠错,并给出相应的结果。
其原理是将输入分解为token,然后再索引中查找相似的关键词(term)并返回
在es里面有四种Suggester:
- Term Suggester
- Phrase Suggester
- Completion Suggester
- Context Suggester
我们逐一来看下
数据准备:使用到的数据请看这里<Elasticsearch 7.x 深入 数据准备>
Term Suggester
The term suggester suggests terms based on edit distance. The provided suggest text
is analyzed before terms are suggested. The suggested terms are provided per
analyzed suggest text token. The term suggester doesn’t take the query into account
that is part of request.
term suggester基于编辑距离算法实现。在提供建议之前,对输入的文本进行分析。
我们在索引中有一个“三生石工艺坊批发店”这个店铺,我们基于这个来查询。
- 我们首先来看下它的分词情况:[这里为啥是ik_smart而不是ik_max_word呢?因为我指定的search_analyzer是ik_smart,在suggest查询是,默认使用search_analyzer分词匹配,当然也可以自己指定,这个在官方文档上有描述,下面我会将它的选项列举出来,稍安勿躁]
GET /store_suggest/_analyze
{
"analyzer": "ik_smart",
"text": ["三生石工艺坊批发店"]
}
- 嗯,我们来看下它的token[为了避免拉的过长,我只保留了token字段]
{
"tokens" : [
{ "token" : "三生石", },
{ "token" : "工艺", },
{ "token" : "坊", },
{ "token" : "批发店", }
]
}
- 可以看到上面我们分词的结果中有三生石,那接下我故意输错为“三声石”,让我们来构建一个term suggester,看下它会提示我们什么
GET /store_suggest/_search
{
"suggest": {
"term-suggestion": {
"text": "三声石", // 我们输入的内容
"term": {
"field": "name", // 指定哪个字段
"suggest_mode": "missing", // 参数1
"min_word_length": 2 // 参数2
}
}
}
}
- 我们来看下结果
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"term-suggestion" : [
{
"text" : "三声",
"offset" : 0,
"length" : 2,
"options" : [
{
"text" : "三生",
"score" : 0.5,
"freq" : 1
}
]
},
{
"text" : "石",
"offset" : 2,
"length" : 1,
"options" : [ ]
}
]
}
}
- score是通过“编辑距离算法”实现的,可以看到我借鉴的文章,关于编辑距离的,讲的挺好。
- 可以看到“三声”提示我们应该为“三生”,我在查询时候使用了几个参数,我们来解释一下
这些参数是四种suggester查询的公共参数
字段名称 | 解释 | 默认值 |
---|---|---|
text | 输入内容 | - |
field | 作用的字段 | - |
analyzer | 使用的分词器 | search analyzer |
size | 使用的分词器 | 每个建议文本返回的最大提示数量 |
sort | 如何排序: score:首先按分数排序,然后按文档频率排序,最后按关键词本身排序。 frequency:首先按文档频率排序,然后是相似度评分,最后是关键词本身。 |
- |
suggest_mode | 模式: missing:如果索引中已经存在,就不提供建议 popular:推荐出现频率更高的词 always:无论是否存在,都提供建议 |
- |
- 下面这些是Term Suggester独有的参数
字段名称 | 解释 | 默认值 |
---|---|---|
max_edits | 编辑距离:只能是1和2之间的值。任何其他值都会导致抛出错误的请求。 | -2 |
prefix_length | 必须匹配的最小前缀字符的数量。 | 1 |
min_word_length | 建议的文本术语必须具有的最小长度才能被包括在内。 | 4 |
string_distance | 要使用哪个字符串距离实现来比较建议的term的相似性 internal:默认值,基于damerau_levenshtein,对于比较索引内的词的字符串距离进行了高度优化 damerau_levenshtein:基于Damerau-Levenshtein算法 levenshtein:基于Levenshtein edit distance算法 jaro_winkler:基于Jaro-Winkler算法 ngram: 基于character n-grams算法 |
internal |
shard_size | 设置要从每个分片中检索的建议的最大数量。在reduce阶段,只根据size选项返回前N个建议。默认size选项,将这个值设置为大于size的值,这有助于获得更准确的文档频率,以牺牲性能为代价获得拼写更准确。由于term是在分词时产生的,因此分片级的文档中拼写正确的频率可能并不精确。增加这值将使这些文件频率更精确。 | - |
max_inspections | 一个因子,用于与shards_size相乘,以便检查shard级别上的更多候选拼写更正。以牺牲性能为代价来提高精度。 | 5 |
min_doc_freq | 建议中出现的文档数量的最小阈值。这可以指定为绝对数量,也可以指定为文档数量的相对百分比。这可以通过只建议高词频的term来提高质量。默认为0f,未启用。如果指定的值大于1,则该数字不能是小数。这个选项使用分片级文档频率。 | 0f |
max_term_freq | 建议文本标记可以存在的文档数量的最大阈值,以便将其包括在内。可以是表示文档频率的相对百分比数(例如0.4)或绝对数。如果指定的值大于1,则不能指定分数。这可以用来排除高频术语(通常拼写正确)的拼写检查。这也提高了拼写检查的性能。这个选项使用碎片级文档频率。 | 0.01 |
Phrase Suggester
The phrase suggester adds additional logic on top of the term suggester to select entire
corrected phrases instead of individual tokens weighted based on ngram-language
models. In practice this suggester will be able to make better decisions about which
tokens to pick based on co-occurrence and frequencies.
phrase suggester 在 term suggester 之上添加额外的逻辑以选择整个经校正的短语,而不是基于 ngram-language 模型加权的单个 token 。
- 我们构建一个看下效果
GET /store_suggest/_search
{
"suggest": {
"phrase-suggestion": {
"text": "三生时工艺坊批发店",
"phrase": {
"field": "name",
"max_errors": 4,
"confidence": 0,
"direct_generator": [{
"field": "name",
"suggest_mode": "missing"
}],
"highlight": {
"pre_tag": "<em style='color=\"red\">",
"post_tag": "</em>"
}
}
}
}
}
- 结果如下:看样子结果不是特别如意,是Phrase Suggester对于中文的支持不是特别美好,或者是分词器的问题,但是我看了几篇博客,说是Completion Suggester可以,那我们等会用Completion试试。另外全网都是“lucne and elasticsear rock”这种的,我自己也试了一下,英文是可以的,我将测试的查询语句和数据放到一块了,可以在数据准备部分下载。
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"phrase-suggestion" : [
{
"text" : "三生时工艺坊批发店",
"offset" : 0,
"length" : 9,
"options" : [
{
"text" : "三生 时 工艺 坊 批发店",
"highlighted" : "三生 时 工艺 坊 批发店",
"score" : 3.0157945E-4
}
]
}
]
}
}
- 好吧不能用归不能用,它的参数还是要列举一下的,话说有很多参数我都没有用过。。。
字段名称 | 解释 | 默认值 |
---|---|---|
field | 作用的字段 | - |
gram_size | - | |
real_word_error_likelihood | 一个词被拼错的可能性,即使这个词存在于字典中。默认值是0.95,这意味着有5%的单词是拼写错误的。 | 0.95 |
confidence | 限制返回结果 | 1 |
max_errors | 最多可以拼接错误的term数量 | - |
separator | 用于在双字母字段中分隔term的分隔符。如果未设置,空白字符将用作分隔符。 | 空字符 |
size | 为每个查询项生成的候选项数 | 5 |
analyzer | - | |
shard_size | 设置要从每个分片检索的建议术语的最大数量。在reduce阶段,只根据size选项返回前N个建议。默认为5。 | 5 |
text | 设置要进行建议的文本 | - |
highlight | 高亮,pre_tag和post_tag | - |
collate | 根据指定的查询检查每个建议,以删除索引中不存在匹配文档的建议。 | - |
direct_generator | 直接生成器 | - |
- 以下为direct_generator的参数
字段名称 | 解释 | 默认值 |
---|---|---|
field | 从中获取候选 suggestions 的字段。 这是必需的选项,需要设置全局或每个 suggestion 。 | - |
size | 每个 suggestion 文本标记返回的最大更正值。 | - |
max_edits | 最大编辑距离候选 suggestions 可以具有,以便被认为是 suggestion 。 只能是介于1和2之间的值。任何其他值都会导致抛出错误的请求错误。 | 2 |
prefix_length | 必须匹配的最小前缀字符的数量是候选 suggestions 。 增加此数字可提高拼写检查性能。 通常拼写错误不会出现在术语的开头。 | 1 |
min_word_length | suggest 文本术语必须包含的最小长度。 | 4 |
max_inspections | 用于乘以 shards_size 以便在碎片级别上检查更多候选拼写校正的因子。 可以以性能为代价提高精度。 | 5 |
min_doc_freq | suggestion 应该出现的文档数量的最小阈值。这可以指定为绝对数字或文档数量的相对百分比。 这可以通过仅提示高频项来提高质量。 如果指定的值大于1,则该数字不能为小数。 分片级文档频率用于此选项。 | 默认值为0f,未启用。 |
max_term_freq | suggestion 文本标记可以存在的文档数量中的最大阈值,以便包括。 可以是表示文档频率的相对百分比数字(例如0.4)或绝对数字。 如果指定的值大于1,则不能指定小数。 这可以用于排除高频术语的拼写检查。 高频项通常拼写正确,这也提高了拼写检查的性能。 分片级文档频率用于此选项。 | 0.01f |
pre_filter | 应用于传递到该候选生成器的每个令牌的过滤器(分析器)。 在生成候选项之前,此过滤器应用于原始令牌。 | - |
post_filter | 在它们被传递给实际短语记分器之前应用于每个生成的令牌的过滤器(分析器)。 | - |
Completion Suggester
The completion suggester provides auto-complete/search-as-you-type functionality. This is
a navigational feature to guide users to relevant results as they are typing, improving
search precision. It is not meant for spell correction or did-you-mean functionality like the
term or phrase suggesters.
Ideally, auto-complete functionality should be as fast as a user types to provide instant
feedback relevant to what a user has already typed in. Hence, completion suggester is
optimized for speed. The suggester uses data structures that enable fast lookups, but are
costly to build and are stored in-memory.
大意是:只能基于前缀查询
,速度很快,性能要求高
需求场景是:每输入一个字符,即时发送一个请求查询匹配项
数据结构:并非是倒排索引实现的,而是将分词的数据编码成FST和索引一起存放;FST会被加载进内存,速度很快
使用Completion是有限制的,即在指定映射的时候,需要指定类型为completion,我们来个栗子,mapping信息可查看数据准备。
GET /store_suggest_v2/_search
{
"suggest": {
"completion-suggestion": {
"prefix": "三生",
"completion": {
"field": "name_suggest"
}
}
}
}
- 查看结果如下:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"completion-suggestion" : [
{
"text" : "三生",
"offset" : 0,
"length" : 2,
"options" : [
{
"text" : "三生石工艺坊批发店",
"_index" : "store_suggest_v2",
"_type" : "_doc",
"_id" : "jSJHQXIBmZ523HCO5Ixv",
"_score" : 1.0,
"_source" : {
"name" : "三生石工艺坊批发店",
"name_suggest" : "三生石工艺坊批发店"
}
}
]
}
]
}
}
- 那我们不使用前缀呢?根据我们前面的分析,我们知道“工艺”也被分为一个token,那我们搜索一下“工艺”,看看是否有结果
GET /store_suggest_v2/_search
{
"suggest": {
"completion-suggestion": {
"prefix": "工艺",
"completion": {
"field": "name_suggest"
}
}
}
}
- 结果是并没有,由此可见,Completion Suggester是真的只支持前缀搜索
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"completion-suggestion" : [
{
"text" : "工艺",
"offset" : 0,
"length" : 2,
"options" : [ ]
}
]
}
}
fuzzy
当前前缀归前缀,你后缀拼错了它也可以支持的,我们来看下completion的模糊属性fuzzy
GET /store_suggest_v2/_search
{
"suggest": {
"completion-suggestion": {
"prefix": "三生是",
"completion": {
"field": "name_suggest",
"fuzzy" : {
"fuzziness" : 2,
"unicode_aware": true
}
}
}
}
}
- 查询结果如下,可以看到“三生石”我写错了,写成了"三生是",但是通过我们的配置,是可以筛选出来的
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"completion-suggestion" : [
{
"text" : "三生是",
"offset" : 0,
"length" : 3,
"options" : [
{
"text" : "三生石工艺坊批发店",
"_index" : "store_suggest_v2",
"_type" : "_doc",
"_id" : "jSJHQXIBmZ523HCO5Ixv",
"_score" : 1.0,
"_source" : {
"name" : "三生石工艺坊批发店",
"name_suggest" : "三生石工艺坊批发店"
}
}
]
}
]
}
}
我们看下fuzzy可以配置的参数:
字段名称 | 解释 | 默认值 |
---|---|---|
fuzziness | 模糊因子:在查询文本或关键字字段时,模糊性被解释为Levenshtein编辑距离——需要对一个字符串进行的字符更改数,以使其与另一个字符串相同。 | AUTO |
transpositions | 如果设置为true,换位被记为一次更改而不是两次 | true |
min_length | 模糊建议器返回前输入的最小长度 | 3 |
prefix_length | 输入的最小长度(未针对模糊替代项进行检查) | 1 |
unicode_aware | 如果设置为true,则所有的度量都以unicode代码点而不是字节为单位。这比原始字节稍慢,因此默认情况下设置为false。 | false |
regex
其实completion也是支持正则查询的,但是我查了很多资料,都是英文正则的例子,我自己测试中文没有成功,我感觉也应该在这则这里加一个unicode_aware属性,它应该也是按照字节来算的,我将测试的例子,写在下面,希望有跟多见解的看官能给我提示。// TODO
GET /store_suggest_v2/_search
{
"suggest": {
"completion-suggestion": {
"regex": "三生.*?",
"completion": {
"field": "name_suggest"
}
}
}
}
Context Suggester
The completion suggester considers all documents in the index, but it is often desirable to
serve suggestions filtered and/or boosted by some criteria.
大意是:可以通过筛选提供建议。
context 支持两种类型,分别是category(任意字符串),geo(地理位置信息)。数据信息可查看数据准备
PUT /store_suggest_v3
{
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word"
},
"name_suggest": {
"type": "completion",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"contexts": [{
"type": "category",
"name": "scene"
}]
}
}
}
}
我们查询“意美”,但是有两个“意美”开头的店铺,但是这两个的性质是不一样的,一个是首饰店铺,一个是家具店铺,我们可以根据用户的场景来选择
# 目前场景是饰品,我们使用的上下文属性值是ornament,如果场景是家具,那我们就使用furniture
GET /store_suggest_v3/_search
{
"suggest": {
"name_suggest": {
"prefix": "意美",
"completion": {
"field": "name_suggest",
"contexts": {
"scene": "ornament"
}
}
}
}
}
- 我们的场景以饰品为例,则结果如下:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"name_suggest" : [
{
"text" : "意美",
"offset" : 0,
"length" : 2,
"options" : [
{
"text" : "意美家居饰品",
"_index" : "store_suggest_v3",
"_type" : "_doc",
"_id" : "yAxiS3IBSI7tvzqGolz1",
"_score" : 1.0,
"_source" : {
"name" : "意美家居饰品",
"name_suggest" : {
"input" : [
"意美家居饰品"
],
"contexts" : {
"scene" : "ornament"
}
}
},
"contexts" : {
"scene" : [
"ornament"
]
}
}
]
}
]
}
}
网友评论