Elasticsearch:分词器中的 token 过滤器使用示例

2022年9月16日   |   by mebius

分词器在 Elasticsearch 的使用中非常重要。分词器中的过滤器可以帮我们对最终的分词进行处理,从而使得我们得到的最终分词会影响存储的大小和搜索的方式。在今天的文章中,我来分享一下一些常用的分词器中的 token 过滤器。更多有关 token 过滤器的内容可以在 Elastic 的官方文档查询。有关更多关于 analyzer 的阅读,请参考我之前的文章 “Elasticsearch: analyzer”。

%title插图%num

如上图所示,在分词器的构成中,它可以含有0或多个 char filters,有且只有一个 tokenizer,0或多个 token filters。

安装

在今天的展示中,我们需要安装中文最为流行的 IK 分词器。详细的安装步骤请参考文章 “Elasticsearch:IK 中文分词器”。

Apostrophe token filter

去掉撇号后的所有字符,包括撇号本身。这个在英文中比较常见。比如,我们写如下的句子:

This is Tom's clothes.

在上面,我们可以看到有一个 ’ 符号。在实际的分词中,我们希望去掉 ‘ 符号后面的所有字符。我们可以使用如下的例子来进行展示:

GET /_analyze
{
  "tokenizer" : "standard",
  "filter" : ["apostrophe"],
  "text" : "Istanbul'a veya Istanbul'dan"
}

上面的 filter 产生如下的结果:

{
  "tokens": [
    {
      "token": "Istanbul",
      "start_offset": 0,
      "end_offset": 10,
      "type": "",
      "position": 0
    },
    {
      "token": "veya",
      "start_offset": 11,
      "end_offset": 15,
      "type": "",
      "position": 1
    },
    {
      "token": "Istanbul",
      "start_offset": 16,
      "end_offset": 28,
      "type": "",
      "position": 2
    }
  ]
}

在实际的使用中,我们可以使用如下的例子来进行展示:

PUT test
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "apostrophe",
            "lowercase"
            ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}

在上面,我们定义了一个叫做 test 的索引,并且它的 text 字段使用 my_analyzer。在没有特定指定 search_analyzer 的情况下,分词和搜索将使用同样的一个分词器:my_analyzer。我们使用如下的一个命令来写入一个文档:

PUT test/_doc/1
{
  "text": "Istanbul'a veya Istanbul'dan"
}

那么我们可以使用如下的一个搜索来搜索该文档:

GET test/_search
{
  "query": {
    "match": {
      "text": "Istanbul"
    }
  }
}

上面的搜索结果为:

{
  "tokens": [
    {
      "token": "Istanbul",
      "start_offset": 0,
      "end_offset": 10,
      "type": "",
      "position": 0
    },
    {
      "token": "veya",
      "start_offset": 11,
      "end_offset": 15,
      "type": "",
      "position": 1
    },
    {
      "token": "Istanbul",
      "start_offset": 16,
      "end_offset": 28,
      "type": "",
      "position": 2
    }
  ]
}

ASCII folding token filter

将不在 Basic Latin Unicode 块中的字母、数字和符号字符(前 127 个 ASCII 字符)转换为它们的 ASCII 等效字符(如果存在)。 例如,过滤器将 更改为 a。

以下 analyzeAPI 请求使用 asciifolding 过滤器删除 aa la carte 中的变音符号:

GET /_analyze
{
  "tokenizer" : "standard",
  "filter" : ["asciifolding"],
  "text" : "aa  la carte"
}

上面的命令返回:

{
  "tokens": [
    {
      "token": "acai",
      "start_offset": 0,
      "end_offset": 4,
      "type": "",
      "position": 0
    },
    {
      "token": "a",
      "start_offset": 5,
      "end_offset": 6,
      "type": "",
      "position": 1
    },
    {
      "token": "la",
      "start_offset": 7,
      "end_offset": 9,
      "type": "",
      "position": 2
    },
    {
      "token": "carte",
      "start_offset": 10,
      "end_offset": 15,
      "type": "",
      "position": 3
    }
  ]
}

Classic token filter

经典分词器生成的术语执行可选的后处理。

此过滤器从单词末尾删除英语所有格 (‘s) 并从首字母缩略词中删除点。 它使用 Lucene 的 ClassicFilter。例如:

GET /_analyze
{
  "tokenizer" : "classic",
  "filter" : ["classic"],
  "text" : "The 2 Q.U.I.C.K. Brown-Foxes jumped over the lazy dog's bone."
}

上面将返回:

{
  "tokens": [
    {
      "token": "The",
      "start_offset": 0,
      "end_offset": 3,
      "type": "",
      "position": 0
    },
    {
      "token": "2",
      "start_offset": 4,
      "end_offset": 5,
      "type": "",
      "position": 1
    },
    {
      "token": "QUICK",
      "start_offset": 6,
      "end_offset": 16,
      "type": "",
      "position": 2
    },
    {
      "token": "Brown",
      "start_offset": 17,
      "end_offset": 22,
      "type": "",
      "position": 3
    },
    {
      "token": "Foxes",
      "start_offset": 23,
      "end_offset": 28,
      "type": "",
      "position": 4
    },
    {
      "token": "jumped",
      "start_offset": 29,
      "end_offset": 35,
      "type": "",
      "position": 5
    },
    {
      "token": "over",
      "start_offset": 36,
      "end_offset": 40,
      "type": "",
      "position": 6
    },
    {
      "token": "the",
      "start_offset": 41,
      "end_offset": 44,
      "type": "",
      "position": 7
    },
    {
      "token": "lazy",
      "start_offset": 45,
      "end_offset": 49,
      "type": "",
      "position": 8
    },
    {
      "token": "dog",
      "start_offset": 50,
      "end_offset": 55,
      "type": "",
      "position": 9
    },
    {
      "token": "bone",
      "start_offset": 56,
      "end_offset": 60,
      "type": "",
      "position": 10
    }
  ]
}

显然,在 Q.U.I.CK. 中的点都被删除了,同时dog’s 中的 ‘s 被去掉了。

Conditional token filter

将一组 token 过滤器应用于与提供的谓词脚本中的条件匹配的标记。此过滤器使用 Lucene 的 ConditionalTokenFilter。

以下 analyzeAPI 请求使用条件过滤器来匹配 THE QUICK BROWN FOX 中少于 5 个字符的标记。 然后它将 lowercase 过滤器应用于那些匹配的标记,将它们转换为小写。

GET /_analyze
{
  "tokenizer": "standard",
  "filter": [
    {
      "type": "condition",
      "filter": [ "lowercase" ],
      "script": {
        "source": "token.getTerm().length() 

上面的命令返回:

{
  "tokens": [
    {
      "token": "the",
      "start_offset": 0,
      "end_offset": 3,
      "type": "",
      "position": 0
    },
    {
      "token": "QUICK",
      "start_offset": 4,
      "end_offset": 9,
      "type": "",
      "position": 1
    },
    {
      "token": "BROWN",
      "start_offset": 10,
      "end_offset": 15,
      "type": "",
      "position": 2
    },
    {
      "token": "fox",
      "start_offset": 16,
      "end_offset": 19,
      "type": "",
      "position": 3
    }
  ]
}

请注意上面的 script 指的是 painless script。在上面,我们看到虽然有 lowercase 过滤器,但是它仅仅作用于 token 的长度小于 5 的 token 上面。从输出中,我们可以看到,BROWN及 QUICK 并没有变为小写,原因是他们的长度是5,不满足条件。

自定义并添加到分词器

要自定义 conditional 过滤器,请将其复制以创建新的自定义标记过滤器的基础。 你可以使用其可配置参数修改过滤器。

例如,以下创建索引 API 请求使用自定义 conditional 过滤器来配置新的自定义分词器。 自定义条件过滤器匹配流中的第一个 token。 然后它使用反向过滤器反转匹配的 token。

PUT /palindrome_list
{
  "settings": {
    "analysis": {
      "analyzer": {
        "whitespace_reverse_first_token": {
          "tokenizer": "whitespace",
          "filter": [ "reverse_first_token" ]
        }
      },
      "filter": {
        "reverse_first_token": {
          "type": "condition",
          "filter": [ "reverse" ],
          "script": {
            "source": "token.getPosition() === 0"
          }
        }
      }
    }
  }
}

Fingerprint token filter

从 token 流中排序和删除重复的 token,然后将流连接成单个输出 token。例如,此过滤器将 [ the, fox, was, very, very, quick ] 标记流更改如下:

  1. 将 token 按字母顺序排序为 [ fox, quick, the, very, very, was ]
  2. 删除 very token 的重复实例。
  3. 将 token 流连接到输出单个 token:[fox quick the very was]

此过滤器使用 Lucene 的 FingerprintFilter。例如:

以下 analyzeAPI 请求使用指纹过滤器为文本 zebra jumps over resting resting dog 创建单个输出 token:

GET _analyze
{
  "tokenizer" : "whitespace",
  "filter" : ["fingerprint"],
  "text" : "zebra jumps over resting resting dog"
}

上面的命令返回:

{
  "tokens": [
    {
      "token": "dog jumps over resting zebra",
      "start_offset": 0,
      "end_offset": 36,
      "type": "fingerprint",
      "position": 0
    }
  ]
}

请注意的是它返回有一个 token。

Reverse token filter

反转流中的每个 token。 例如,你可以使用反向过滤器将 cat 更改为 tac。反转 token 对于基于后缀的搜索很有用,例如查找以 -ion 结尾的单词或按扩展名搜索文件名。请参阅我之前的文章 “Elasticsearch:正确使用 regexp 搜索”。这个过滤器使用 Lucene 的 ReverseStringFilter

以下 analyze API 请求使用反向过滤器来反转 quick fox jumps 中的每个 token:

GET _analyze
{
  "tokenizer" : "standard",
  "filter" : ["reverse"],
  "text" : "quick fox jumps"
}

上面的过滤器返回:

{
  "tokens": [
    {
      "token": "kciuq",
      "start_offset": 0,
      "end_offset": 5,
      "type": "",
      "position": 0
    },
    {
      "token": "xof",
      "start_offset": 6,
      "end_offset": 9,
      "type": "",
      "position": 1
    },
    {
      "token": "spmuj",
      "start_offset": 10,
      "end_offset": 15,
      "type": "",
      "position": 2
    }
  ]
}

就如第一个例子中显示的那样,我们可以使用如下的例子来定义并使用这个 filter:

PUT reverse_example
{
  "settings" : {
    "analysis" : {
      "analyzer" : {
        "whitespace_reverse" : {
          "tokenizer" : "whitespace",
          "filter" : ["reverse"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "analyzer": "whitespace_reverse"
      }
    }
  }
}

我们可以使用如下的命令来写入一个文档:

PUT reverse_example/_doc/1
{
  "text": "I like the speed of this network"
}

我们可以使用如下的方法来进行搜索:

GET reverse_example/_search
{
  "query": {
    "regexp": {
      "text": "krow.*"
    }
  }
}

特别需要指出的是:当我们使用通配符在前面进行搜索时搜索比较慢。这个时候,我们可以选择 reverse 过滤器来进行反转。

Unique token filter

从流中删除重复的 token。 例如,你可以使用 unique 过滤器将 the lazy lazy dog 更改为 the lazy dog。如果 only_on_same_position 参数设置为 true,则unique 过滤器仅删除同一位置的重复标记。

注意:当 only_on_same_position 为 true 时,unique 过滤器的工作方式与 remove_duplicates 过滤器相同。

我们使用如下的例子来进行展示:

GET _analyze
{
  "tokenizer" : "whitespace",
  "filter" : ["unique"],
  "text" : "the quick fox jumps the lazy fox"
}

上面的命令返回:

{
  "tokens": [
    {
      "token": "the",
      "start_offset": 0,
      "end_offset": 3,
      "type": "word",
      "position": 0
    },
    {
      "token": "quick",
      "start_offset": 4,
      "end_offset": 9,
      "type": "word",
      "position": 1
    },
    {
      "token": "fox",
      "start_offset": 10,
      "end_offset": 13,
      "type": "word",
      "position": 2
    },
    {
      "token": "jumps",
      "start_offset": 14,
      "end_offset": 19,
      "type": "word",
      "position": 3
    },
    {
      "token": "lazy",
      "start_offset": 24,
      "end_offset": 28,
      "type": "word",
      "position": 4
    }
  ]
}

显然上面的返回中,只有一个 fox,而在原文档中有两个 fox。我们可以通过如下的方式来添加这个过滤器到 analyzer 中:

PUT custom_unique_example
{
  "settings" : {
    "analysis" : {
      "analyzer" : {
        "standard_truncate" : {
        "tokenizer" : "standard",
        "filter" : ["unique"]
        }
      }
    }
  }
}

我们可以通过如下的方式来定制这个过滤器:

PUT letter_unique_pos_example
{
  "settings": {
    "analysis": {
      "analyzer": {
        "letter_unique_pos": {
          "tokenizer": "letter",
          "filter": [ "unique_pos" ]
        }
      },
      "filter": {
        "unique_pos": {
          "type": "unique",
          "only_on_same_position": true
        }
      }
    }
  }
}

在上面,我们设置only_on_same_position 为 true。在默认的情况下,这个值为 false。

Length token filter

删除比指定字符长度更短或更长的标记。 例如,你可以使用长度过滤器来排除短于 2 个字符的标记和长于 5 个字符的标记。此过滤器使用 Lucene 的 LengthFilter

提示:长度过滤器删除整个 token。 如果你希望将 token 缩短到特定长度,请使用 truncate 过滤器。

例如:

GET _analyze
{
  "tokenizer": "whitespace",
  "filter": [
    {
      "type": "length",
      "min": 0,
      "max": 4
    }
  ],
  "text": "the quick brown fox jumps over the lazy dog"
}

上面的命令返回长度为 0 到 4 的 token:

{
  "tokens": [
    {
      "token": "the",
      "start_offset": 0,
      "end_offset": 3,
      "type": "word",
      "position": 0
    },
    {
      "token": "fox",
      "start_offset": 16,
      "end_offset": 19,
      "type": "word",
      "position": 3
    },
    {
      "token": "over",
      "start_offset": 26,
      "end_offset": 30,
      "type": "word",
      "position": 5
    },
    {
      "token": "the",
      "start_offset": 31,
      "end_offset": 34,
      "type": "word",
      "position": 6
    },
    {
      "token": "lazy",
      "start_offset": 35,
      "end_offset": 39,
      "type": "word",
      "position": 7
    },
    {
      "token": "dog",
      "start_offset": 40,
      "end_offset": 43,
      "type": "word",
      "position": 8
    }
  ]
}

这个过滤器对中文也有很多的帮助。比如,我们只想有超过两个字的中文 token:

PUT twitter
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "ik_smart",
          "filter": [
            "longer_than_2"
          ]
        }
      },
      "filter": {
        "longer_than_2": {
          "type": "length",
          "min": 2
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}

我们使用上面的索引来进行测试:

GET twitter/_analyze
{
  "analyzer": "my_analyzer",
  "text": ["我爱北京天安门"]
}

上面的命令返回:

{
  "tokens": [
    {
      "token": "北京",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "天安门",
      "start_offset": 4,
      "end_offset": 7,
      "type": "CN_WORD",
      "position": 3
    }
  ]
}

显然它只返回 “北京” 及 “天安门” 这个两个长度超过 2 的 token。我们和如下的分词进行比较:

GET _analyze
{
  "analyzer": "ik_smart",
  "text": ["我爱北京天安门"]
}
{
  "tokens": [
    {
      "token": "我",
      "start_offset": 0,
      "end_offset": 1,
      "type": "CN_CHAR",
      "position": 0
    },
    {
      "token": "爱",
      "start_offset": 1,
      "end_offset": 2,
      "type": "CN_CHAR",
      "position": 1
    },
    {
      "token": "北京",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "天安门",
      "start_offset": 4,
      "end_offset": 7,
      "type": "CN_WORD",
      "position": 3
    }
  ]
}

很显然,当我们写入如下的文档:

PUT twitter/_doc/1
{
  "text": "我爱北京天安门"
}

我们使用如下的命令来进行搜索:

GET twitter/_search
{
  "query": {
    "match": {
      "text": "我"
    }
  }
}

它将不返回任何的文档,但是如果我们使用如下的命令来进行搜索:

GET twitter/_search
{
  "query": {
    "match": {
      "text": "北京"
    }
  }
}

它将返回我们的文档。

Lower token filter

将 token 文本更改为小写。 例如,你可以使用 lowercase 过滤器将 THE Lazy DoG 更改为 the lazy dog。

除了默认过滤器之外,lowercase token 过滤器还提供对 Lucene 的希腊语、爱尔兰语和土耳其语的特定语言小写过滤器的访问。

例如:

GET _analyze
{
  "tokenizer" : "standard",
  "filter" : ["lowercase"],
  "text" : "THE Quick FoX JUMPs"
}

上面的命令返回:

{
  "tokens": [
    {
      "token": "the",
      "start_offset": 0,
      "end_offset": 3,
      "type": "",
      "position": 0
    },
    {
      "token": "quick",
      "start_offset": 4,
      "end_offset": 9,
      "type": "",
      "position": 1
    },
    {
      "token": "fox",
      "start_offset": 10,
      "end_offset": 13,
      "type": "",
      "position": 2
    },
    {
      "token": "jumps",
      "start_offset": 14,
      "end_offset": 19,
      "type": "",
      "position": 3
    }
  ]
}

很显然,它把上面的大写字母都变为小写的了。

Upercase token filter

将 token 文本更改为大写。 例如,你可以使用大写过滤器将 Lazy Dog 更改为 THE LAZY DOG。此过滤器使用 Lucene 的 UpperCaseFilter

以下 analyze API 请求使用默认 uppercase 过滤器将 Quick FoX JUMP 更改为大写:

GET _analyze
{
  "tokenizer" : "standard",
  "filter" : ["uppercase"],
  "text" : "the Quick FoX JUMPs"
}

上面的命令返回结果:

{
  "tokens": [
    {
      "token": "THE",
      "start_offset": 0,
      "end_offset": 3,
      "type": "",
      "position": 0
    },
    {
      "token": "QUICK",
      "start_offset": 4,
      "end_offset": 9,
      "type": "",
      "position": 1
    },
    {
      "token": "FOX",
      "start_offset": 10,
      "end_offset": 13,
      "type": "",
      "position": 2
    },
    {
      "token": "JUMPS",
      "start_offset": 14,
      "end_offset": 19,
      "type": "",
      "position": 3
    }
  ]
}

以下创建索引 API 请求使用 upercase 过滤器来配置新的自定义分词器。

PUT uppercase_example
{
  "settings": {
    "analysis": {
      "analyzer": {
        "whitespace_uppercase": {
          "tokenizer": "whitespace",
          "filter": [ "uppercase" ]
        }
      }
    }
  }
}

Stop token filter

从 token 流中删除停用词。未自定义时,过滤器默认删除以下英文停用词:

a, an, and, are, as, at, be, but, by, for, if, in, into, is, it, no, not, of, on, or, such, that, the, their, then, there, these, they, this, to, was, will, with

除了英语之外,stop 过滤器还支持多种语言的预定义 stop 词列表。 你还可以将自己的 stop 词指定为数组或文件。stop 过滤器使用 Lucene 的 StopFilter

例子:

下面的 analyze API 请求使用 stop 过滤器从 a quick fox jumps over the lazy dog 来删除停用词a 和 the:

GET /_analyze
{
  "tokenizer": "standard",
  "filter": [ "stop" ],
  "text": "a quick fox jumps over the lazy dog"
}

上面的命令返回结果:

{
  "tokens": [
    {
      "token": "quick",
      "start_offset": 2,
      "end_offset": 7,
      "type": "",
    tgcode  "position": 1
    },
    {
      "token": "fox",
      "start_offset": 8,
      "end_offset": 11,
      "type": "",
      "position": 2
    },
    {
      "token": "jumps",
      "start_offset": 12,
      "end_offset": 17,
      "type": "",
      "position": 3
    },
    {
      "token": "over",
      "start_offset": 18,
      "end_offset": 22,
      "type": "",
      "position": 4
    },
    {
      "token": "lazy",
      "start_offset": 27,
      "end_offset": 31,
      "type": "",
      "position": 6
    },
    {
      "token": "dog",
      "start_offset": 32,
      "end_offset": 35,
      "type": "",
      "position": 7
    }
  ]
}

从上面的输出结果中,我们可以看到 a 及 the 没有出现。

如果我们想自定义 stop 过滤器,我们可以仿照如下的方法:

PUT stop_example
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "my_stop"
          ]
        }
      },
      "filter": {
        "my_stop": {
          "type": "stop",
          "stopwords": [
            "over",
            "dog"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}

在上面,我们自定义了一个叫做 my_stop 的 stop 过滤器。它自己含有 over 及 dog。也就是说 over 及 dog 将不会被分词。我们使用如下的命令来进行测试:

GET stop_example/_analyze
{
  "analyzer": "my_analyzer",
  "text": ["a quick fox jumps over the lazy dog"]
}

上面的命令返回结果:

{
  "tokens": [
    {
      "token": "a",
      "start_offset": 0,
      "end_offset": 1,
      "type": "",
      "position": 0
    },
    {
      "token": "quick",
      "start_offset": 2,
      "end_offset": 7,
      "type": "",
      "position": 1
    },
    {
      "token": "fox",
      "start_offset": 8,
      "end_offset": 11,
      "type": "",
      "position": 2
    },
    {
      "token": "jumps",
      "start_offset": 12,
      "end_offset": 17,
      "type": "",
      "position": 3
    },
    {
      "token": "the",
      "start_offset": 23,
      "end_offset": 26,
      "type": "",
      "position": 5
    },
    {
      "token": "lazy",
      "start_offset": 27,
      "end_offset": 31,
      "type": "",
      "position": 6
    }
  ]
}

从上面的结果中,我们可以看出来, dog 及 over 不见了。当然,我们也看到了 a 及 the 又同时出现了。如果我们还是想保持之前的默认的 stop 过滤器,我们可以重新设计 stop_example 索引:

DELETE stop_example

PUT stop_example
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "my_stop",
            "stop"
          ]
        }
      },
      "filter": {
        "my_stop": {
          "type": "stop",
          "stopwords": [
            "over",
            "dog"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}

由于 filter 可以是多个,而且它的执行顺序是从上而下执行的。在上面,它先执行 my_stop,然后是 stop 过滤器。我们使用同样的命令来进行测试:

GET stop_example/_analyze
{
  "analyzer": "my_analyzer",
  "text": ["a quick fox jumps over the lazy dog"]
}

这次显示的结果为:

{
  "tokens": [
    {
      "token": "quick",
      "start_offset": 2,
      "end_offset": 7,
      "type": "",
      "position": 1
    },
    {
      "token": "fox",
      "start_offset": 8,
      "end_offset": 11,
      "type": "",
      "position": 2
    },
    {
      "token": "jumps",
      "start_offset": 12,
      "end_offset": 17,
      "type": "",
      "position": 3
    },
    {
      "token": "lazy",
      "start_offset": 27,
      "end_offset": 31,
      "type": "",
      "position": 6
    }
  ]
}

显然,a 和 the 不见了。

Predicate script token filter

删除与提供的谓词脚本不匹配的标记。 该过滤器仅支持内联 Painless 脚本。 在分词谓词上下文中评估脚本。

示例:

以下 analyze API 请求使用 predicate_token_filter 过滤器仅输出c从 the fox jumps the lazy dog 长于三个字符的 token。

GET /_analyze
{
  "tokenizer": "whitespace",
  "filter": [
    {
      "type": "predicate_token_filter",
      "script": {
        "source": """
          token.term.length() > 3
        """
      }
    }
  ],
  "text": "the fox jumps the lazy dog"
}

上面的命令返回的结果为:

{
  "tokens": [
    {
      "token": "jumps",
      "start_offset": 8,
      "end_offset": 13,
      "type": "word",
      "position": 2
    },
    {
      "token": "lazy",
      "start_offset": 18,
      "end_offset": 22,
      "type": "word",
      "position": 4
    }
  ]
}

事实上,我们可以使用这个过滤器来实现上面描述的 length 过滤器。比如:

PUT predicate_example
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "ik_smart",
          "filter": [
            "my_predicate"
          ]
        }
      },
      "filter": {
        "my_predicate": {
          "type": "predicate_token_filter",
          "script": {
            "source": """
              token.term.length() > 1
           """
          }
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}

我们使用如下的命令来进行测试:

GET predicate_example/_analyze
{
  "analyzer": "my_analyzer",
  "text": ["我爱北京天安门"]
}
{
  "tokens": [
    {
      "token": "北京",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "天安门",
      "start_offset": 4,
      "end_offset": 7,
      "type": "CN_WORD",
      "position": 3
    }
  ]
}

显然只有长度是大于 1 的 token 才会显示。在实际的使用中,脚本的执行速度较慢一些。如果你的脚本的长度或计算的时间较长,我们需要注意使用。

自定义并添加到分词器

要自定义 predicate_token_filter 过滤器,复制它以创建新的自定义 token 过滤器的基础。 你可以使用其可配置参数修改过滤器。

以下创建索引 API 请求使用自定义 predicate_token_filter 过滤器 my_script_filter 配置新的自定义分词器。

my_script_filter 过滤器删除除 ALPHANUM 之外的任何类型的 token。

PUT /my-index-000001
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "my_script_filter"
          ]
        }
      },
      "filter": {
        "my_script_filter": {
          "type": "predicate_token_filter",
          "script": {
            "source": """
              token.type.contains("ALPHANUM")
            """
          }
        }
      }
    }
  }
}

Trim token filter

从流中的每个 token 中删除前导和尾随空格。 虽然这可以更改 token 的长度,但 trim 过滤器不会更改 token 的偏移量。 trim 过滤器使用 Lucene 的 TrimFilter

例子:

要查看 trim 过滤器的工作原理,你首先需要生成一个包含空格的 token。以下 analyze API 请求使用 keywordtokenizer 为“ fox ”生成 token。

GET _analyze
{
  "tokenizer" : "keyword",
  "text" : " fox "
}

API 返回以下响应。 请注意,“ fox ” token 包含原始文本的空格。 请注意,尽管更改了 token 的长度,但 start_offset 和 end_offset 保持不变。

{
  "tokens": [
    {
      "token": " fox ",
      "start_offset": 0,
      "end_offset": 5,
      "type": "word",
      "position": 0
    }
  ]
}

要删除空格,请将 trim 过滤器添加到之前的 analyzeAPI 请求。

GET _analyze
{
  "tokenizer" : "keyword",
  "filter" : ["trim"],tgcode
  "text" : " fox "
}

API 返回以下响应。 返回的 fox token 不包含任何前导或尾随空格。

{
  "tokens": [
    {
      "token": "fox",
      "start_offset": 0,
      "end_offset": 5,
      "type": "word",
      "position": 0
    }
  ]
}

以下创建索引 API 请求使用 trim 过滤器来配置新的自定义分词器。

PUT trim_example
{
  "settings": {
    "analysis": {
      "analyzer": {
        "keyword_trim": {
          "tokenizer": "keyword",
          "filter": [ "trim" ]
        }
      }
    }
  }
}

Synonym token filter

这是一个同义词过滤器。你可以阅读我之前的文章 “Elasticsearch:使用同义词 synonyms 来提高搜索效率”。

Truncate token filter

Truncate 超过指定字符限制的标记。 此限制默认为 10,但可以使用长度参数进行自定义。例如,你可以使用 truncate 过滤器将所有 token 缩短为 3 个字符或更少,将 jumping fox 更改为 jum fox。此过滤器使用 Lucene 的 TruncateTokenFilter

例子:

以下 analyze API 请求使用 truncate 过滤器来缩短 quinquennial extravaganza carried on 中超过 10 个字符的标记:

GET _analyze
{
  "tokenizer" : "whitespace",
  "filter" : ["truncate"],
  "text" : "the quinquennial extravaganza carried on"
}

上面的命令返回:

{
  "tokens": [
    {
      "token": "the",
      "start_offset": 0,
      "end_offset": 3,
      "type": "word",
      "position": 0
    },
    {
      "token": "quinquenni",
      "start_offset": 4,
      "end_offset": 16,
      "type": "word",
      "position": 1
    },
    {
      "token": "extravagan",
      "start_offset": 17,
      "end_offset": 29,
      "type": "word",
      "position": 2
    },
    {
      "token": "carried",
      "start_offset": 30,
      "end_offset": 37,
      "type": "word",
      "position": 3
    },
    {
      "token": "on",
      "start_offset": 38,
      "end_offset": 40,
      "type": "word",
      "position": 4
    }
  ]
}

我们可以发现 token 的长度不超过 10。10 是默认的值。

添加到分词仪

以下创建索引 API 请求使用截断过滤器来配置新的自定义分词器。

PUT customtgcode_truncate_example
{
  "settings" : {
    "analysis" : {
      "analyzer" : {
        "standard_truncate" : {
        "tokenizer" : "standard",
        "filter" : ["truncate"]
        }
      }
    }
  }
}

我们甚至可以针对 truncate 过滤器进行定制:

PUT 5_char_words_example
{
  "settings": {
    "analysis": {
      "analyzer": {
        "lowercase_5_char": {
          "tokenizer": "lowercase",
          "filter": [ "5_char_trunc" ]
        }
      },
      "filter": {
        "5_char_trunc": {
          "type": "truncate",
          "length": 5
        }
      }
    }
  }
}

通过对 length 参数的定制,我们可以限制 token 的长度最多不超过 5,而不是默认的 10。

Limit token count token filter

限制输出 token 的数量。 限制过滤器通常用于根据 token 计数限制文档字段值的大小。默认情况下,限制过滤器仅保留流中的第一个 token。 例如,过滤器可以将 token 流 [one、two、three] 更改为 [one]。此过滤器使用 Lucene 的 LimitTokenCountFilter

示例:

GET _analyze
{
  "tokenizer": "standard",
    "filter": [
    {
      "type": "limit",
      "max_token_count": 2
    }
  ],
  "text": "quick fox jumps over lazy dog"
}

上面的输出为:

{
  "tokens": [
    {
      "token": "quick",
      "start_offset": 0,
      "end_offset": 5,
      "type": "",
      "position": 0
    },
    {
      "token": "fox",
      "start_offset": 6,
      "end_offset": 9,
      "type": "",
      "position": 1
    }
  ]
}

也就是取前面的两个 token。

添加到分词器

以下创建索引 API 请求使用 limit 过滤器来配置新的自定义分词器。

PUT limit_example
{
  "settings": {
    "analysis": {
      "analyzer": {
        "standard_one_token_limit": {
          "tokenizer": "standard",
          "filter": [ "limit" ]
        }
      }
    }
  }
}

我们也可以定制 limit 过滤器:

PUT custom_limit_example
{
  "settings": {
    "analysis": {
      "analyzer": {
        "whitespace_five_token_limit": {
          "tokenizer": "whitespace",
          "filter": [ "five_token_limit" ]
        }
      },
      "filter": {
        "five_token_limit": {
          "type": "limit",
          "max_token_count": 5
        }
      }
    }
  }
}

上面的max_token_count 在默认的情况下为 1。我们可以通过对它的定制来限制最多的 token 输出。

Shingle token filter

请详细阅读之前的文章 “Elasticsearch: Ngrams, edge ngrams, and shingles”。

中文相关的 filter

请详细阅读我之前的文章:

好了,今天的分享就到这里。更多的过滤器,请参阅官方文档

参考:

【1】Token filter reference | Elasticsearch Guide [8.4] | Elastic

文章来源于互联网:Elasticsearch:分词器中的 token 过滤器使用示例

Tags: