绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
ElasticSearch词法分析概述
2020-06-03 13:37:44

(内部分享转外销)

上次的分享介绍了用于构造相关度的TF-IDF及其改进算法BM25 ,可以看到的这些算法都以单词为单元,计算对应的相关度,这次的分享主要介绍Elasticsearch是如何从一个大段文本中产生这样的单元的

(知乎似乎不支持目录插入?)

本文基本是ES的官方文档的翻译,掺杂了少量在使用这些组件的感悟


<!-- TOC -->

- [主要组件和处理流程](#主要组件和处理流程)
- [Tokenizer 标记器](#tokenizer-标记器)
- [Filters 过滤器](#filters-过滤器)
- [Analyzers 分析器](#analyzers-分析器)
- [Normalizers 规范器](#normalizers-规范器)
- [调试](#调试)
- [处理流](#处理流)
- [详细列表(ES 6.3)](#详细列表es-63)
- [Tokenizer](#tokenizer)
- [Standard Tokenizer](#standard-tokenizer)
- [Letter Tokenizer](#letter-tokenizer)
- [Lowercase Tokenizer](#lowercase-tokenizer)
- [Whitespace Tokenizer](#whitespace-tokenizer)
- [UAX URL Email Tokenizer](#uax-url-email-tokenizer)
- [Classic Tokenizer](#classic-tokenizer)
- [NGram Tokenizer](#ngram-tokenizer)
- [Edge NGram Tokenizer](#edge-ngram-tokenizer)
- [Keyword Tokenizer](#keyword-tokenizer)
- [Pattern Tokenizer](#pattern-tokenizer)
- [Char Group Tokenizer](#char-group-tokenizer)
- [Path Tokenizer](#path-tokenizer)
- [Filters 过滤/转换器](#filters-过滤转换器)
- [和Tokenizer的差不多的](#和tokenizer的差不多的)
- [词干转换器Stemmer](#词干转换器stemmer)
- [Keyword 和Stemmer的搭配](#keyword-和stemmer的搭配)
- [Shingle](#shingle)
- [Stop Token Filter](#stop-token-filter)
- [Phonetic 同读音过滤器](#phonetic-同读音过滤器)
- [Synonym 同义词](#synonym-同义词)
- [对齐、去重](#对齐去重)
- [正则:捕获和替换](#正则捕获和替换)
- [Normalization Filter](#normalization-filter)

<!-- /TOC -->


主要组件和处理流程

Tokenizer 标记器

接收字符串流,把其打碎成tokens (通常是单个单词),输出是tokens的流
一个简单的whitespace tokenizer,会按照空格打碎,比如把"Quick brown fox!"转换成["Quick", "brown", "fox!"]

更多的Tokenizer后面我会介绍

Filters 过滤器

分为 字符(Character)层的过滤器和Token层的过滤器,可以过滤或转换一些字符或者Token 例如可以过滤掉HTML标签<b>或者解码HTML

&amp;->&
&nbsp;-> (non-breaking space)
&lt;-> <

Analyzers 分析器

相当于是Tokenizer+Filter,内置了若干常用的分析器

例如上文说的Whitespace

Normalizers 规范器

类似于分析器,但只能产生出单个Token,而分析器是可以一对多的 另外,Normalizers会使得 文本进入引擎后,在后面的流程中得到的都是变换后的结果 举个栗子:简单的lowercase 功能是小写化

在Analyzer中
title = "Quick brown fox!"会转换成["quick", "brown", "fox!"] 并计算对应的TFIDF分数,但搜索出来的时候,显示的title依旧是 "Quick brown fox!"
如果在Title这个字段上应用的是Normalizers,那么除了索引时的转换,从索引中拿出来的对应title将会变成"quick brown fox!"

这种情况会在ES 的keyword类型(可以认为是unique或hash)的使用上产生差异

调试

可以在{host}/_analyze上调试词法分析,结合Tokenizer/Filter 以获得预期的结果

POST _analyzer
{
  "analyzer":"arabic",
  "text": "                                           "
}

响应:

{
    "tokens": [
        {
            "token": "   ",
            "start_offset": 4,
            "end_offset": 7,
            "type": "<ALPHANUM>",
            "position": 1
        },
        {
            "token": "   ",
            "start_offset": 11,
            "end_offset": 15,
            "type": "<ALPHANUM>",
            "position": 3
        }
        //略了一部分
    ]
    
}

处理流

过滤字符 -> 生成词块-> 过滤词块->进入索引
Character Filter->Tokenizer->Token Filter

同样的,在搜索阶段,如果对text类型的字段进行match查询,也会由search_analyzer控制对于query的分析,并对其进行搜索

Tokenizer + Filter... 可以等效于另一个 Tokenizer。Analyzer 也可以包含 Tokenizer + Filter

详细列表(ES 6.3)

Tokenizer

Standard Tokenizer

一个标准的标记器,使用Unicode Text Segmentation算法,从词的边界断开,过滤掉了绝大部分符号字符

POST _analyze
{
  "tokenizer": "standard",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

[ The, 2, QUICK, Brown, Foxes, jumped, over, the, lazy, dog's, bone ]

可以调节的参数:max_token_length 控制token的大长度,如果超过则从大长度处断开

Letter Tokenizer

只要不是字母,就打断

POST _analyze
{
  "tokenizer": "letter",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

[ The, QUICK, Brown, Foxes, jumped, over, the, lazy, dog, s, bone ]

Lowercase Tokenizer

和上面那个差不多,但是还会顺便小写掉
Lowercase Tokenizer = Letter tokenizer + Lowercase filter

POST _analyze
{
  "tokenizer": "lowercase",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

[ the, quick, brown, foxes, jumped, over, the, lazy, dog, s, bone ]

Whitespace Tokenizer

用空白字符(包括空格和制表符)断开

POST _analyze
{
  "tokenizer": "whitespace",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

[ The, 2, QUICK, Brown-Foxes, jumped, over, the, lazy, dog's, bone. ]

可以调节的参数:max_token_length 控制token的大长度,如果超过则从大长度处断开

UAX URL Email Tokenizer

辨识URL和Email地址,打断成对应的Token

POST _analyze
{
  "tokenizer": "uax_url_email",
  "text": "Email me at john.smith@global-international.com"
}

[ Email, me, at, john.smith@global-international.com ]

作为对比,这个文本在标准tokenizer上返回的结果是

[ Email, me, at, john.smith, global, international.com ]

这里又要注意到一点(.),上面我们看到的bone.的点是在词尾,所以被过滤掉了,但它不会过滤掉在单词中间的点

可以调节的参数:max_token_length

Classic Tokenizer

主要基于英语语法,启发式的对待缩略词、公司名、email地址、主机名 在非英语语言上表现也不一定好
主要策略

  • 在绝大多数符号字符上分隔单词,移除符号。和standard一样,点.在词的边缘的时候会被移除
dog's bone.. join.smith 

{
    "token": "dog's",
    "start_offset": 45,
    "end_offset": 50,
    "type": "<APOSTROPHE>",
    "position": 9
},
{
    "token": "bone",
    "start_offset": 51,
    "end_offset": 55,
    "type": "<ALPHANUM>",
    "position": 10
},
{
    "token": "join.smith",
    "start_offset": 58,
    "end_offset": 68,
    "type": "<HOST>",
    "position": 11
}
  • 在连字符-上做切分,除非两边是数字
{
    "token": "asd86-10086",
    "start_offset": 69,
    "end_offset": 80,
    "type": "<NUM>",
    "position": 12
}
  • 可以识别出来email地址、主机名
{
  "tokenizer": "classic",
  "text": "I@robot.com http://www.baidu.com/123"
}

[
    {
        "token": "I@robot.com",
        "start_offset": 0,
        "end_offset": 11,
        "type": "<EMAIL>",
        "position": 0
    },
    {
        "token": "http",
        "start_offset": 12,
        "end_offset": 16,
        "type": "<ALPHANUM>",
        "position": 1
    },
    {
        "token": "www.baidu.com",
        "start_offset": 19,
        "end_offset": 32,
        "type": "<HOST>",
        "position": 2
    },
    {
        "token": "123",
        "start_offset": 33,
        "end_offset": 36,
        "type": "<ALPHANUM>",
        "position": 3
    }
]

NGram Tokenizer

在字符级别做ngram 可调参数: min_gram 小长度, 默认1 max_gram 大长度, 默认2 token_chars 可以被包含在token中的字符集,默认是全部包含,可以选择

  • letter —  for example a, b, ï or 京
  • digit —  for example 3 or 7
  • whitespace —  for example " " or "\n"
  • punctuation — for example ! or "
  • symbol —  for example $ or √
POST _analyze
{
  "tokenizer": "ngram",
  "text": "Quick Fox"
}

[ Q, Qu, u, ui, i, ic, c, ck, k, "k ", " ", " F", F, Fo, o, ox, x ]

如果token_chars=letter 空格那些就没了

{
  "tokenizer": {
  	"type":"ngram",
  	"token_chars":"letter"
  },
  "text": "Quick Fox"
}
[ Q, Qu, u, ui, i, ic, c, ck, k, F, Fo, o, ox, x ]

Edge NGram Tokenizer

和上面的参数是一样的,区别是先把文本打碎成单词,然后只从单词开始的位置算ngram

{
  "tokenizer": {"type":"edge_ngram",
  	"token_chars": [
    "letter",
    "digit"
    ]
  },
  "text": "Quick Fox"
}

{
    "tokens": [
        {
            "token": "Q",
            "start_offset": 0,
            "end_offset": 1,
            "type": "word",
            "position": 0
        },
        {
            "token": "Qu",
            "start_offset": 0,
            "end_offset": 2,
            "type": "word",
            "position": 1
        },
        {
            "token": "F",
            "start_offset": 6,
            "end_offset": 7,
            "type": "word",
            "position": 2
        },
        {
            "token": "Fo",
            "start_offset": 6,
            "end_offset": 8,
            "type": "word",
            "position": 3
        }
    ]
}

可以用来做search-as-you-type 功能,不过使用自动补全会更有效率一些。我们App中的type-hint是用自动补全做的

Keyword Tokenizer

等于什么都不做,输出是什么,输出就是什么

Pattern Tokenizer

根据给定的正则去切分
simple_patternpattern,前者比后者的正则表达式约束更多,但前者也更快
simple_pattern_split 的区别是用正则表达式匹配到的字符切分,上面两个则是挑选出匹配到的字符
像是re.match和re.sub的区别

Char Group Tokenizer

根据给定的字符组去切分,比正则标记器的效率更高一些

Path Tokenizer

像文件系统一样进行切分,生成每一个路径
例如:/foo/bar/baz → [/foo, /foo/bar, /foo/bar/baz ]

Filters 过滤/转换器

非常多,ES 6.3给出了43个filter,这里只列出比较有用的和之前在搜索中已经应用过的

和Tokenizer的差不多的

基本顾名思义

  • Standard Token Filter
  • Length Token Filter
  • Lowercase Token Filter
  • Uppercase Token Filter
  • NGram Token Filter
  • Edge NGram Token Filter
  • Trim
  • Truncate
  • Limit Token Count Token Filter 限制索引文档中的Token数量
  • Classic Token Filter
  • Apostrophe Token Filter 撇号
  • Remove Duplicates Token Filter

词干转换器Stemmer

porter_stem:使用Porter stemming算法
栗子: is this shorts possibly plastered-> [is,thi,short,possibl,plaster]

stemmer: 一些基于语言的词干转换器。English等同于porter_stem
词干分析器基本只在动词上用的比较合理,在我们的电商场景里,short和shorts提取词干后都会变成short. 这个可以在商品评分和相关度评分得到一个比较好的均衡之后再次进行测试
stemmer_override: 可以手动指定映射规则

kstem: 一个用于英语的高性能过滤器。结合了算法和内置词典
"t shirts shorts jackets"-> "t shirt shorts jacket",

snowball: 基于Snowball-generated的词干过滤器,也支持多种语言。看了下snowball的文档,也是基于词法规则的


综合起来,词干转换器以前的AB测试效果不是很好,后期改善了相关度和商品评分之后可以再次进行试验。


Keyword 和Stemmer的搭配

Repeat(keyword_repeat)和Marker(keyword_marker)通常会和上面的词干转换器搭配

keyword_marker 可以保护某些单词不被词干转换器处理掉 { "type": "keyword_marker", "keywords": ["cats"] } 这个规则将会使得后面的词干处理器不处理cats

keyword_repeat 会使得被处理掉的单词再次被重复

"I like cats" -> [I, like, cats, cat]
这个在我们的搜索场景中也是比较有用,但目前还没有使用到

Shingle

是Token级别的ngram
please divide this sentence into shingles -> ["please divide", "divide this", "this sentence", "sentence into", and "into shingles"]

在我们的场景里比较有用,一个例子是tshirt/t shirt/t-shirt,ear ring/earring这样的标题,shingle会使得产生的token可以被模糊匹配上,少量改善了召回质量(目前基本被ring的商品评分过高覆盖了这个效果)
如果没有shingle,用户输入的 ear ring 就只能和ring 进行模糊匹配,匹配不上earring.

另外,由于shingle字段的查询的复杂度较高,并且ES对于这样的复杂度缺乏提前终止的保护,timeout参数也无法保护ES。通常在query的单词数量过大的时候就不再使用shingle字段了,否则会造成ES压力增大

common_grams :Common Grams Token Filtery的效果差不多,只不过是用下划线连接起来了bigram

cjk_bigram: 对CJK字符集(中日韩)的bigram

Stop Token Filter

停止词过滤器,将会过滤掉停止词

t shirts is this the-> t shirts

在ES一层使用这个停止词的优点是它会过滤掉词语,因此会使得标题、query的tf-idf分数不被停止词影响
以前也上过,这个可以在商品评分和相关度评分得到一个比较好的均衡之后再次进行测试
目前线上用的是外置的停止词过滤,缺点是搜索时依旧会被影响tf-idf分数

Phonetic 同读音过滤器

基于一些规则,在索引时对相同的读音构造出一致的索引 涉及算法:Soundex, Metaphone, and a variety of other algorithms

"t shirts shorts fulness"->"[T,XRTS,XRTS,FLNS]

Synonym 同义词

先进行同义词的对照,可以根据配置,把同义词设置到1个或多个映射上
synonym_graph和synonym,主要的困难是AWS的ES托管,不能访问ES本地路径,所以我们无法加载同义词表。另外同义词表每次的更改,需要重建整个索引,代价也较大。目前我们没有使用

对齐、去重

truncate 对齐,大于长度的token将会被裁剪到对应的尺寸
unique 去重,去掉重复的token,会优化tf-idf的表现,参数only_on_same_position 控制是否控制只对相邻的token去重

正则:捕获和替换

pattern_capture 捕获正则表达式
pattern_replace 替换

主要注意事项:要小心正则表达式中的灾难回溯

Normalization Filter

一些语言相关的normalization 主要作用是移除特殊的字符

分享好友

分享这个小栈给你的朋友们,一起进步吧。

Elasticsearch
创建时间:2020-05-22 14:49:51
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。我们建立一个网站或应用程序,并要添加搜索功能,但是想要完成搜索工作的创建是非常困难的。我们希望搜索解决方案要运行速度快,我们希望能有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON通过HTTP来索引数据,我们希望我们的搜索服务器始终可用,我们希望能够从一台开始并扩展到数百台,我们要实时搜索,我们要简单的多租户,我们希望建立一个云的解决方案。因此我们利用Elasticsearch来解决所有这些问题及可能出现的更多其它问题。
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 栈栈
    专家
戳我,来吐槽~