尽管像 BPE 这样的数据驱动型词元化是目前最常用的方法,但在某些情况下,我们希望将词元限制为完整的词,而不是子词。 例如,当我们运行针对英语的句法分析算法时,解析器可能需要以语法上的词作为输入。 或者在任何语言学应用中,如果我们对所研究的词元已有某种先验定义,这种做法也会有用。 又或者在社会科学应用中,正字法意义上的词本身就是有意义的研究单位。

在基于规则的词元化中,我们会预先定义一个标准,并实现相应的规则来执行这种词元化方式。 下面我们以英语的词元化为例进行探讨。

对于英语,我们有一些期望目标:我们通常希望将标点符号切分为独立的词元,逗号对句法分析器来说是有用的信息,句号则有助于识别句子边界。 但我们也常常希望保留词内部出现的标点符号,例如 m.p.h.Ph.D.AT&Tcap’n 中的情况。 特殊字符和数字也需要在价格(如 $45.55)和日期(如 01/02/06)中保持完整;我们不希望把价格拆分成 “45” 和 “55” 这样的独立词元。 此外,还有 URL(如 https://www.stanford.edu)、Twitter 话题标签(如 #nlproc)或电子邮件地址(如 someone@cs.colorado.edu)等结构也需要整体保留。

数字表达式带来了额外的复杂性:英语中的逗号除了出现在词边界外,还会出现在数字内部,每三位数字一组,例如 555,500.50。 不同语言的词元化规则也有所不同;例如西班牙语、法语和德语使用逗号表示小数点,而用空格(有时用句点)代替英语中千位分隔符的位置,例如 555 500,50。

基于规则的词元器还可以用于展开由撇号标记的附着词(clitic)缩略形式,例如将 what’re 转换为两个词元 what are,将 we’re 转换为 we are。 附着词是指不能独立存在、只能依附于其他词的词素。 这类缩略形式也出现在其他使用字母的文字系统中,包括法语的代词(如 j’ai)和冠词(如 l’homme)。

根据具体应用的不同,词元化算法也可能将多词表达式(如 New Yorkrock ’n’ roll)视为单个词元,这通常需要某种形式的多词表达式词典。 因此,基于规则的词元化与命名实体识别(named entity recognition,NER)任务密切相关,NER 旨在检测人名、日期和组织机构等(见第 17 章)。

一种常用的词元化标准是宾州树库词元化标准(Penn Treebank tokenization standard,PTB),该标准用于语言数据联盟(Linguistic Data Consortium, LDC)发布的已标注语料库(即“树库”),而 LDC 是许多有用数据集的来源。 该标准将附着词分开(例如 doesn’t 变为 doesn’t),保留带连字符的词整体不变,并将所有标点符号单独切分(为节省空间,此处用可见空格符号 ‘␣’ 表示词元之间的分隔,尽管实际输出中更常见的是换行符):

Input: "The San Francisco-based restaurant," they said,
       "doesn’t charge $10".

Output: "␣The␣San␣Francisco-based␣restaurant␣, "␣they␣said␣,␣
        "␣does␣n’t␣charge␣$␣10␣"␣.

在实践中,由于词元化是在任何其他语言处理步骤之前执行的,因此必须非常高效。 对于基于规则的词元化,我们通常使用基于正则表达式的确定性算法,将正则表达式编译为高效的有限状态自动机。 例如,图 2.12 展示了一个可用于英语词元化的基本正则表达式,该表达式可与基于 Python 的自然语言工具包(NLTK)中的 nltk.regexp_tokenize 函数配合使用(Bird et al., 2009;https://www.nltk.org)。

>>> text = 'That U.S.A. poster-print costs $12.40...'
>>> pattern = r'''(?x) # set flag to allow verbose regexps
... (?:[A-Z]\.)+        # abbreviations, e.g. U.S.A.
... | \w+(?:-\w+)*      # words with optional internal hyphens
... | \$?\d+(?:\.\d+)?%? # currency, percentages, e.g. $12.40, 82%
... | \.\.\.            # ellipsis
... | [][.,;"'?():_‘-]  # these are separate tokens; includes ], [
... '''
>>> nltk.regexp_tokenize(text, pattern)
['That', 'U.S.A.', 'poster-print', 'costs', '$12.40', '...']

图 2.12 在 NLTK(Bird et al., 2009)中使用正则表达式进行词元化的 Python 示例,为便于阅读添加了注释;(?x) 为“详细模式”标志,指示 Python 忽略正则表达式中的注释和空白字符。图引自 Bird et al. (2009) 第 3 章。

精心设计的确定性算法能够处理由此产生的歧义问题,例如撇号在不同上下文中的处理方式应有所不同:作为所有格标记时(如 the book’s cover)、作为引号时(如 ‘The other class’, she said),或在附着词中(如 they’re)。

2.5.1 句子切分

基于规则的切分方法也常用于另一种词元化过程:句子的切分。 句子切分(Sentence segmentation)是文本处理中一个可选但常见的步骤。 尤其在将 NLP 算法用于识别语言结构(如句法结构)等任务时,句子切分尤为重要。

句子切分的方式依赖于语言和文体。 在英语书面文本中,将文本切分为句子,最有用线索通常是标点符号,例如句号、问号和感叹号。 其中,问号和感叹号作为句子边界的标记相对明确,简单的规则即可在它们出现时完成句子切分。

然而,句号字符 “.” 则具有歧义:它既可能是句子边界的标志,也可能是缩写词(如 Dr.Inc.)的一部分。 你刚刚读过的上一句话就展示了这种歧义的一个更复杂情形——Inc. 末尾的句号同时充当了缩写标记和句子边界标记。 正因如此,句子切分与词元化通常需要联合处理。

许多英语句子切分方法首先判断一个句号究竟是属于某个词的一部分,还是句子边界的标志(这一判断通常基于确定性规则,有时也通过机器学习实现)。 一个缩写词词典有助于判断该句号是否属于常见缩写的一部分;这类词典可以人工构建,也可以通过机器学习获得(Kiss and Strunk, 2006),最终的句子切分器同样可以采用这两种方式构建。 例如,在 Stanford CoreNLP 工具包中(Manning et al., 2014),句子切分是基于规则的,并且是词元化的确定性结果:当一个句子结束标点(.、! 或 ?)未被合并到其他字符组成的词元中(例如缩写词或数字)时,该位置即被视为句子结尾;此外,该标点之后还可以选择性地跟随闭合引号或括号等符号。