喂!醒醒~推演推演分词器?

喂!醒醒~推演推演分词器?

引言

Elasticsearch能够完成全文检索的功能,主要依赖于Text analysis,使得搜索不再局限于完整匹配。举个官网的栗子来说:如果你要搜索Quick fox jumps,你可能是想要得到这样的搜索结果:A quick brown fox jumps over the lazy dog。同时,包含了fast fox或者foxes leap这类相同含义的文章,分词是全文检索要解决的关键性问题。

分词的三个需求

Tokenization(记号化)

Tokenization描述的是从字符流生成记号的一个过程,因此Tokenization又被称为记号化过程。通俗的来说,是将文章拆分成更小的块,大部分是独立的词语,进行匹配,这个块被我们称为Token记号

举个栗子来说,如果我们匹配the quick brown fox jumpsqucik fox,是匹配不上的,但实际上,这两个短语的相关性是很高的,我们在搜索quick fox的时候,是期望the quick brown fox jumps也可以返回的。因此,我们就有了对索引文档进行分词的需求。

the quick brown fox jumps可以被分成the,quick,brown,fox,jumps;同时,分词或者记号化也要对查询的输入进行分词,例如quick fox被拆分成 quick fox,这样在全文进行检索的时候,我们才能进行匹配,当然,如果你不愿意进行拆分匹配的情况,实际上可以根据字段本身的类型和查询的方式来区分需不需要拆分匹配。

Normalization(标准化)

记号化赋予了独立词语匹配的能力,但是仍然是停留在字面上的匹配。很容易发现上面只做记号化的问题。例如:

  • Quickquick并不能匹配上,即使你我都知道这只是大小写的区别
  • foxfoxes他们有着一样的词根,但是记号化的处理无法让这样两个词进行匹配
  • jumpleap都能表达跳跃的意思,那么fox jumpfox leap是不是一个意思呢?

为了解决这些问题,文本分词器急需解决的需求就是,将这些记号化的词块,用统一的格式进行Normalization(标准化)。对于大小写的统一,词根词语的统一,同义词的统一。同样,这样的策略依然适用在文本和搜索的输入。

Customize Text analysis

如我们上面聊的那般,文本分析依赖于分词器,任何一个文本分析,全文检索可以设定一系列的规则来处理文本。例如Elasticsearch给了一系列的standard analyzer来预置处理。无论是汉语、德语等语言差别,甚至也有网络语言,还是不同的业务场景,不同的文本分析重点,都会让我们对文本分词有自己的定义,所以需要开放一个接口,来让我们自定义不同的分词器,例如熟知的IK中文分词器。对于接口的实现,我们需要实现哪些功能呢?

  • 在记号化之前进行改变文本
  • 定义如何记号化。即把文本拆分转换成小块。
  • 在建立索引或者搜索之前进行标准化处理。

分词器结构

上面聊了分词器需要解决的三个需求,也正是分词器设计的思路的出发点,让我们探究一下es的分词器是如何设计的。无论是预置的,还是标准的都包含三个基础模块,Character filters,Tokenizers,Token filters。并且三部分可以组合、开放自定义出新的分词器。

Character filter 字符过滤器

字符过滤器接收的是原始的文本字符流,在这一步可以对字符流进行添加删除修改操作。这意味着,我们可以对字符进行任意操作,例如把阿拉伯数字转换成汉语数字,对HTML这样特殊的文本进行任意处理,字符过滤器在分词器里面是一个集合,也就是意味着我们可以添加N个字符过滤器。

Tokenizer 分词器

正如前面聊到的那样,分词器接收的也是字符流,在这里经由字符过滤器进行了文本的处理,会通过分词器根据分词规则进行分词,例如根据空格,根据正则进行拆分,这是Tokenizer的第一个功能。

同时,分词器还负责记录每一个拆分出来的词语在文本中的索引下标和偏移量,这是分词器的第二个功能。与字符过滤器不同的是,在一个文本分析中,只允许有且只有一个分词器。

Token Filter 词项过滤器

有了字符过滤器,对文本进行处理,分词器将文本拆分成了更小的词项。此时我们还要解决的需求是标准化。而词项过滤器就是做这个的,它接收的是进过了tokenizer分词器拆分的词项数据流,同字符过滤器一样,它支持新增、删除、修改的操作。例如之前提到的Quickquick可以使用lowercase的词项过滤器完成。

在这里,我们需要注意一点的是,在分词器的时候,记录了各个词项的位置,那么在词项过滤Token Filter中不允许改变偏移量。一个文本分析中允许多个词项过滤器,依次进行处理。

词项搜索的原理(Token Graphs)

我们知道,当一个分词器tokenizer把一个文本转换成词项流,她同时记录了两个信息。一个是每一个词项在词项流中的下标position;一个是词项指向节点之间的长度postionLength,听到这里你可能比较懵,让我们根据实际的例子来看看。

例如quick brown fox在经历了tokenizer处理之后,可以构成一个词项图,如下所示

image-20220811153905559.png

每一个节点表示,token在流中的下标位置,节点之间的边表示每个词项,指向下一个下标。

在Token Filter中我们了解到,词项过滤器可以对词项做同义词替代,或者大小写转换,但是不能更改词项的下标,那么怎么实现的呢?

image-20220811154147543.png

如图展示的是使用了同义词过滤器,让quick和fast有0节点指向1节点,他们能够从相同的起点,到终点。

以此类推,例如domain name system is fragile这样的一个短语,在词项图中可以这么表示。

image-20220811154518042.png

对于domain name system 的同义词,我们可以通过dns来表示,那么dns的同义词,我们替代了三个节点,这怎么表示呢?

image-20220811154648463.png 此时dns这条边就由节点0指向了节点3,在我们面提到分词器她记录的另外一个节点之间距离的positionLength就为3.

总结

我们介绍了全文检索中的分词原理,从需求推演到了实现,从实现看到了架构,同时了解了token graphs的原理,是如何进行设计的。实际上,关于token graph还有很多需要探索的地方,例如positionLength的作用是什么?Token Graph会失效么?

分类:
后端