原文:Speech and Language Processing (3rd ed. draft)
当前译文基于 Draft of August 24, 2025。
译文仅供学习参考,请勿转载或用于商业用途。
原书作者:
- Daniel Jurafsky,哈佛大学
- James H. Martin,科罗拉多大学博尔德分校
原文:Speech and Language Processing (3rd ed. draft)
当前译文基于 Draft of August 24, 2025。
译文仅供学习参考,请勿转载或用于商业用途。
原书作者:
对于英文文本,我们可以通过一条简单的 Unix 命令行完成朴素的单词词元化(tokenization)和词频统计。 正如 Church(1994)所指出的,当我们需要快速获取某个文本语料库的基本信息时,这种方法非常实用。 我们将用到几个 Unix 命令:tr 用于系统性地将输入中的特定字符进行替换;sort 按字母顺序对输入行进行排序;uniq 合并相邻的重复行,并对这些行计数。 例如,我们从莎士比亚全部词语开始,存储在一个文件 sh.txt 中。 我们可以使用 tr 将所有非字母字符序列替换为换行符,从而实现单词词元化。 这里,'A-Za-z' 表示所有英文字母,-c 选项表示取反(即匹配非字母字符),因此该命令会将每个非字母字符替换为换行符。 -s(“squeeze”,压缩)选项则确保连续的多个非字母字符只生成一个换行符,避免出现空行。 tr -sc 'A-Za-z' '\n' < sh.txt 该命令的输出如下所示: THE SONNETS by William Shakespeare From fairest creatures ... 现在每行只有一个单词,我们可以将其排序后传给 uniq -c,后者会合并相邻的相同行并统计出现次数: tr -sc 'A-Za-z' '\n' < sh.txt | sort | uniq -c 输出结果类似: 1945 A 72 AARON 19 ABBESS 25 Aaron 6 Abate 1 Abates ... 如果我们希望将所有大写字母统一转为小写,可以再加一步转换: tr -sc 'A-Za-z' '\n' < sh.txt | tr A-Z a-z | sort | uniq -c 输出变为: ...
计算机科学中文本处理最有用的工具之一是正则表达式(regular expression,简称 regex),它是一种用于描述文本字符串的语言。 正则表达式被广泛应用于各种编程语言、Unix 系统中的文本处理工具(如 grep),以及编辑器(如 vim 或 Emacs)中。 此外,在 BPE 等词元化算法的预词元化(pre-tokenization)阶段,正则表达式也发挥着重要作用。 形式上,正则表达式是一种用于描述字符串集合的代数表示法。 实际上,我们可以用它在文本中搜索某个字符串,并指定如何修改该字符串,这两项功能对词元化至关重要。 我们使用正则表达式在一个字符串(string)中搜索某种模式(pattern),该字符串可以是一行文本,也可以是更长的文本。 例如,Python 函数 re.search(pattern, string) 会扫描 string,并返回其中第一个与 pattern 匹配的内容。 在以下示例中,我们通常会高亮显示与正则表达式完全匹配的字符串,并仅展示第一个匹配结果。 我们将采用 Python 语法,将正则表达式写作由双引号界定的原始字符串(raw string):r"regex"。 原始字符串会将反斜杠 \ 视为字面字符,这一点很重要,因为我们接下来要介绍的许多正则表达式模式都会用到反斜杠。 正则表达式存在多种变体,因此使用在线正则表达式测试工具可以帮助确认你的正则表达式是否按预期工作。 2.7.1 字符析取:方括号 最简单的正则表达式就是一串普通字符。 模式 r"Buttercup" 会在任意字符串中匹配子串 Buttercup(例如在字符串 I’m called little Buttercup 中)。但很多时候我们需要使用特殊字符。 例如,我们可能希望匹配某一个字符或另一个字符中的任意一个。 通常,正则表达式是区分大小写的:r"s" 能匹配小写字母 s,但不能匹配大写字母 S。 为了同时匹配 s 和 S,我们可以使用字符析取(character disjunction)运算符,即方括号 [ 和 ]。 括号内的字符序列表示一个字符集合,匹配其中任意一个字符即可。 例如,图 2.9 显示,模式 r"[mM]" 可以匹配包含 m 或 M 的字符串。 模式 匹配内容 示例字符串 r"[mM]ary" Mary 或 mary “Mary Ann stopped by Mona’s” r"[abc]" ‘a’、‘b’ 或 ‘c’ “In uomini, in solda$ti” r"[1234567890]" 任意一位数字 “plenty of 7 to 5” 图 2.9 使用方括号 [] 表示字符的析取(即“或”关系)。 ...
词语并非凭空产生。 我们所研究的任何一段文本,都是由一个或多个特定的说话者或写作者,在特定语言的特定方言中,在特定的时间、特定的地点,出于特定的目的而产生的。 其中最重要的变异维度或许是语言本身。 自然语言处理算法若能适用于多种语言,则其价值最大。 根据在线语言目录《民族语》(Ethnologue)的统计(Simons 和 Fennig,2018),截至本文写作时,世界上共有7097种语言。 在开发算法时,应在多种语言上进行测试,尤其是那些具有不同语言特性的语言;与此相对,目前存在一种令人遗憾的趋势,即自然语言处理算法往往仅在英语上进行开发或测试(Bender,2019)。 即使算法的应用范围超出了英语,也往往集中于大型工业化国家的官方语言(如中文、西班牙语、日语、德语等),但我们不应将工具局限于这些少数语言。 此外,大多数语言本身也包含多种变体,通常由不同地区或不同社会群体使用。 因此,例如,如果我们处理的文本使用了非裔美国人英语(AAE)或非裔美国人白话英语(AAVE)的特征——这是数百万非裔美国人社区成员使用的英语变体(King,2020),我们就必须使用能够适应这些语言变体特征的自然语言处理工具。 推文(Twitter posts)中可能包含非裔美国人英语的常见表达,例如 iont(相当于主流美国英语(MAE)中的 I don’t),或 talmbout(对应于 MAE 的 talking about),这两个例子都会影响词语切分(Blodgett 等,2016;Jones,2015)。 在一次交际行为中混合使用多种语言的现象也十分普遍,这种现象被称为语码转换(code switching)。 语码转换在全球范围内极为常见;以下是包含西班牙语和(音译的)印地语与英语混合使用的例子(Solorio 等,2014;Jurgens 等,2017): (2.13) Por primera vez veo a @username actually being hateful! it was beautiful:) [For the first time I get to see @username actually being hateful! it was beautiful:)] (2.20) dost tha or ra- hega … dont wory … but dherya rakhe [“he was and will remain a friend … don’t worry … but have faith”] ...
尽管像 BPE 这样的数据驱动型词元化是目前最常用的方法,但在某些情况下,我们希望将词元限制为完整的词,而不是子词。 例如,当我们运行针对英语的句法分析算法时,解析器可能需要以语法上的词作为输入。 或者在任何语言学应用中,如果我们对所研究的词元已有某种先验定义,这种做法也会有用。 又或者在社会科学应用中,正字法意义上的词本身就是有意义的研究单位。 在基于规则的词元化中,我们会预先定义一个标准,并实现相应的规则来执行这种词元化方式。 下面我们以英语的词元化为例进行探讨。 对于英语,我们有一些期望目标:我们通常希望将标点符号切分为独立的词元,逗号对句法分析器来说是有用的信息,句号则有助于识别句子边界。 但我们也常常希望保留词内部出现的标点符号,例如 m.p.h.、Ph.D.、AT&T 和 cap’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 York 或 rock ’n’ roll)视为单个词元,这通常需要某种形式的多词表达式词典。 因此,基于规则的词元化与命名实体识别(named entity recognition,NER)任务密切相关,NER 旨在检测人名、日期和组织机构等(见第 17 章)。 一种常用的词元化标准是宾州树库词元化标准(Penn Treebank tokenization standard,PTB),该标准用于语言数据联盟(Linguistic Data Consortium, LDC)发布的已标注语料库(即“树库”),而 LDC 是许多有用数据集的来源。 该标准将附着词分开(例如 doesn’t 变为 does 和 n’t),保留带连字符的词整体不变,并将所有标点符号单独切分(为节省空间,此处用可见空格符号 ‘␣’ 表示词元之间的分隔,尽管实际输出中更常见的是换行符): ...
词元化(Tokenization)是自然语言处理的第一步,指将连续的输入文本分割为若干词元(tokens)的过程。 我们已经讨论了三种可能的词元单位:词(words)、语素(morphemes)和字符(characters)。 但每种作为基本单位都存在问题。 词和语素在语义上似乎处于 NLP 处理的理想粒度,因为它们通常具有相对稳定的含义,但难以形式化地精确定义。 字符虽然定义清晰,但作为词元单位又过小,难以承载足够的语义信息。 本节将介绍当前 NLP 实践中真正采用的方法:通过数据驱动的方式定义词元,这些词元通常大小接近词或语素,但在必要时也可小至单个字符。 为什么需要对输入进行词元化? 一个原因是,将输入转换为一组确定且固定的单位后,不同算法和系统才能就基本问题达成一致。 例如,这段文本有多长?(包含多少个单位?) 或者,“don’t” 或 “New York” 算一个词元还是两个? 因此,标准化的词元化对 NLP 实验的可复现性至关重要。本书后续介绍的许多算法(如语言模型的困惑度(perplexity)指标)都默认所有文本已采用统一的词元化方案。 此外,包含更小单位(如语素或字母)的词元化方法还能有效解决未登录词(unknown words)问题。 什么是未登录词?正如下一章将看到的,NLP 算法通常从一个训练语料库(training corpus)中学习语言规律,再将这些规律用于处理另一个独立的测试语料库(test corpus)。 例如,若训练语料中包含 low、new 和 newer,但不包含 lower,那么当 lower 出现在测试集中时,系统将无法识别它。 为应对这一问题,现代词元器会自动归纳出一组子词(subwords),即比完整单词更小的词元单位。 这些子词可以是任意子字符串,也可以是带有语义的单位(如语素 -est 或 -er)。 在现代词元化方案中,许多词元仍是完整单词,但也包含大量高频出现的语素或其他子词(如 -er)。 这样一来,任何未见过的词都可以被表示为若干已知子词的组合。 例如,即使我们从未见过 lower,当它出现时,仍可成功将其切分为 low 和 er,这两个子词已在训练中出现过。 在最极端的情况下,一个极其罕见的词(比如缩写词 GRPO)甚至可以被切分为单个字母序列。 当前大语言模型广泛采用两种子词切分算法:字节对编码(Byte-Pair Encoding, BPE)(Sennrich et al., 2016)和 unigram 语言模型(Unigram Language Modeling, ULM)(Kudo, 2018)1。 本节重点介绍 BPE 算法(Sennrich et al., 2016;Gage, 1994),见图 2.6。 与大多数词元化方案类似,BPE 算法包含两个部分:训练器(trainer)和编码器(encoder)。 在训练阶段,我们输入一个原始训练语料(通常已通过空格等简单方式粗略分词),从中归纳出一个词表(vocabulary),即一组学习得到的词元。 在编码阶段,编码器接收一条新的测试句子,并将其切分为训练阶段所学词表中的词元序列。 ...
在词元化(tokenization)时还可以考虑另一个选项:以单个字符为单位。 但问题随之而来:我们该如何表示跨越不同语言和书写系统的字符呢? Unicode标准正是为此而生,它是一种用于表示世界上任何语言(包括已消亡的语言如苏美尔楔形文字,以及人造语言如克林贡语)所使用的所有字符和文字的编码方法。 我们先简要回顾一下 Unicode 中一个仅面向英语的子集(在 Unicode 标准中技术上称为“基本拉丁字母”(Basic Latin),通常被称为 ASCII)。 自 20 世纪 60 年代起,用于书写英语的拉丁字母(比如本句中使用的这些字符)采用一种名为 ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)的编码方案进行表示。 ASCII 使用单个字节(byte)表示每个字符。 一个字节最多可表示 256 个不同的字符,但 ASCII 仅使用了其中的 127 个;其字节的最高位始终设为 0。 (实际上,它只用到了其中 95 个可打印字符,其余是为一种早已淘汰的设备电传打字机(teletype)预留的控制字符。) 下图展示了一些 ASCII 字符及其十六进制与十进制编码: 图 2.4 部分英文字符对应的 ASCII 编码,同时以十六进制和十进制形式列出。 然而,ASCII 显然远远不够,因为世界上各种书写系统包含的字符远不止这些! 即使对于同样使用拉丁字母的文字,其字符数量也远超 ASCII 的 95 个可打印字符。 例如,下面这句西班牙语(意为“先生,桑丘答道”)就包含两个非 ASCII 字符:ñ 和 ó: (2.10) Se ñor- respondió Sancho- 更不用说,世界上大量语言根本不使用拉丁字母! 例如,天城文(Devanagari)被用于 120 种语言(包括印地语、马拉地语、尼泊尔语、信德语和梵语)。 以下是《世界人权宣言》印地语文本中的一个天城文示例: 中文在 Unicode 中收录了约 10 万个汉字(包括中文、日文、韩文和越南文所使用的重叠与非重叠变体,字符合称 CJKV)。 ...
词是有内部结构的。 在字符层面,这一点显而易见。 单词 cats 由四个字符 ‘c’、‘a’、‘t’、‘s’ 构成。 但更深层次上,词还包含具有连贯语义的组成部分。 这些组成部分称为语素(morphemes),而对语素的研究称为形态学(morphology)。 语素是语言中最小的表义单位。 例如,单词 fox 仅包含一个语素(即 fox 本身),而 cats 则包含两个语素:表示“猫”的语素 cat 和表示复数的语素 -s。 以下是一个用连字符标出语素边界的英文句子: (2.6) Doc work-ed care-ful-ly wash-ing the glass-es 如前所述,在中文中,书写系统恰好与语素高度对应——每个汉字通常对应一个语素。 以下是一个普通话例句,每个汉字(即语素)均附有词义标注(gloss),其后为整句翻译: (2.7) 梅 干 菜 用 清 水 泡 软, 捞 出 后 , 沥 干 plum dry vegetable use clear water soak soft, remove out after, drip dry 切 碎 chop fragment Soak the preserved vegetable in water until soft, remove, drain, and chop 我们通常将语素分为两大类,词根(roots):词的核心语素,承载主要语义;词缀(affixes):附加在词根上,表达各类附加意义。 在上述英文例子中,worked 的 work 是词根,-ed 是词缀;同样,glasses 中的 glass 是词根,-es 是词缀。 ...
下面这个句子中有多少个词? They picnicked by the pool, then lay back on the grass and looked at the stars. 如果我们不将标点符号视为词,则该句包含 16 个词;若计入标点,则为 18 个词。 是否将句号(“.”)、逗号(“,”)等视为词,取决于具体任务。 标点符号对于识别语言单位的边界(如逗号、句号、冒号)以及某些语义特征(如问号、感叹号、引号)至关重要。 大型语言模型通常将标点符号视为独立的词。 在口语中,对“词”的界定会带来其他复杂性。 例如,考虑以下来自口语对话的话语(utterance)(话语是语言学中与书面句子相对应的口语技术术语): I do uh main- mainly business data processing 该话语包含两类不流畅现象(disfluencies)。 被中断的词 main- 被称为片段(fragment)。 像 uh 和 um 这样的成分则被称为填充词(fillers)或有声停顿(filled pauses)。 我们是否应将它们视为词? 答案依然取决于具体应用场景。 如果我们在构建语音转写系统,可能最终希望去除这些不流畅现象。 但有时我们也会保留它们。 事实上,uh 或 um 等不流畅现象在语音识别中对预测后续词汇有帮助,因为它们可能表明说话者正在重启某个子句或想法。因此,在语音识别任务中,它们通常被当作普通词处理。 此外,由于不同说话人使用不同的不流畅形式,这些现象还可作为识别说话人的线索。 Clark 与 Fox Tree(2002)甚至指出,uh 和 um 在英语中具有不同的语用含义。 你认为它们的区别是什么? 在思考“什么是词”时,或许最重要的是区分两种谈论词的方式,这一区分将在本书中反复使用。 词型(word types)指语料库中互不相同的词的数量;若词汇表为 $V$,则词型数量即为词汇量(vocabulary size)$|V|$。 词例(word instances)则是文本中所有连续出现的词的总数 $N$。1 若忽略标点符号,上述野餐句包含 14 个词型和 16 个词例: ...
用户:我需要一些帮助,这一点似乎很明确。 ELIZA:如果你得到了一些帮助,对你而言这意味着什么? 用户:也许我可以学会和我母亲相处。 ELIZA:跟我多说说你的家人吧。 用户:我母亲照顾我。 ELIZA:你家里还有谁照顾你? 用户:我父亲。 ELIZA:你父亲。 用户:你在某些方面很像我父亲。 Weizenbaum (1966) 上述对话来自 ELIZA——一个早期的自然语言处理系统。该系统通过模仿罗杰斯学派心理治疗师(Rogerian psychotherapist)的回应方式,能够与用户进行有限的对话(Weizenbaum, 1066)。 ELIZA 的实现令人惊讶地简单:它通过对词(words)进行模式匹配,识别诸如 “I need X” 这样的短语,并将其转换为合适的输出,例如 “What would it mean to you if you got X?”。 尽管以现代标准来看,ELIZA 对人类对话的模拟非常粗糙,但其效果却出奇地成功:许多与 ELIZA 交互的用户竟相信它真正“理解”了自己。 正因如此,这项工作首次促使研究者开始思考聊天机器人对其用户可能产生的影响(Weizenbaum, 1976)。 当然,ELIZA 所开创的那种基于模式的简单模仿,现代聊天机器人已不再使用。 然而,ELIZA 所体现的这种基于模式的词处理方法,在当今的词元化(tokenization)任务中依然适用;词元化即指从连续文本中将词及其组成部分转化为词元的过程。 词元化是现代自然语言处理(NLP)的第一步,采用了一些源于 ELIZA 时代的基于模式的方法。 要理解词元化,我们首先需要提问:什么是词(word)? “um” 算是一个词吗?“New York” 呢? 不同语言中“词”的本质是否相似? 有些语言(如越南语或粤语)的词通常非常短,而另一些语言(如土耳其语)的词则非常长。 此外,我们还需考虑如何用字符(characters)来表示词。 为此,我们将介绍 Unicode——现代字符表示系统,以及 UTF-8 文本编码方案。 同时,我们还将引入 语素(morpheme)的概念,即词中有意义的子成分(例如单词 longer 中的语素 -er)。 文本词元化的标准方法是利用输入字符提供切分线索。 因此,在理解了词可能包含的子成分之后,我们将介绍标准的 字节对编码(Byte-Pair Encoding, BPE)算法,该算法能自动将输入文本切分为词元(tokens)。 BPE 利用字符序列的简单统计规律,归纳出一个子词(subword)词元的词汇表。 所有词元化系统在处理过程中也都依赖于 正则表达式(regular expressions)。 正则表达式是一种用于形式化描述和操作文本字符串的语言,是所有现代 NLP 系统中的重要工具。 我们将介绍正则表达式,并展示其应用示例。 ...
我们首先介绍双向 Transformer 编码器,这是 BERT 及其后续模型(如 RoBERTa(Liu 等,2019)或 SpanBERT(Joshi 等,2020))的基础。 第 7 章中我们介绍了从左至右的语言模型,应用于问答或摘要等自回归式上下文生成任务,第 8 章则展示了如何使用因果型(从左至右)的 transformer 实现语言模型。 然而,这类模型从左至右的特性也构成了一种限制:在某些任务中,处理某个词元(token)时若能“窥视”其未来的词元将大有裨益。 这一点在序列标注(sequence labeling)任务中尤为明显,在这些任务中,我们希望为每个词元打上标签,如 9.5 节将要介绍的的命名实体识别(named entity tagging),或如后续章节将介绍的词性标注或句法分析。 本节所介绍的双向编码器与因果模型属于截然不同的类型。 第 8 章中的因果模型是生成式模型,旨在高效地生成序列中的下一个词元。 而双向编码器的核心目标则是计算输入词元的上下文化表示(contextualized representations)。 双向编码器利用自注意力机制,将输入嵌入序列 $(x_1, \dots, x_n)$ 映射为长度相同的输出嵌入序列 $(h_1, \dots, h_n)$,其中每个输出向量均融合了整个输入序列的信息,从而实现上下文化。 这些输出嵌入构成了各输入词元的上下文相关表示,在各类需要基于上下文对词元进行分类或决策的应用中具有广泛用途。 回顾一下,我们在前文提到,第 8 章的模型有时被称为仅解码器(decoder-only)模型,因为它们对应于第 12 章将要介绍的编码器–解码器架构中的解码器部分。 相比之下,本章所讨论的掩码语言模型则常被称为仅编码器(encoder-only)模型,因为它们为每个输入词元生成编码表示,但通常不用于通过解码或采样来生成连贯文本。 这一点至关重要:掩码语言模型并不用于文本生成,而是主要用于解释性任务(interpretative tasks)。 9.1.1 双向掩码模型的架构 我们首先讨论整体架构。 基于 Transformer 的双向语言模型与前几章介绍的因果型 Transformer 在两个方面有所不同。 第一,注意力机制是非因果的,词元 $i$ 的注意力可以关注其后续词元(如 $i+1$ 等); 第二,其训练方式略有不同,因为我们预测的是文本中间的某个词元,而非序列末尾的下一个词元。 本节先讨论第一点,第二点将在下一节展开。 图 9.1a(此处复用自第 8 章)展示了第 8 章中从左至右方法的信息流。 在该方法中,每个词元的注意力计算仅基于其前面及当前的输入词元,忽略了位于当前词元右侧的潜在有用信息。 双向编码器通过允许注意力机制遍历整个输入序列,克服了这一限制,如图 9.1b 所示。 ...