logo

从文本到词元:分词管道的工作原理

Published on

目录

引言

当你在搜索框中输入一句话时,很容易想象搜索引擎看到的和你看到的是一样的。但实际上,搜索引擎(或搜索数据库)并不存储文本块,也不存储句子。它们甚至不以我们理解的方式存储单词。它们会拆解输入文本(无论是索引内容还是查询内容),清洗干净,然后重新组装成更抽象但更有用的东西:词元(tokens)。这些词元就是你用来搜索的内容,也是存储在倒排索引中供搜索的内容。

让我们放慢脚步,观察这个管道的实际运作,在每个阶段暂停,看看语言是如何被拆解和重组的,以及这如何影响搜索结果。

我们将使用"The quick brown fox jumps over the lazy dog"的变体作为测试用例:

The full-text database jumped over the lazy café dog

这个句子包含了分词过程中所有有趣的元素:大小写、标点符号、重音符号,以及在管道中会发生变化的词汇。最终,它看起来会有所不同,但已经完美地为搜索做好了准备。

注意: 这不是一个完整的管道,只是对词法搜索系统中常见过滤器的概览。不同的数据库和搜索引擎将这些过滤器作为可组合的构建块暴露出来,你可以根据需要启用、禁用或重新排序。

无论你使用的是 Lucene/ElasticsearchTantivy/ParadeDB,还是 PostgreSQL 全文搜索,这些基本原理都是通用的。

使用大小写和字符折叠过滤文本

在我们考虑拆分文本之前,需要先过滤掉任何无用的内容。这通常意味着审查构成文本字符串的字符:将所有字母转换为小写,如果我们知道可能存在变音符号(如 résumé、façade 或 Noël 中的符号),则将其折叠为基本字母。

这一步确保字符在分词开始之前被规范化和统一。Café 变成 caferésumé 变成 resume,这样无论是否带有重音符号,搜索都能匹配。小写化确保 database 能匹配 Database,尽管这可能会引入一些问题:比如将 Olive(人名)与 olive(橄榄)匹配。大多数系统接受这种权衡:误报比漏掉结果要好。代码搜索是一个显著的例外,因为它通常需要保留符号并尊重大小写,如 camelCasePascalCase

让我们看看输入字符串是如何转换的:

输入:

The full-text database jumped over the lazy café dog

↓ 小写化 & 折叠变音符号

输出:

the full-text database jumped over the lazy cafe dog

我们将大写的 T 替换为小写的 t,并将 é 折叠为 e。这里没有什么令人惊讶的地方。

当然,这里还可以应用更多的过滤器,但为了简洁起见,让我们继续。

使用分词将文本拆分为可搜索的片段

分词阶段将我们过滤后的文本拆分成可索引的单元。这是我们从将句子作为单一单元处理,转变为将其视为离散的、可搜索的部分(称为词元)的集合的地方。

对于英文文本,最常见的方法是简单的空格和标点分词:按空格和标点符号拆分,就得到了词元。但即使是这个基本步骤也有细微差别:制表符、换行符或像 full-text 这样的连字符词都可能有不同的行为。每个系统都有其特点,默认的 Lucene 分词器将 it's 转换为 [it's],而 Tantivy 则拆分为 [it, s]1

一般来说,分词器分为三类:

  1. 面向单词的分词器 在词边界处将文本拆分成单个单词。这包括简单的空格分词器(按空格拆分)以及更复杂的语言感知分词器(能理解非英语字符集)2。这些分词器适用于大多数需要匹配完整单词的搜索应用。

  2. 部分单词分词器 将单词拆分成更小的片段,适用于匹配单词的一部分或处理复合词。N-gram 分词器创建重叠的字符序列,而边缘 n-gram 分词器专注于前缀或后缀。这些对于自动完成功能和模糊匹配非常强大,但可能会在搜索结果中产生噪音。

  3. 结构化文本分词器 专为特定数据格式设计,如 URL、电子邮件地址、文件路径或结构化数据。它们保留有意义的分隔符并处理领域特定的模式,这些模式会被通用分词器破坏。当你的内容包含需要特殊处理的非散文文本时,这些分词器至关重要。

输入:

the full-text database jumped over the lazy cafe dog

↓ 按空格和标点符号拆分

输出(使用空格分词器):

| the | full | text | database | jumped | over | the | lazy | cafe | dog |

10 个词元

如果使用 Trigram 分词器(长度为 3 的 n-gram),输出会非常不同:

| the | he  | ful | ull | ll- | l-t | -te | tex | ext | ... |

这会产生更多的词元,适用于模糊搜索场景。

使用停用词过滤器去除填充词

有些词几乎没有权重。它们无处不在,稀释了语义:"the"、"and"、"of"、"are"。这些就是停用词。搜索引擎通常会完全丢弃它们3,押注剩下的内容会携带更多信号。

这并非没有风险。在 The Who(乐队名)中,"the" 是有意义的。这就是为什么停用词列表通常是可配置的4,而不是通用的。在支持 BM25 的系统中,停用词通常被完全省略,因为排名公式会给非常常见的词较低的权重。但在不支持 BM25 的系统中(如 PostgreSQL 的 tsvector),停用词过滤就至关重要了。

输入词元:

| the | full | text | database | jumped | over | the | lazy | cafe | dog |

↓ 移除停用词

输出词元:

| full | text | database | jumped | over | lazy | cafe | dog |

8 个词元

注意到移除停用词后,我们的词元列表立即变得更加集中了吗?我们从十个词元减少到了八个,剩下的内容承载了更多的语义权重。

使用词干提取还原到词根

Jumpjumpsjumpedjumping。人类一眼就能看出它们的联系。但计算机不行,除非我们给它们一种方法5

这就是词干提取的用武之地。词干提取器是一个基于规则的机器,它将单词削减到一个共同的核心。有时这个过程很优雅,有时则很粗暴。大多数现代英语词干提取的基础来自 Martin Porter 1980 年的算法,该算法定义了在尊重词汇结构的同时剥离后缀的方法。今天,许多词干提取器都基于 Snowball 变体。

结果可能看起来很奇怪。Database 变成 databaslazy 变成 lazi。但这没关系,因为词干提取器不在乎美观,它们在乎的是一致性。如果 lazy 的每种形式都折叠为 lazi,搜索引擎就可以将它们视为同一个词6

还有词形还原(lemmatization),它使用语言学知识将单词转换为其字典形式,但它比词干提取的"足够好"方法更复杂,计算成本也更高7

输入词元:

| full | text | database | jumped | over | lazy | cafe | dog |

↓ Porter 词干提取

输出词元:

| full | text | databas | jump | over | lazi | cafe | dog |

8 个词元

这是最终的转换:我们的词元已经被还原为它们的基本词干。Jumped 变成 jumplazy 变成 lazidatabase 变成 databas。这些词干可能看起来不像真正的单词,但它们有一个关键作用:它们是一致的。无论有人搜索 jumpingjumped 还是 jumps,它们都会还原为 jump 并匹配我们索引的内容。这就是词干提取的力量:弥合人类表达同一概念的多种方式之间的差距。

最终词元

我们的句子已经穿越了完整的管道。最初的 "The full-text database jumped over the lazy café dog" 经过每个阶段的转换:去除标点符号和大写字母,拆分成单个单词,过滤掉常见的停用词,最后还原为词干。

结果是一组干净的八个词元:

| full | text | databas | jump | over | lazi | cafe | dog |

这种转换会应用于我们存储在倒排索引中的所有数据,也会应用于我们的查询。当有人搜索 "databases are jumping" 时,该查询会被分词:小写化、拆分、移除停用词、词干提取。它变成 databasjump,这将与我们索引的内容完美匹配。

为什么分词很重要

分词不会获得荣耀。没有人在会议上吹嘘他们的停用词过滤器。但它是搜索的静默引擎。没有它,dogs 不会匹配 dogjumping 也找不到 jump

每个搜索引擎都在这里投入大量资源,因为其他一切(评分、排名、相关性)都依赖于正确处理词元。这不是光鲜的工作,但它是精确的。当你把这部分做对了,搜索中的其他一切都会运转得更好。


脚注

Footnotes

  1. 哪个更好?这取决于具体情况:it's 看起来更正确,并且避免了存储一个无用的 s 词元,但它不会匹配对 it 的搜索。

  2. 通用的形态学库,如 JiebaLindera,通常用于提供能够处理中文、韩文、日文等字符的分词器。

  3. 当我们移除停用词时,仍然保留文档中每个词元的原始位置。这允许位置查询("在 cat 五个词范围内找到 dog"),即使我们已经丢弃了一些词。

  4. Lucene 和 Tantivy 默认都关闭停用词,当为英语启用时,它们使用相同的默认列表:[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]

  5. 另一种方法是向量搜索,它用语义搜索取代了词法词干提取。

  6. 单词也可能被过度词干提取,例如 universityuniverse 都词干提取为 univers,但它们有着非常不同的含义。

  7. 词形还原使用实际的词汇表来确保它只生成真实的单词。为了准确地做到这一点,它需要知道源词的"词性"(名词、动词等)。