小白学 ElasticSerach(二):原理介绍篇(中)

写在前面:

在我阅读学习其它资料的过程,发现Analyzer和Tokenizer有时候同时会被翻译成"分词器",可能会让像我一样的小白读者将两者混淆。因此在这篇笔记里面,在容易混淆的地方直接用的英文名词。

通常说"ES分词器"指的其实是Analyzer,"分析器"也是指的Analyzer,但Tokenizer(中文翻译过来也是分词器)是Analyzer组成不可缺少的部分。

一、Analyzer基础概念

分词

分词是指将文本转换成一系列单词( term or token )的过程,也可以叫做文本分析,在es里面称为Analysis ,如下图所示:

Analyzer与ES

Analyzer是ES中专门处理分词的组件,称为"分词器"或者"分析器" ,ES建立索引和查询过程都离不开它。

  • 建立倒排索引:将文档通过分词器分成词条(term),每一个Term都指向包含这个Term的文档集合。 name字段的倒排索引
  • 查询:ES会根据搜索类型决定是否对query进行分词,然后和倒排索引中的term进行相关性查询,匹配相应的文档。

当搜索"小米手机" 的时候,小米手机会被分词为"小米"和"手机",去name字段对应的倒排索引里面查询,通过"小米"得到文档id集合:[1,2,3],通过"手机"得到文档id集合:[3,4],id为3的文档同时命中"小米"和"手机",相关性最高,会被优先展示。 默认情况下,相关性越大,分数越高,会被优先展示。理想状态下,我们想要的当然是同时匹配"小米"和"手机"的结果。但是如果因为想把华为产品进行优先展示,可以人为调整分数,把包含"华为"的文档进行优先展示。比较典型的是,以前一些搜索引擎在搜索医院和疾病的时候,会对搜索结果进行分数调控,优先把莆田系医院放在搜索结果的上面。

Analyzer的组成

这三个部分是有先后顺序的,过程比较好理解,先对原始文本处理,然后进行切分,最后对切分的单词进行再处理。

如果我们自己想要自定义一个分词器,也是由这三部分组成,数量如下:

Analyzer = Charater Filters(0个或多个) + Tokenizer(有且仅有一个) + Token Filters(0个或多个)

二、Charater Filters

HTML Strip Character Filter

  • 从输入中去除HTML元素,只保留文本内容,比如<br></br>,<p></p>等标签,<p>Hello World</p> 被处理为 "Hello World"。

  • HTML中预定义字符的替换,比如&apos替换为单引号'

HTML中预定义字符对照可参考:www.madore.org/\\~david/co...

示例:

Markdown 复制代码
GET /_analyze
{
  "tokenizer": "keyword",
  "char_filter": [
    "html_strip"
  ],
  "text": "<p>I'm so <b>happy</b>!</p>"
}

处理之后:

Markdown 复制代码
I'm so happy!

可配置项:

  • escaped_tags:可选,字符串数组,不处理包含尖括号 ( < >) 的 HTML 元素数组。从文本中剥离 HTML 时,过滤器会跳过这些 HTML 元素。例如,"escaped_tags": ["p"] 会跳过<p></p>标签,不会删除这对标签。

Mapping Character Filter

设置预定义的映射关系,将指定的字符或字符串替换为其他字符或字符串。例如,可以定义一个规则将 "&" 替换为 "和",将 "||" 替换为 "或者"。

Markdown 复制代码
GET /_analyze
{
  "tokenizer": "keyword",
  "char_filter": [
    {
      "type":"mapping",
      "mappings": [
        "& => 和",
        "|| => 或者"
      ]
    }
  ],
  "text": "午餐吃面条||米饭,菜要牛肉&青菜"
}

处理之后:

Markdown 复制代码
午餐吃面条或者米饭,菜要牛肉和青菜

Pattern Replace Character Filter

使用正则表达式匹配字符,并将匹配的内容替换为指定的字符串。这对于处理有复杂需求的文本非常有用。

参数:

  • pattern:一个Java正则表达式. 必填。

  • replacement:替换字符串,可以使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1.. 1 .. </math>1.. 9语法来引用捕获组,可以参考这里

  • flags:Java正则表达式标志。标志应该用"|"进行分割,例如"CASE_INSENSITIVE | COMMENTS"。

示例:

Markdown 复制代码
PUT my-index-000001
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "standard",
          "char_filter": [
            "my_char_filter"
          ]
        }
      },
      "char_filter": {
        "my_char_filter": {
          "type": "pattern_replace",
          "pattern": "(\\d+)-(?=\\d)",
          "replacement": "$1_"
        }
      }
    }
  }
}

POST my-index-000001/_analyze
{
  "analyzer": "my_analyzer",
  "text": "My credit card is 123-456-789"
}

处理之后;

Markdown 复制代码
[ My, credit, card, is, 123_456_789 ]

三、Tokenizer

单词分词器(Word Oriented Tokenizers)

1.1 Standard Tokenizer

standard tokenizer 按照 Unicode 文本分段算法的定义,将文本划分为字边界上的术语。它删除了大多数标点符号,是大多数语言的最佳选择。

可选参数:

  • max_token_length:单个 token 的最大长度。如果一个 token 超过这个长度,则以max_token_length 为间隔分割。默认值是255.

示例:

Markdown 复制代码
PUT my-index-000001
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "my_tokenizer"
        }
      },
      "tokenizer": {
        "my_tokenizer": {
          "type": "standard",
          "max_token_length": 5
        }
      }
    }
  }
}

POST my-index-000001/_analyze
{
  "analyzer": "my_analyzer",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

处理之后:

Markdown 复制代码
[ The, 2, QUICK, Brown, Foxes, jumpe, d, over, the, lazy, dog's, bone ]

1.2 Letter Tokenizer

Letter Tokenizer每当遇到非字母的字符时,分词器就会将文本划分为词条 。

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

上面的句子会生成如下的词元:

Markdown 复制代码
[ The, QUICK, Brown, Foxes, jumped, over, the, lazy, dog, s, bone ]

1.3 Lowercase Tokenizer

类似于 letter tokenizer,遇到非字母时分割文本,并将所有分割后的词元转为小写。功能上等同于 letter tokenizer + lowercase token filter,但是由于单次执行了所有步骤,所以效率更高。

1.4 whitespace Tokenizer

类似 standard tokenizer,每当遇到 whitespace 字符时将文本分解成词条。

Markdown 复制代码
POST _analyze
{
  "analyzer": "whitespace",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

处理之后:

Markdown 复制代码
[ The, 2, QUICK, Brown-Foxes, jumped, over, the, lazy, dog's, bone. ]

1.5 UAX URL Email Tokenizer

和Standard Tokenizer类似,只不过它会把 URL和 email 地址当成一个词元,可选参数也一样是max_token_length。

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

分解结果:

Markdown 复制代码
[ Email, me, at, john.smith@global-international.com ]

而 standard tokenizer 会生成:

Markdown 复制代码
[ Email, me, at, john.smith, global, international.com ]

1.6 Classic Tokenizer

Classic Tokenizer是一种基于语法的标记器,适用于英文文档。 特殊处理首字母缩略词、公司名称、电子邮件地址和互联网主机名。 但是,这些规则并不总是起作用,而且除了英文之外,大多数语言中并不能正常工作。

1.7 Thai Tokenizer

将泰文文本分成单词,使用的是 java 的泰语分割算法。文本中的其他语言按照standard tokenizer 处理。

部分词分词器(Partial Word Tokenizers)

Partial Word Tokenizers将文本或单词分解成小片段,以进行部分单词匹配。主要有两个。

2.1 N-gram tokenizer

N-gram tokenizer遇到指定的字符(如 : 空白、标点)时分割文本,然后返回指定长度的每个单词的 N-grams

N-grams 就像一个滑动窗口在单词上移动,是一个连续的指定长度的字符序列。 通常用于查询不使用空格或具有较长复合词(如德语)的语言。

使用默认设置,ngram tokenizer将初始文本视为单个词元,并生成最小长度为1且最大长度为2的 N-gram:

Markdown 复制代码
POST _analyze
{
  "tokenizer": "ngram",
  "text": "Quick Fox"
}

分词结果:

Markdown 复制代码
[ Q, Qu, u, ui, i, ic, c, ck, k, "k ", " ", " F", F, Fo, o, ox, x ]

可选参数:

  • min_gram:以 gram 为单位的最小字符长度。 默认为1。

  • max_gram:以 gram 为单位的最大字符长度。 默认为2。

  • token_chars:应包含在词元中的字符类。 Elasticsearch将分割不属于指定类的字符。 默认为[](保留所有字符)。字符类可能是以下任何一种:(1)单词:例如a,b等;(2)数字 :例如3或17;(3)空格 : 例如" "或换行符 (4)标点符号: 例如"!",","等 (5)一些特殊符号:例如$或√

2.2 Edge NGram Tokenizer

首先将文本分解为单词,然后发出每个单词的N 元语法,其中 N 元语法的开头锚定到单词的开头。

具体可以参考;www.elastic.co/guide/en/el...

结构化分词器(Structured Text Tokenizers)

这里介绍三种常见的结构化分词器

3.1 Keyword Tokenizer

Keyword Tokenizer将整个输入字符串作为单个词条返回,适用于如小写电子邮件地址等不想分割的地方。

Markdown 复制代码
POST _analyze
{
  "tokenizer": "keyword",
  "text": "New York"
}

处理之后:

Markdown 复制代码
[ New York ]

3.2 Pattern Tokenizer

使用正则表达式分割文本。遇到单词分隔符将文本分割为词元, 或者将捕获到匹配的文本作为词元。默认的匹配模式时 \W+ ,遇到非单词的字符时分割文本。

3.3Path Tokenizer

path_hierarchy tokenizer 把分层的值看成是文件路径,用路径分隔符分割文本,输出树上的各个节点。

Markdown 复制代码
POST _analyze
{
  "tokenizer": "path_hierarchy",
  "text": "/one/two/three"
}

处理之后:

Markdown 复制代码
[ /one, /one/two, /one/two/three ]

Analyzer的第三部分是Token Filters,可以翻译成词元过滤器,它接受来自 Tokenizer(分词器) 的 tokens 流,并且可以修改 tokens(例如小写转换),删除 tokens(例如,删除 stopwords停用词)或添加 tokens(例如同义词)。

Elasticsearch 提供了很多内置的词元过滤器,可以用于构建自定义分词器。

在第四部分的1.4,讲了Stop词元过滤器的配置与使用。

四、Analyzer

ES自带分词器

1.1 Standard Analyzer

这里对"The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."进行分词,其它Analyzer也会使用这个例子分词,从而比较它们的异同。

在上文提到了Analyzer 的组成: Charater Filters(0个或多个) + Tokenizer(有且仅有一个) + Token Filters(0个或多个),并且它们的处理顺序是:Charater Filters------> Tokenizer------> Token Filters。下面给出Standard Analyzer的组成部分,并且给出每一部分处理之后的结果(其它Analyzer也是这样)。

Standard Analyzer是默认的分词器,特性为:

  • 按词切分,基于Unicode文本分割算法,支持多语言。对于中文支持不太好,汉字会被处理按照每个字进行拆分。

  • 大写的字母会被转为小写

1.2 Simple Analyzer

特性为:

  • 按照非字母切分,数字和符号会被过滤掉

  • 小写处理

代码示例:

Markdown 复制代码
POST _analyze
{
  "analyzer": "simple",
  "text":     "Happy NewYear%春节快 乐"
}

根据空格和%切分,Happy和NewYear里面的大写字母会被转为小写:

Markdown 复制代码
[happy,newyear,春节快,乐]

1.3 Whitespace Analyzer

按照空格切分,不过滤内容

Markdown 复制代码
POST _analyze
{
  "analyzer": "whitespace",
  "text":     "Happy NewYear%春节快 乐"
}

处理结果:

Markdown 复制代码
[Happy,NewYear%春节快,乐]

1.4 Stop Analyzer

相比 Simple Analyzer多了Stop Word处理。

Stop Word指语气助词等修饰性的词语,比如the、an等等。

扩展:Stop token filter

Stop token filter在没有自定义的时候,过滤器默认删除以下英文停用词:

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 token filter支持自定义词语,从而让过滤器删除它们,下面举个🌰

(1)设置Stop token filter的过滤词为:[ "there","are","two","and"],同时在过滤的时候忽略大小写。

Markdown 复制代码
PUT /my-index-02
{
  "settings": {
    "analysis": {
      "analyzer": {
        "default": {
          "tokenizer": "whitespace",
          "filter": [ "my_custom_stop_words_filter" ]
        }
      },
      "filter": {
        "my_custom_stop_words_filter": {
          "type": "stop",
          "ignore_case": true,
          "stopwords": [ "there","are","two","and"]
        }
      }
    }
  }
}

(2)对"There are two dogs and a cat"用空格进行分词,用自定义的词语过滤

Markdown 复制代码
POST my-index-02/_analyze
{
  "analyzer": "default",
  "text": "There are two dogs and a cat"
}

处理之后的结果:

Markdown 复制代码
[dogs, a, cat]

1.5 Keyword Analyzer

不分词,直接将输入作为一个单词输出,所以这也是为什么keyword 用于精准匹配。

1.6 Pattern Analyzer

特性为:

通过正则表达式自定义分割符

默认是\W+ ,即非字词的符号作为分隔符

自定义分词器

在前面,我们提到了 Analyzer由三部分组成,Analyzer = Charater Filters(0个或多个) + Tokenizer(有且仅有一个) + Token Filters(0个或多个),在一些场景,我们想要实现实现业务特定功能。

下面举个🌰

对于"I'm a :) <b>person</b>, and you?",我们想把输入的英文都转为小写,且过滤掉里面的HTML标签,并且想把 :) 映射为 happy,:( 映射为 sad

处理过程如下:

Markdown 复制代码
PUT my-index-000002
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_custom_analyzer": { 
          "char_filter": [
            "html_strip",
            "emoticons"
          ],
          "tokenizer": "standard",
          "filter": [
            "lowercase"
          ]
        }
      },
      "char_filter": {
        "emoticons": { 
          "type": "mapping",
          "mappings": [
            ":) => _happy_",
            ":( => _sad_"
          ]
        }
      }
    }
  }
}
Markdown 复制代码
POST my-index-000002/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text": "I'm a :) <b>person</b>, and you?"
}

分词结果:

Markdown 复制代码
["i'm","a","_happy_","person","and","you"]

五.中文分词器

中文分词指的是将汉字的序列切分成单独的词。在英文中,单词之间是以空格作为自然分界符,但汉语中词没有一个形式上的分界符。

IK

可自定义词库,支持热更新分词词典 网址:github.com/medcl/elast...

1.1 IK的两种模式

实现中英文单词的切分

ik分词器包含两种模式:

  • ik_smart:最少切分,粗粒度
Markdown 复制代码
GET /_analyze
{
  "text":"中国人民",
  "analyzer":"ik_smart"
}

分词结果:

Markdown 复制代码
[中国人民]
  • ik_max_word:最细切分,细粒度
Markdown 复制代码
GET /_analyze
{
  "text":"中国人民",
  "analyzer":"ik_max_word"
}

分词结果:

Markdown 复制代码
[中国人民,中国人,中国,国人,人民]

1.2 实践与踩坑:IK的拓展词典和停用词典

设想我们常见的一种场景,一些敏感词我们想把它屏蔽掉,一些网络新兴词我们想把它加入分词的词条行列。

1.原始模式下,我们没做额外配置,但是假如我们想把"Happy新春"作为一个词,并且不需要"新春"这个词条。

Markdown 复制代码
GET /_analyze
{
  "text":"Happy新春快乐",
  "analyzer":"ik_max_word"
}

没做额外配置的分词结果如下:

Markdown 复制代码
[happy,春节快乐,春节,快乐]

2.ik在es_plugins下面,路径是:

es-plugins\_data\ik\config

这个文件夹下有很多配置文件。

ik配置文件描述

  • IKAnalyzer.cfg.xml:IK分词配置文件。
  • main.dic:主词库。
  • stopword.dic:英文停用词,不会建立在倒排索引中。
  • quantifier.dic:特殊词库:计量单位等。
  • suffix.dic:特殊词库:行政单位。
  • surname.dic:特殊词库:百家姓。
  • preposition:特殊词库:语气词。

3.拓展

要拓展ik分词器的词库,在名为ext.dic的文件中,添加想要拓展的词语即可:

4.禁用

要禁用某些敏感词条,只需要修改一个ik分词器目录中的config目录中的IkAnalyzer.cfg.xml文件

然后在名为stopword.dic的文件中,添加想要禁用的词语:

4.生效

接下来只需要修改分词器目录中的config目录中的IkAnalyzer.cfg.xml文件:

然后在生效之后,我们输入原来的语句,可以发现结果是一开始我们想要的

Markdown 复制代码
GET /_analyze
{
  "text":"Happy新春快乐",
  "analyzer":"ik_max_word"
}

这就是我们想要的结果了

分词结果:

Markdown 复制代码
[happy新春,happy,新春快乐,快乐]

踩坑实录(自己的修改一开始没有生效,在对下面两项进行修改才真正生效):

  • 注意ext.dic,stop.dic的编码方式是UTF-8编码
  • 重启es

1.3 IK原理浅析

IK的设计包含了统计学,多种算法的知识,这里从浅层概括一下主要内容:

1.词典

词典是IK的核心部分,包括了主词典、停用词词典、姓氏词典等。Dictionary字典管理类加载它们到内存结构。

词典结构使用了Tire Tree(字典树),它是一种结构相当简单的树型结构,用于构建词典,通过前缀字符逐一比较来快速查找词,所以有时也称为前缀树。这里举个简单的例子:

中国,中国人,蓝天,蓝色这些词就是存在树中的单词。

2.算法:

  • 在ik_max_word模式下,IK分词器使用一种正向最大匹配法(FMM),从文本的最左侧开始匹配最长的词语,直到无法匹配为止,然后再从剩余文本中继续执行相同的过程。

  • 在ik_smart模式下,IK分词器借鉴Viterbi算法,这是一种动态规划算法,用于寻找最有可能的词组合,以此优化分词结果。

3.优化

为了提高处理速度,Ik做了一些优化。例如:

  • 懒加载:在不影响启动速度的前提下,按需加载词典,减少系统启动时的资源消耗。

  • 缓存:缓存热点数据,如一些常用词汇,避免重复分词带来的性能损耗。

  • 多线程提高兵发能力:对于大规模文本,支持多线程并发处理,提高分词速度。

2.jieba

python中最流行的分词系统,支持分词和词性标注 支持繁体分词、自定义词典、并行分词等 github.com/sing1ee/ela...

拓展:可根据上下文内容进行分词,基于自然语言处理的分词系统 Hanlp:

  • 由一系列模型与算法组成的Java工具包,目标是普及自然语言处理在生产环境中 的应用
  • github.com/hankcs/HanL...

THULAC:

  • THU Lexical Analyzer for Chinese ,由清华大学自然语言处理与社会人文计算 实验室研制推出的一套中文词法分析工具包,具有中文分词和词性标注功能
  • github.com/microbun/el...

感谢阅读,如果本篇文章有任何错误和建议,欢迎给我留言指正,感谢🙏

参考:

相关推荐
Biehmltym3 小时前
【AI】09AI Agent LLM → Streaming → Session 记录 的完整链路
大数据·人工智能·elasticsearch
小湘西4 小时前
Elasticsearch 的一些默认配置上下限
java·大数据·elasticsearch
Dxy12393102167 小时前
Elasticsearch 8如何做好标题搜索
大数据·elasticsearch
斯普信云原生组8 小时前
Elasticsearch(ES) 内存 CPU 过高问题排查报告
大数据·elasticsearch·搜索引擎
弘毅 失败的 mian8 小时前
Git 分支管理
大数据·经验分享·笔记·git·elasticsearch
阿坤带你走近大数据9 小时前
Elasticsearch(ES)的基本概念、架构及基本使用介绍
大数据·elasticsearch
Elastic 中国社区官方博客9 小时前
使用 Elasticsearch 中的结构化输出创建可靠的 agents
大数据·人工智能·elk·elasticsearch·搜索引擎·ai·全文检索
G皮T10 小时前
【Elasticsearch】查询性能调优(六):track_total_hits 影响返回结果的相关性排序吗
大数据·数据库·elasticsearch·搜索引擎·全文检索·性能·opensearch
imbackneverdie11 小时前
AI赋能下的下一代检索工具:DeepSearch与传统数据库/搜索引擎有何本质不同?
人工智能·搜索引擎·ai·自然语言处理·aigc·ai写作·ai工具
LCG米11 小时前
嵌入式Linux系统构建:为STM32MP157移植Buildroot并开发温湿度采集驱动
linux·stm32·elasticsearch