Elasticsearch:词干、Shingles 和同义词过滤器

1,121 阅读6分钟

分词器生成的分词可能需要进一步丰富或增强,例如小写(或大写)标记、提供同义词、开发词干词、删除撇号或标点符号等。 分词过滤器对分词进行处理以执行此类转换。

Elasticsearch 提供了将近 50 个分词过滤器,正如你可以想象的那样,在这里讨论所有这些过滤器是不可行的。 我已经设法抓住了一些,但请随时参考官方文档以了解其余的分词过滤器。 我们可以通过简单地附加到分词器并在 _analyze API 调用中使用它来测分词过滤器,如以下清单所示:



1.  GET _analyze
2.  {
3.    "tokenizer" : "standard",
4.    "filter" : ["uppercase","reverse"],
5.    "text" : "Elastic Stack"
6.  }


上面的命令输出的结果为:



1.  {
2.    "tokens": [
3.      {
4.        "token": "CITSALE",
5.        "start_offset": 0,
6.        "end_offset": 7,
7.        "type": "<ALPHANUM>",
8.        "position": 0
9.      },
10.      {
11.        "token": "KCATS",
12.        "start_offset": 8,
13.        "end_offset": 13,
14.        "type": "<ALPHANUM>",
15.        "position": 1
16.      }
17.    ]
18.  }


过滤器接受一个分词过滤器数组; 例如,我们在此示例中提供了 uppercase 和 reverse 过滤器)。 输出将是 “CITSALE” 及 “KCATS”(每个词进行了大写及反转)。

你还可以将过滤器附加到自定义分析器,如以下清单所示。 然后因为我们知道如何附加分词过滤器,所以我们将看几个例子。



1.  PUT index_with_filters
2.  {
3.    "settings": {
4.      "analysis": {
5.        "analyzer": {
6.          "token_filter_analyzer": {
7.            "tokenizer": "standard",
8.            "filter": [ "uppercase","reverse"]
9.          }
10.        }
11.      }
12.    }
13.  }


我们可以一如下的方式来测试上面的定制分析器:



1.  POST index_with_filters/_analyze
2.  {
3.    "text": "Elastic Stack",
4.    "analyzer": "token_filter_analyzer"
5.  }


词干过滤器(stemmer filter)

词干提取是一种将单词缩减为词根的机制(例如,单词 “bark” 是 “barking” 的词根)。Elasticsearch 提供了一个开箱即用的词干提取器,可以将单词缩减为词根形式。 以下清单演示了词干分析器的用法示例。



1.  POST _analyze
2.  {
3.    "tokenizer": "standard",
4.    "filter": ["stemmer"],
5.    "text": "I like watching TV"
6.  }


上面命令输出的结果为:



1.  {
2.    "tokens": [
3.      {
4.        "token": "I",
5.        "start_offset": 0,
6.        "end_offset": 1,
7.        "type": "<ALPHANUM>",
8.        "position": 0
9.      },
10.      {
11.        "token": "like",
12.        "start_offset": 2,
13.        "end_offset": 6,
14.        "type": "<ALPHANUM>",
15.        "position": 1
16.      },
17.      {
18.        "token": "watch",
19.        "start_offset": 7,
20.        "end_offset": 15,
21.        "type": "<ALPHANUM>",
22.        "position": 2
23.      },
24.      {
25.        "token": "TV",
26.        "start_offset": 16,
27.        "end_offset": 18,
28.        "type": "<ALPHANUM>",
29.        "position": 3
30.      }
31.    ]
32.  }


从上面的输出中,我们可以看出来 watching 的分词变为 watch,也就是说 watch 是 watching 的词干。

Shingle filter

Shingles 是在分词级别生成的单词 n-gram(不同于在字母级别发出 n-gram 的 n-gram 和 edge_ngram)。 例如,文本 “james bond” 发出 “james” 和 “james bond”。 以下代码显示了 shingle 过滤器的示例用法:



1.  POST _analyze
2.  {
3.    "tokenizer": "standard",
4.    "filter": ["shingle"],
5.    "text": "java python go"
6.  }


上面的输出为:



1.  {
2.    "tokens": [
3.      {
4.        "token": "java",
5.        "start_offset": 0,
6.        "end_offset": 4,
7.        "type": "<ALPHANUM>",
8.        "position": 0
9.      },
10.      {
11.        "token": "java python",
12.        "start_offset": 0,
13.        "end_offset": 11,
14.        "type": "shingle",
15.        "position": 0,
16.        "positionLength": 2
17.      },
18.      {
19.        "token": "python",
20.        "start_offset": 5,
21.        "end_offset": 11,
22.        "type": "<ALPHANUM>",
23.        "position": 1
24.      },
25.      {
26.        "token": "python go",
27.        "start_offset": 5,
28.        "end_offset": 14,
29.        "type": "shingle",
30.        "position": 1,
31.        "positionLength": 2
32.      },
33.      {
34.        "token": "go",
35.        "start_offset": 12,
36.        "end_offset": 14,
37.        "type": "<ALPHANUM>",
38.        "position": 2
39.      }
40.    ]
41.  }


过滤器的默认行为是发出 uni-grams 和双词 n-grams。 我们可以通过创建带有自定义 shingle 过滤器的自定义分析器来更改此默认行为。 下面的清单显示了这是如何配置的。



1.  PUT index_with_shingle
2.  {
3.    "settings": {
4.      "analysis": {
5.        "analyzer": {
6.          "shingles_analyzer":{
7.            "tokenizer":"standard",
8.            "filter":["shingles_filter"]
9.          }
10.        },
11.        "filter": {
12.          "shingles_filter":{
13.            "type":"shingle",
14.            "min_shingle_size":2,
15.            "max_shingle_size":3,
16.            "output_unigrams":false
17.          }
18.        }
19.      }
20.    }
21.  }


在某些文本上调用此代码(如下面的清单所示)会生成两组和三组单词。



1.  POST index_with_shingle/_analyze
2.  {
3.    "text": "java python go",
4.    "analyzer": "shingles_analyzer"
5.  }


上面命令输出的结果为:



1.  {
2.    "tokens": [
3.      {
4.        "token": "java python",
5.        "start_offset": 0,
6.        "end_offset": 11,
7.        "type": "shingle",
8.        "position": 0
9.      },
10.      {
11.        "token": "java python go",
12.        "start_offset": 0,
13.        "end_offset": 14,
14.        "type": "shingle",
15.        "position": 0,
16.        "positionLength": 2
17.      },
18.      {
19.        "token": "python go",
20.        "start_offset": 5,
21.        "end_offset": 14,
22.        "type": "shingle",
23.        "position": 1
24.      }
25.    ]
26.  }


分析器返回 [java python, java python go, python go] 因为我们已经将过滤器配置为仅生成 2 个和 3 个单词的 shingles。 输出中删除了像 “java”、“python”等一元组(一个单词 shingle),因为我们禁用了过滤器来输出它们。

更多关于 Singles 过滤器的内容,请参考文章 “Elasticsearch: Ngrams, edge ngrams, and shingles”。

同义词过滤器

同义词是具有相同含义的不同词。 例如,对于 football 和 soccer(后者是美国对 football 的称呼),两者都应该指向足球比赛。 同义词过滤器有助于创建一组词,以帮助在搜索时产生更丰富的用户体验。

Elasticsearch 希望我们通过使用同义词分词过滤器配置分析器来提供一组单词及其同义词。 如清单所示,我们在索引的设置上创建同义词过滤器:



1.  PUT index_with_synonyms
2.  {
3.    "settings": {
4.      "analysis": {
5.        "filter": {
6.          "synonyms_filter":{
7.            "type":"synonym",
8.            "synonyms":[ "soccer => football"]
9.          }
10.        }
11.      }
12.    }
13.  }


在代码示例中,我们创建了一个与同义词类型关联的同义词列表(soccer 被视为 football 的替代名称)。 一旦我们用这个过滤器配置了索引,我们就可以测试文本字段:



1.  POST index_with_synonyms/_analyze
2.  {
3.    "text": "What's soccer?",
4.    "tokenizer": "standard", 
5.    "filter": ["synonyms_filter"]
6.  }


上面命令输出的结果为:



1.  {
2.    "tokens": [
3.      {
4.        "token": "What's",
5.        "start_offset": 0,
6.        "end_offset": 6,
7.        "type": "<ALPHANUM>",
8.        "position": 0
9.      },
10.      {
11.        "token": "football",
12.        "start_offset": 7,
13.        "end_offset": 13,
14.        "type": "SYNONYM",
15.        "position": 1
16.      }
17.    ]
18.  }


这会产生两个分词:“What's” 和 “football”。 从输出中可以看出,“soccer” 这个词被 “football” 这个词替换了。

从文件中配置同义词

我们可以通过文件系统上的文件提供同义词,而不是像我们在前面的清单中那样对它们进行硬编码。 为此,我们需要在 synonyms_path 变量中提供文件路径,如以下清单所示。



1.  PUT index_with_synonyms_from_file_analyzer
2.  {
3.    "settings": {
4.      "analysis": {
5.        "analyzer": {
6.          "synonyms_analyzer":{
7.            "type":"standard",
8.            "filter":["synonyms_from_file_filter"]
9.          }
10.        }
11.        ,"filter": {
12.          "synonyms_from_file_filter":{
13.            "type":"synonym",
14.            "synonyms_path":"synonyms.txt" #A Relative path of the synonyms file
15.          }
16.        }
17.      }
18.    }
19.  }


确保在 $ELASTICSEARCH_HOME/config 下创建了一个名为 “synonyms.txt” 的文件,其内容如下:

带有一组同义词的 synonyms.txt 文件



1.  # file: synonyms.txt
2.  important=>imperative
3.  beautiful=>gorgeous


我们可以使用相对或绝对路径调用该文件。 相对路径指向 Elasticsearch 安装文件夹的 config 目录。 我们可以通过使用以下输入调用 _analyze API 来测试上述分析器,如清单中所示:



1.  POST index_with_synonyms_from_file_analyzer/_analyze
2.  {
3.    "text": "important",
4.    "tokenizer": "standard", 
5.    "filter": ["synonyms_from_file_filter"]
6.  }


我们当然应该得到 “imperative” 分词作为响应,证明同义词是从我们放在配置文件夹中的 synonyms.txt 文件中提取的。 你可以在 Elasticsearch 运行时向该文件添加更多值并尝试一下。

更多阅读: