ElasticSearch【有与无】【搜索引擎】【ES19】归一化词元

目录

1.简介

1.1.口音处理

1.2.Unicode

Unicode 大小写折叠

Unicode 字符折叠

1.3.排序和整理

大小写敏感排序

语言之间的区别

Unicode 归类算法

Unicode 排序

指定语言

多排序规则

自定义排序


1.简介

文本切割成词元(token)只是这项工作的一半。为了让这些词元(token)更容易搜索, 这些词元(token)需要被 归一化(normalization)--这个过程会去除同一个词元(token)的无意义差别,例如大写和小写的差别。

这些都是语汇单元过滤器的工作。语汇单元过滤器接收来自分词器(tokenizer)的词元(token)流。还可以一起使用多个语汇单元过滤器,每一个都有自己特定的处理工作。每一个语汇单元过滤器都可以处理来自另一个语汇单元过滤器输出的单词流。

【举例】

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_lowercaser": { // 自定义分析器
          "tokenizer": "standard",
          "filter":  [ "lowercase" ]
        }
      }
    }
  }
}

GET /my_index/_analyze?analyzer=my_lowercaser
The QUICK Brown FOX! 
得到的词元是 the, quick, brown, fox
 

1.1.口音处理

去掉变音符号,把Unicode字符转化为ASCII来表示

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "folding": {
          "tokenizer": "standard",
          "filter":  [ "lowercase", "asciifolding" ]  // 把Unicode字符转化为ASCII来表示:
        }
      }
    }
  }
}

GET /my_index?analyzer=folding
My œsophagus caused a débâcle
得到的词元 my, oesophagus, caused, a, debacle
 

【保留】

PUT /my_index/_mapping/my_type
{
  "properties": {
    "title": {  // 原文形式
      "type":           "string",
      "analyzer":       "standard",
      "fields": {
        "folded": {  // 去掉变音符号
          "type":       "string",
          "analyzer":   "folding"
        }
      }
    }
  }
}
GET /my_index/_analyze?field=title 
Esta está loca
得到的词元 esta, está, loca

GET /my_index/_analyze?field=title.folded 
Esta está loca
得到的词元 esta, esta, loca

 

GET /my_index/_validate/query?explain
{
  "query": {
    "multi_match": {
      "type":     "most_fields",
      "query":    "está loca",
      "fields": [ "title", "title.folded" ]
    }
  }
}
(title:está        title:loca       )
(title.folded:esta title.folded:loca)

无论用户搜索的是 esta 还是 está; 两个文档都会被匹配,因为去掉变音符号形式的单词在 title.folded 字段中。然而,只有原文形式的单词在 title 字段中。此额外匹配会把包含原文形式单词的文档排在结果列表前面。

用 title.folded 字段来 扩大我们的网 (widen the net)来匹配更多的文档,然后用原文形式的 title 字段来把关联度最高的文档排在最前面。在可以为了匹配数量牺牲文本原意的情况下,这个技术可以被用在任何分析器里。

asciifolding 过滤器有一个叫做 preserve_original 的选项可以让你这样来做索引,
把词的原文词元(original token)和处理--折叠后的词元(folded token)放在同一个字段的同一个位置。
开启了这个选项,结果会像这样:

Position 1     Position 2
--------------------------
(ésta,esta)    loca
--------------------------
虽然这个是节约空间的好办法,但是也意味着没有办法再说“给我精确匹配的原文词元”(Give me an exact match on the original word)。
包含去掉和不去掉变音符号的词元,会导致不可靠的相关性评分。

所以,正如我们这一章做的,把每个字段的不同形式分开到不同的字段会让索引更清晰。

1.2.Unicode

当Elasticsearch在比较词元(token)的时候,它是进行字节(byte)级别的比较。 换句话说,如果两个词元(token)被判定为相同的话,他们必须是相同的字节(byte)组成的。然而,Unicode允许你用不同的字节来写相同的字符。

【问题】

é 和  的不同是什么?这取决于你问谁。对于Elasticsearch,第一个是由 0xC3 0xA9 这两个字节组成的,第二个是由 0x65 0xCC 0x81 这三个字节组成的。

对于Unicode,他们的差异和他们的怎么组成没有关系,所以他们是相同的。第一个是单个单词 é ,第二个是一个简单 e 和重音符 ´。

如果你的数据有多个来源,就会有可能发生这种状况:因为相同的单词使用了不同的编码,导致一个形式的 déjà 不能和它的其他形式进行匹配。

【解决】

这里有4种Unicode 归一化形式 (normalization forms) : nfcnfdnfkcnfkd,它们都把Unicode字符转换成对应标准格式,把所有的字符 进行字节(byte)级别的比较。

Unicode归一化形式 (Normalization Forms)

_组合_ (_composed_) 模式—`nfc` 和 `nfkc`—用尽可能少的字节(byte)来代表字符。 
((("composed forms (Unicode normalization)"))) 所以用 `é` 来代表单个字母 `é` 。  
_分解_ (_decomposed_) 模式—`nfd` and `nfkd`—用字符的每一部分来代表字符。
所以 `é` 分解为 `e` 和 `´`。 ((("decomposed forms (Unicode normalization)")))

规范 (canonical) 模式—nfc 和 nfd&—把连字作为单个字符,例如 ffi 或者 œ 。 
兼容 (compatibility) 模式—nfkc 和 nfkd—将这些组合的字符分解成简单字符的等价物,例如: f + f + i 或者 o + e.

【举例】

PUT /my_index
{
  "settings": {
    "analysis": {
      "filter": {
        "nfkc_normalizer": {  // 用 nfkc 归一化(normalization)模式来归一化(Normalize)所有词元(token).
          "type": "icu_normalizer",
          "name": "nfkc"
        }

      },
      "analyzer": {
        "my_normalizer": {
          "tokenizer": "icu_tokenizer",
          "filter":  [ "nfkc_normalizer" ]
        }
      }
    }
  }
}

包括刚才提到过的 icu_normalizer 语汇单元过滤器(token filters)在内,这里还有 icu_normalizer 字符 过滤器(character filters)。
虽然它和语汇单元过滤器做相同的工作,但是会在文本到达过滤器之前做。到底是用standard 过滤器,还是 icu_tokenizer 过滤器,其实并不重要。
因为过滤器知道怎么来正确处理所有的模式。

但是,如果你使用不同的分词器,
例如: ngram, edge_ngram, 或者 pattern 分词器,那么在语汇单元过滤器(token filters)之前使用 icu_normalizer 字符过滤器就变得有意义了。

通常来说,你不仅仅想要归一化(normalize)词元(token)的字节(byte)规则,还需要把他们转成小写字母。这个可以通过 icu_normalizer 和定制的归一化(normalization)的模式 nfkc_cf 来实现。

 

Unicode 大小写折叠

在Unicode中,大小写折叠 (Case folding) 把单词转换到一种(通常是小写)形式,是让写法不会影响单词的比较,所以拼写不需要完全正确。

【举例】

单词 ß,已经是小写形式了,会被折叠(folded)成 ss。类似的小写的 ς 被折叠成 σ,这样的话,无论 σ, ς, 和 Σ出现在哪里, 他们就都可以比较了。

`icu_normalizer` 语汇单元过滤器默认的归一化(normalization)模式是 `nfkc_cf`。
它像 `nfkc` 模式一样:

1.组合 (Composes) 字符用最短的字节来表示。
2.用 兼容 (compatibility)模式,把像 ffi 的字符转换成简单的 ffi
3.大小写折叠 (Case-folds) 字符成一种适合比较的形式

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_lowercaser": {
          "tokenizer": "icu_tokenizer",
          "filter":  [ "icu_normalizer" ]  // icu_normalizer 默认是 nfkc_cf 模式.
        }
      }
    }
  }
}

【测试】

GET /_analyze?analyzer=standard 
Weißkopfseeadler WEISSKOPFSEEADLER
得到的词元(token)是 weißkopfseeadler, weisskopfseeadler

GET /my_index/_analyze?analyzer=my_lowercaser 
Weißkopfseeadler WEISSKOPFSEEADLER
得到的词元(token)是 weisskopfseeadler, weisskopfseeadler

 

Unicode 字符折叠

在多语言((("Unicode", "character folding")))((("tokens", "normalizing", "Unicode character folding")))处理中,
`lowercase` 语汇单元过滤器(token filters)是一个很好的开始。但是作为对比的话,也只是对于整个巴别塔的惊鸿一瞥。
所以 <<asciifolding-token-filter,`asciifolding` token filter>> 需要更有效的Unicode 
_字符折叠_ (_character-folding_)工具来处理全世界的各种语言。((("asciifolding token filter")))
`icu_folding` 语汇单元过滤器(token filters) (provided by the <<icu-plugin,`icu` plug-in>>)的功能
和 `asciifolding` 过滤器一样, ((("icu_folding token filter")))但是它扩展到了非ASCII编码的语言,
例如:希腊语,希伯来语,汉语。它把这些语言都转换对应拉丁文字,甚至包含它们的各种各样的计数符号,象形符号和标点符号。
`icu_folding` 语汇单元过滤器(token filters)自动使用 `nfkc_cf` 模式来进行大小写折叠和Unicode归一化(normalization),
所以不需要使用 `icu_normalizer` 

【举例】

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_folder": {
          "tokenizer": "icu_tokenizer",
          "filter":  [ "icu_folding" ]
        }
      }
    }
  }
}

GET /my_index/_analyze?analyzer=my_folder
١٢٣٤٥ // 阿拉伯数字 ١٢٣٤٥ 被折叠成等价的拉丁数字: 12345.

如果你有指定的字符不想被折叠,你可以使用 UnicodeSet(像字符的正则表达式) 来指定哪些Unicode才可以被折叠。

PUT /my_index
{
  "settings": {
    "analysis": {
      "filter": {
        "swedish_folding": { 
          "type": "icu_folding",
          "unicodeSetFilter": "[^åäöÅÄÖ]" // 不处理那些大写和小写
        }
      },
      "analyzer": {
        "swedish_analyzer": {  // 分析器首先分词,然后用swedish_folding语汇单元过滤器来折叠单词,最后把他们走转换为小写,除了被排除在外的单词: Å, Ä, 或者 Ö
          "tokenizer": "icu_tokenizer",
          "filter":  [ "swedish_folding", "lowercase" ]
        }
      }
    }
  }
}

 

1.3.排序和整理

analyzed 域用来搜索, not_analyzed 域用来排序。

analyzed 域无法排序并不是因为使用了分析器,而是因为分析器将字符串拆分成了很多词汇单元,就像一个 词汇袋 ,所以 Elasticsearch 不知道使用那一个词汇单元排序。

依赖于 not_analyzed 域来排序的话不是很灵活:这仅仅允许使用原始字符串这一确定的值排序。然而 可以 使用分析器来实现另外一种排序规则,只要选择的分析器总是为每个字符串输出有且仅有一个的词汇单元

 

大小写敏感排序

【举例】

PUT /my_index
{
  "mappings": {
    "user": { // analyzed name 域用来搜索
      "properties": {
        "name": { 
          "type": "string",
          "fields": {
            "raw": {  // not_analyzed name.raw 域用来排序
              "type":  "string",
              "index": "not_analyzed"
            }
          }
        }
      }
    }
  }
}

 

[测试]

PUT /my_index/user/1
{ "name": "Boffey" }

PUT /my_index/user/2
{ "name": "BROWN" }

PUT /my_index/user/3
{ "name": "bailey" }

GET /my_index/user/_search?sort=name.raw

BROWN 、 Boffey 、 bailey

大写字母开头的字节要比小写字母开头的字节权重低,所以这些姓名是按照最低值优先排序。

 

[忽略大小写]

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "case_insensitive_sort": {
          "tokenizer": "keyword",    // 分词器将输入的字符串原封不动的输出
          "filter":  [ "lowercase" ]     // 分词过滤器将词汇单元转化为小写字母
        }
      }
    }
  }
}

[测试]

PUT /my_index/_mapping/user
{
  "properties": {
    "name": {
      "type": "string",
      "fields": {
        "lower_case_sort": { 
          "type":     "string",
          "analyzer": "case_insensitive_sort" // 大小写不敏感排序
        }
      }
    }
  }
}

PUT /my_index/user/1
{ "name": "Boffey" }

PUT /my_index/user/2
{ "name": "BROWN" }

PUT /my_index/user/3
{ "name": "bailey" }

GET /my_index/user/_search?sort=name.lower_case_sort

bailey 、 Boffey 、 BROWN

[说明]

但是这个顺序是正确的么?它符合我门的期望所以看起来像是正确的, 但我们的期望可能受到这个事实的影响:这本书是英文的,我们的例子中使用的所有字母都属于到英语字母表。

如果我们添加一个德语姓名 Böhm 会怎样呢?

现在我们的姓名会返回这样的排序: bailey 、 Boffey 、 BROWN 、 Böhm 。 Böhm 会排在 BROWN 后面的原因是这些单词依然是按照它们表现的字节值排序的。 r 所存储的字节为 0x72 ,而 ö 存储的字节值为 0xF6 ,所以 Böhm 排在最后。每个字符的字节值都是历史的意外。

显然,默认排序顺序对于除简单英语之外的任何事物都是无意义的。事实上,没有完全“正确”的排序规则。这完全取决于你使用的语言。

 

语言之间的区别

【排序】

  • 英语: bailey 、 boffey 、 böhm 、 brown
  • 德语: bailey 、 boffey 、 böhm 、 brown
  • 德语电话簿: bailey 、 böhm 、 boffey 、 brown
  • 瑞典语: baileyboffeybrownböhm

 

Unicode 归类算法

归类是将文本按预定义顺序排序的过程。 Unicode 归类算法 或称为 UCA (参见 www.unicode.org/reports/tr10 ) 定义了一种将字符串按照在归类单元表中定义的顺序排序的方法(通常称为排序规则)。

UCA 还定义了 默认 Unicode 排序规则元素表 或称为 DUCET , DUCET 为无论任何语言的所有 Unicode 字符定义了默认排序。

大多时候使用 DUCET 作为起点并且添加一些自定义规则用来处理每种语言的特性。

UCA 将字符串和排序规则作为输入,并输出二进制排序键。 将根据指定的排序规则对字符串集合进行排序转化为对其二进制排序键的简单比较。

 

Unicode 排序

参考

icu_collation 分词过滤器默认使用 DUCET 排序规则。这已经是对默认排序的改进了。想要使用 icu_collation 我们仅需要创建一个使用默认 icu_collation 过滤器的分析器

【举例】

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "ducet_sort": {
          "tokenizer": "keyword",
          "filter": [ "icu_collation" ]  // 使用默认 DUCET 归类
        }

      }
    }
  }
}

通常,想要排序的字段就是想要搜索的字段

PUT /my_index/_mapping/user
{
  "properties": {
    "name": {
      "type": "string",
      "fields": {
        "sort": {
          "type": "string",
          "analyzer": "ducet_sort"
        }
      }
    }
  }
}

使用这个映射, name.sort 域将会含有一个仅用来排序的键。

【测试】

PUT /my_index/user/_bulk
{ "index": { "_id": 1 }}
{ "name": "Boffey" }
{ "index": { "_id": 2 }}
{ "name": "BROWN" }
{ "index": { "_id": 3 }}
{ "name": "bailey" }
{ "index": { "_id": 4 }}
{ "name": "Böhm" }

GET /my_index/user/_search?sort=name.sort

bailey 、 Boffey 、 Böhm 、 BROWN

【注意】

这个排序对英语和德语来说都正确,这已经是一种进步,但是它对德语电话簿和瑞典语来说还不正确。

 

指定语言

可以为特定的语言配置使用归类表的 icu_collation 过滤器

参考 ICU 本地支持

【举例】

UT /my_index
{
  "settings": {
    "number_of_shards": 1,
    "analysis": {
      "filter": {
        "german_phonebook": {  // 为德语电话薄创建一个自定义版本的 icu_collation
          "type":     "icu_collation",
          "language": "de",
          "country":  "DE",
          "variant":  "@collation=phonebook"
        }
      },
      "analyzer": {
        "german_phonebook": {  // 包装在自定义的分析器中
          "tokenizer": "keyword",
          "filter":  [ "german_phonebook" ]
        }
      }
    }
  },
  "mappings": {
    "user": {
      "properties": {
        "name": {
          "type": "string",
          "fields": {
            "sort": { // 为name.sort 域配置它
              "type":     "string",
              "analyzer": "german_phonebook"
            }
          }
        }
      }
    }
  }
}

【测试】

PUT /my_index/user/_bulk
{ "index": { "_id": 1 }}
{ "name": "Boffey" }
{ "index": { "_id": 2 }}
{ "name": "BROWN" }
{ "index": { "_id": 3 }}
{ "name": "bailey" }
{ "index": { "_id": 4 }}
{ "name": "Böhm" }

GET /my_index/user/_search?sort=name.sort

bailey 、 Böhm 、 Boffey 、 BROWN 

在德语电话簿归类中, Böhm 等同于 Boehm ,所以排在 Boffey 前面。

 

多排序规则

每种语言都可以使用复数域来支持对同一个域进行多规则排序

【举例】

PUT /my_index/_mapping/_user
{
  "properties": {
    "name": {
      "type": "string",
      "fields": {
        "default": {
          "type":     "string",
          "analyzer": "ducet" 
        },
        "french": {
          "type":     "string",
          "analyzer": "french" 
        },
        "german": {
          "type":     "string",
          "analyzer": "german_phonebook" 
        },
        "swedish": {
          "type":     "string",
          "analyzer": "swedish" 
        }
      }
    }
  }
}

【说明】

使用这个映射,只要按照 name.french 、 name.german 或 name.swedish 域排序,就可以为法语、德语和瑞典语用户正确的排序结果了。不支持的语言可以回退到使用 name.default 域,它使用 DUCET 排序顺序。

 

自定义排序

icu_collation 分词过滤器提供很多选项,不止 language 、 country 、和 variant ,这些选项可以用于定制排序算法。可用的选项有以下作用:

  • 忽略变音符号
  • 顺序大写排先或排后,或忽略大小写
  • 考虑或忽略标点符号和空白
  • 将数字按字符串或数字值排序
  • 自定义现有归类或定义自己的归类

更多的信息可以查询 ICU plug-in documentation 和 ICU project collation documentation 。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

琴 韵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值