分词器是什么
本文系统梳理分词器的起源与发展历程,用通俗语言详解传统分词算法与子词分词的核心原理,对比中英文分词的本质差异,介绍主流分词工具与应用场景,并探讨分词技术的未来趋势,适合零基础读者从零理解NLP的核心基础组件。
第一章:认识分词器——什么是分词,为什么重要
1.1 什么是分词器:用大白话解释
想象一下,你收到一封用外文写的信,但所有字母都连在一起,没有空格,也没有标点。比如英文写成”hellohowareyoutoday”,你能一眼看懂吗?可能要花点时间琢磨从哪里断字。
这就是计算机面对人类语言时的困境。
分词器(Tokenizer),简单来说就是帮计算机”断字”的工具。它把一长串连续的文本,切割成一个个有意义的最小语言单位——我们称之为”词”或者”Token”。
举个最直观的例子:
- 输入:我爱自然语言处理
- 分词输出:我 / 爱 / 自然 / 语言 / 处理
对于英文来说,这件事看起来简单一些,因为单词之间本来就有空格。比如”I love natural language processing”,按空格切一下就差不多了。但事情没那么简单——英文里有缩写”don’t”、连字符”state-of-the-art”、所有格”Tom’s”,这些都不能简单地按空格切。
而对于中文、日文、韩文等语言,问题就复杂多了。中文的字与字之间没有任何分隔符,一句话就是一串连续的汉字。人可以凭借语感和知识轻松断词,但计算机不行。它不知道”南京市长江大桥”应该切成”南京市 / 长江大桥”,还是”南京 / 市长 / 江大桥”。
分词器就是解决这个问题的核心技术。它是自然语言处理(NLP)的第一道大门,所有后续的语义理解、情感分析、机器翻译、搜索引擎,都建立在分词的基础之上。
1.2 为什么需要分词:计算机不懂”词”
很多人会问:计算机这么聪明,连围棋冠军都能赢,为什么连分词这么”简单”的事都做不好?
答案是:计算机根本不知道什么是”词”。
在计算机眼里,文本只是一串二进制编码。它看到”我爱你”,只知道这是三个Unicode字符,编号分别是U+6211、U+7231、U+4F60。至于这三个字合起来是什么意思,哪些字该放在一起组成词,计算机天生是不懂的。
那为什么不直接用”字”来处理呢?比如每个汉字作为一个单位?
原因有三个:
第一,语义粒度不对。 单个汉字承载的语义太少了。“自”是什么意思?“然”是什么意思?单独拿出来都很模糊。但合在一起的”自然”,意思就明确了。如果用字级别的单位来做语义理解,模型需要从单个字拼凑出词义,难度会大很多。
第二,序列太长。 一句话”我昨天去超市买了苹果和香蕉”,按字算有14个单位,按词算只有8个单位。越长的序列,模型处理起来越慢、越难。Transformer的自注意力机制计算量和序列长度的平方成正比,序列翻倍,计算量翻四倍。
第三,信息密度低。 常用汉字只有几千个,但常用词有几十万。词能承载更丰富、更精确的语义信息。
所以,人类语言处理的第一步,就是把连续的文本切成有意义的词语单元。这就是分词器存在的根本原因。
1.3 Token是什么:语言的最小单位
你可能经常听到”Token”这个词。它和”词”有什么区别?
词(Word) 是语言学概念,是能够独立运用的最小语言单位。 Token 是工程概念,是分词器切分后得到的最小处理单元。
两者有时候一致,有时候不一致。比如:
- 在传统中文分词里,Token基本等于词
- 在子词分词里,一个词可能被切成多个Token
- 标点符号、数字也会成为独立的Token
打个比方:如果把语言比作乐高积木,那么”词”就是完整的乐高小人、乐高房子,而”Token”就是每一块积木零件。一个完整的组件可能由好几块积木拼成。
在大模型时代,Token的概念变得尤为重要。因为大模型处理的不是文字,而是Token ID——每个Token对应一个数字。模型的输入是一串数字,输出也是一串数字。
比如GPT-3.5的词汇表里有大约10万个Token,每个Token都有一个唯一的编号。当你输入”你好”时,分词器先把它转成Token,再映射成数字ID,模型才能处理。
1.4 分词器在NLP流水线中的位置
分词是几乎所有NLP任务的第一步。我们常说”垃圾进,垃圾出”(Garbage in, garbage out),分词如果错了,后面的所有步骤都会受影响。
一个典型的中文NLP流水线是这样的:
- 原始文本输入 → “工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作”
- 分词 → [“工信处”, “女干事”, “每月”, “经过”, “下属”, “科室”, “都”, “要”, “亲口”, “交代”, “24”, “口”, “交换机”, “等”, “技术性”, “器件”, “的”, “安装”, “工作”]
- 词性标注 → 标出每个词是名词、动词还是形容词
- 命名实体识别 → 找出人名、地名、机构名
- 句法分析 → 分析句子的语法结构
- 语义理解 → 最终理解句子的含义
可以看到,分词处在整个流水线的最上游。如果”女干事”被错误切成”女 / 干事”,或者”交换机”被切成”交换 / 机”,后面的词性标注、实体识别都会跟着错。
这就是为什么分词技术研究了几十年——它是地基,地基不稳,上面的楼再高也会塌。
1.5 通俗类比:分词器就像语言的”翻译官”
为了让你更好地理解,我们来做几个通俗类比:
类比一:分词器像语言的”断句师” 古人写文章是没有标点的,比如《论语》就是一整段文字。后人读书首先要学”句读”(jù dòu),也就是给文章断句加标点。同样一句话,断句不同,意思可能完全相反。分词器做的事情,本质上就是给计算机”断句”——只不过断的是词,不是句子。
类比二:分词器像食材处理师 厨师做菜之前,需要把一整块肉切成片、切成丝、切成丁。不同的菜需要不同大小的肉块。分词器就是NLP厨房的”食材处理师”,把整段文本切成合适大小的”语言肉块”,供后面的”厨师”(各种NLP模型)使用。切太大炒不熟,切太小容易碎。
类比三:分词器像电报编码员 以前发电报,要把文字转换成摩尔斯电码。分词器做的事情类似——把自然语言转换成模型能懂的”数字编码”。只不过摩尔斯电码是字母级的,而分词器是词级或子词级的。
理解了这些基本概念,接下来我们就穿越时空,看看分词器是怎样一步步发展到今天的。
第二章:分词器的起源与发展历史
分词器的发展史,本质上是一部在”词汇表大小”与”序列长度”之间寻找最优平衡点的进化史。它经历了从人工规则到统计模型,再到深度学习和子词智能的四次重大跃迁。
2.1 史前时代:人工分词与规则萌芽(1950s-1980s)
分词研究的源头,可以追溯到20世纪50年代的机器翻译探索。
1954年,美国乔治敦大学和IBM公司合作,进行了世界上第一次机器翻译实验——把俄语翻译成英语。这次实验虽然只处理了250个单词、6条语法规则,但开启了人类用计算机处理自然语言的序幕。
那时候的中文信息处理还处在萌芽期。中文打字机都还没普及,更别说中文分词了。
真正的中文分词研究起步于20世纪80年代。随着计算机进入中国,中文信息处理成为迫切需求。当时遇到的第一个拦路虎就是:计算机怎么理解中文句子?
最早的思路非常朴素——查字典。
研究者们把一本词典装进计算机,然后让程序扫描句子,遇到词典里有的词就切出来。这就是最早的”基于词典匹配”的分词方法,也叫机械分词法。
1983年,北京航空学院的研究者提出了”正向最大匹配法”,这是第一个系统化的中文分词算法。同期还有逆向最大匹配法、双向匹配法等变种。
这个时代的分词器,本质上就是个”高级查字典程序”。它没有任何智能,完全依赖人工编写的词典和规则。好处是简单、速度快;坏处是遇到词典里没有的词就傻眼,遇到歧义句子就乱切。
2.2 第一阶段:规则驱动时代(1980s-1990s)
整个80年代到90年代中期,分词研究都处在规则驱动时代。
核心思想
分词=查字典+人工规则。所有的分词逻辑都是人写死的。比如”最长匹配优先”、“单字词越少越好”等等。
代表算法
- 正向最大匹配法(FMM):从左往右,每次尽量匹配最长的词
- 逆向最大匹配法(BMM):从右往左,每次尽量匹配最长的词
- 双向最大匹配法:正反各切一遍,选结果更好的那个
- 最少切分法:哪种切法分出的词最少,就选哪种
典型系统
- 1987年,中科院计算所的”书面汉语自动分词系统”(CDWS)
- 90年代初,清华大学的SEG系统
- 哈尔滨工业大学的分词系统
历史局限
这个时代的分词系统,准确率大概在90%-95%之间。听起来不低,但实际用起来问题很多:
- 未登录词完全无法处理:词典里没有的词,比如人名、新出现的词,一律切错
- 歧义消解能力弱:“南京市长江大桥”这种经典歧义句,基本靠碰运气
- 词典维护成本高:新词不断出现,人工更新词典永远追不上
但不管怎样,规则时代奠定了中文分词的基础。最大匹配法的思想直到今天还在被使用——很多轻量级分词工具的核心依然是它。
2.3 第二阶段:统计驱动时代(1990s-2015)
2.3.1 统计方法的兴起
90年代,统计学方法开始席卷整个NLP领域。人们发现,与其人工写无穷无尽的规则,不如让计算机从大量语料中自动学习规律。
1990年代初,隐马尔可夫模型(HMM)被引入中文分词。研究者把分词问题转化为”序列标注”问题——给每个汉字打标签,标记它是词首(B)、词中(M)、词尾(E)还是单字词(S)。
比如”我爱北京天安门”标注成:
- 我/S 爱/S 北/B 京/E 天/B 安/M 门/E
这样一来,分词就变成了分类问题:给每个字分到B/M/E/S中的一类。
1999年,John Lafferty等人提出了条件随机场(CRF)模型,这是统计分词时代的巅峰之作。CRF比HMM更强大,能融合更多特征,准确率更高。很快,CRF就成为了工业界分词系统的标配。
2.3.2 里程碑:SIGHAN Bakeoff
2003年,在日本札幌举行的第一届ACL-SIGHAN国际中文分词竞赛,是中文分词发展史上的里程碑事件。
这次比赛首次统一了训练语料和测试标准,来自世界各地的研究团队同台竞技。比赛结果显示,基于统计机器学习的方法全面超越了纯规则方法。
这次竞赛之后,中文分词研究进入了快车道:
- 2005年前后,CRF成为主流
- 2007年,感知机和结构化学习方法兴起
- 2010年代初,联合模型与半监督学习出现
统计时代的分词准确率提升到了95%-97%。更重要的是,模型对未登录词有了一定的识别能力——它能根据字的搭配规律,猜测哪些字可能组成一个新词。
2.3.3 统计时代的工具爆发
这个时期涌现了一大批优秀的开源分词工具:
- ICTCLAS:中科院张华平博士开发,曾经的中文分词标杆
- Jieba:2012年诞生,Python生态最流行的中文分词库
- HanLP:何晗开发,功能最全面的Java/Python双语NLP工具
- THULAC:清华大学出品,准确率与速度兼顾
- IK Analyzer:搜索领域广泛使用的Java分词器
这些工具大多采用”词典+统计”的混合方案——先用词典匹配处理常见词,再用HMM或CRF处理未登录词,兼顾了速度和准确率。
2.4 第三阶段:子词智能时代(2015至今)
2.4.1 BPE:从数据压缩到NLP的传奇
子词分词的故事要从1994年说起。
美国软件工程师菲利普·盖奇(Philip Gage)在《C Users Journal》上发表了一篇文章《A New Algorithm for Data Compression》,提出了BPE(Byte Pair Encoding)算法。这本来是一个数据压缩算法——找出文本中出现最频繁的相邻字节对,用一个新字节替换它,反复操作就能压缩文件。
谁也没想到,20年后这个算法会彻底改变NLP领域。
2015年,英国爱丁堡大学的菲利普·森里奇(Philipp Sennrich)在论文《Neural Machine Translation of Rare Words with Subword Units》中,把BPE改造成了子词分词算法。
当时神经机器翻译(NMT)刚兴起,但遇到了一个头疼的问题——未登录词(OOV)。训练语料里没见过的词,模型完全不知道怎么翻译。比如人名、地名、专业术语,这些词不可能全部出现在训练数据里。
BPE的思路非常巧妙:不把整个词当单位,而是把词拆成更小的”子词”单元。比如”unhappiness”可以拆成”un + happy + ness”。这样即使模型没见过”unhappiness”这个完整词,也能通过子词组合猜出它的意思和翻译。
而且,子词共享还能大大压缩词汇表。英文有几十万个单词,但词根词缀就那么多。用子词的话,一个几万大小的词表就能表示几乎所有英文单词。
2.4.2 子词算法百花齐放
BPE成功之后,各种子词算法如雨后春笋般涌现:
- 2016年:Google推出WordPiece,用于BERT等模型
- 2018年:Google推出SentencePiece,支持多语言无空格分词
- 2018年:Unigram算法被广泛应用于T5、ALBERT等模型
子词分词解决了困扰NLP多年的OOV问题,让模型能够处理任意文本。这为后来大语言模型的爆发铺平了道路——想象一下,如果GPT只能处理词汇表里有的那几万个词,它根本不可能和你自由对话。
2.4.3 大模型时代的分词器
2018年BERT问世,2020年GPT-3发布,大模型时代正式到来。
分词器的地位也发生了微妙的变化:
- 以前,分词是一个独立的研究任务,有专门的评测和竞赛
- 现在,分词成了大模型的一个内置组件,服务于预训练目标
词表大小也在不断膨胀:
- BERT-base:约3万个Token
- GPT-2:约5万个Token
- GPT-3.5:约10万个Token
- GPT-4:超过10万个Token
- 国产大模型:普遍在10万-15万之间
词表越大,每个Token承载的语义越多,相同文本需要的Token数越少,模型推理越快。但词表也不是越大越好——词表越大,Embedding层参数越多,模型越难训练。
2.5 第四阶段:去分词化探索(2020至今)
有趣的是,当子词分词发展到顶峰时,学术界开始反思:我们真的需要分词器吗?
这个问题听起来有点叛逆,但理由很充分:
- 分词器是人为设计的预处理步骤,它的切分规则不一定最优
- 分词错误会向下游传播,造成误差累积
- 不同语言需要不同的分词器,跨语言处理麻烦
- 特殊字符、代码、混合文本处理困难
于是,一批”无分词器”的研究出现了:
- ByT5(2021):Google提出,直接在UTF-8字节上建模,完全不需要分词。虽然效果略逊,但鲁棒性更强,对噪声不敏感。
- MEGABYTE(2023):Meta提出,通过层次化设计解决字节序列过长的问题,用两级模型处理字节。
- H-Net(2025):卡内基梅隆大学提出,动态分块机制,自适应地将字节流分割成语义单元。
- ByteFlow(2025):亚马逊提出,基于压缩率的自适应分块策略。
这些研究还处在实验室阶段,目前子词分词在效率和性能上仍是最佳平衡点。但它们代表了一个重要的方向——也许未来的模型,会自己学会该怎么”断词”,不再需要人类预先设计分词器。
2.6 发展脉络总结
让我们用一张时间线来回顾分词器的进化之路:
| 时代 | 时间 | 核心方法 | 代表技术 | 准确率 | 核心问题 |
|---|---|---|---|---|---|
| 规则时代 | 1980s-1990s | 词典匹配+人工规则 | 最大匹配法 | ~90% | 未登录词、歧义 |
| 统计时代 | 1990s-2015 | 统计机器学习 | HMM、CRF | 95%-97% | 特征工程、数据依赖 |
| 子词时代 | 2015-至今 | 子词合并/剪枝 | BPE、WordPiece | 接近100% | 词表大小权衡 |
| 去分词时代 | 2020-至今 | 字节/字符级建模 | ByT5、MEGABYTE | 探索中 | 计算效率 |
这条演进路线有一个清晰的趋势:人工干预越来越少,模型自动学习的成分越来越多。从人写规则,到人设计特征让统计模型学习,再到模型自动学习子词单元,最后可能连分词这个步骤都不需要了。
这正是整个AI领域的发展缩影——人类做的事情越来越抽象,机器自己学习的东西越来越多。
第三章:传统分词算法原理详解(小白通俗版)
了解了历史,我们来深入看看各种分词算法的原理。这一章我们聚焦传统分词方法——也就是大模型出现之前,中文分词领域的主流技术。
别担心,我会用最通俗的语言和例子来讲,保证小白也能看懂。
3.1 基于词典的分词方法
基于词典的分词,也叫”机械分词”,是最直观、最容易理解的一类方法。
核心思想就一句话:拿句子去查词典,能匹配上的就是词。
但具体怎么匹配?从左往右还是从右往左?匹配长的还是短的?不同的策略衍生出了不同的算法。
3.1.1 正向最大匹配法(FMM)
核心思想:从句子左边开始,每次尽量取最长的、能在词典中找到的词。
通俗类比:就像你吃面条,从一头开始,每次夹起尽可能长的一筷子面。
算法步骤:
- 确定词典中最长的词有几个字(比如4个字)
- 从句子开头取4个字,去词典里查
- 如果查到了,就把这4个字作为一个词切出来,指针后移4位
- 如果没查到,就减少一个字(取3个字),再查
- 重复直到匹配成功,然后继续处理剩下的句子
- 直到整个句子处理完
手把手例子:
句子:“研究生命起源” 词典:{“研究”, “研究生”, “生命”, “起源”} 最长词长度:3
第一步:从左取3个字 → “研究生” 查词典 → 有! 切分出:“研究生” 剩余句子:“命起源”
第二步:从左取3个字 → “命起源” 查词典 → 没有 减到2个字 → “命起” 查词典 → 没有 减到1个字 → “命” 查词典 → 有(单字) 切分出:“命” 剩余句子:“起源”
第三步:取2个字 → “起源” 查词典 → 有! 切分出:“起源”
最终结果:研究生 / 命 / 起源
你看,问题来了——“研究生命起源”正确的切分应该是”研究 / 生命 / 起源”,但正向最大匹配切出了”研究生 / 命 / 起源”,这就错了。
这就是正向最大匹配的典型问题:因为从左往右贪心匹配,容易把前面的词切得太长,导致后面的词切错。
3.1.2 逆向最大匹配法(BMM)
既然从左往右容易错,那反过来从右往左呢?
核心思想:从句子右边开始,每次尽量取最长的、能在词典中找到的词。
还是刚才的例子:
句子:“研究生命起源” 词典:{“研究”, “研究生”, “生命”, “起源”} 最长词长度:3
第一步:从右取3个字 → “命起源” 查词典 → 没有 减到2个字 → “起源” 查词典 → 有! 切分出:“起源” 剩余句子:“研究生命”
第二步:从右取3个字 → “究生命” 查词典 → 没有 减到2个字 → “生命” 查词典 → 有! 切分出:“生命” 剩余句子:“研究”
第三步:取2个字 → “研究” 查词典 → 有! 切分出:“研究”
最终结果:研究 / 生命 / 起源
这次就对了!
统计表明,中文中逆向最大匹配的准确率确实比正向高一点点。因为中文的修饰语多在前面,中心词多在后面,从后往前匹配更容易抓住核心词。
但逆向匹配也不是万能的,它也有自己的易错场景。
3.1.3 双向最大匹配法
既然正向和逆向各有优劣,那不如两个都跑一遍,选结果更好的那个?
这就是双向最大匹配法。
选优规则通常是:
- 如果两种方法切出来的词数量不一样,选词数少的那个(词越少说明平均每个词越长,越可能正确)
- 如果词数一样,选单字词更少的那个
还是用刚才的例子:
- 正向结果:[“研究生”, “命”, “起源”] → 3个词,1个单字词
- 逆向结果:[“研究”, “生命”, “起源”] → 3个词,0个单字词
词数相同,逆向的单字词更少,所以选逆向结果。
双向匹配比单一方向的准确率高,但代价是要跑两遍分词,速度慢了一些。
3.1.4 词典匹配法的本质问题
词典匹配法简单、快速,但有两个绕不开的硬伤:
第一个硬伤:歧义
“南京市长江大桥”到底怎么切?
- 南京市 / 长江大桥
- 南京 / 市长 / 江大桥
两种切法词典里都有,匹配法根本不知道哪个对。它只知道”这些词都在词典里”,但不知道哪种组合符合语义。
第二个硬伤:未登录词
词典里没有的词,比如人名”张三”、新词”元宇宙”,匹配法肯定切不对。它会把”张三”切成”张 / 三”,把”元宇宙”切成”元 / 宇宙”。
这两个问题,光靠查字典是解决不了的。必须引入统计和概率,让模型学会”哪种切法更合理”。
3.2 基于统计的分词方法
统计分词的核心思想是:哪种分词结果出现的概率大,哪种就是对的。
换句话说,不是看词典里有没有,而是看”人们会不会这么说话”。
3.2.1 N-gram语言模型:用概率说话
先介绍一个基础概念:语言模型。
语言模型就是计算一句话”像不像人话”的模型。一句话越符合语言习惯,概率就越高。
比如:
- P(“我喜欢吃苹果”) → 概率很高,很像人话
- P(“苹果吃喜欢我”) → 概率很低,不像人话
怎么计算一句话的概率呢?用链式法则: P(“我喜欢吃苹果”) = P(“我”) × P(“喜欢”|“我”) × P(“吃”|“我喜欢”) × P(“苹果”|“我喜欢吃”)
但这样计算太复杂了,因为前面的词越长,组合越多,根本统计不过来。
于是人们做了一个简化假设:一个词出现的概率,只和它前面的n个词有关。这就是N-gram模型。
- 1-gram(一元模型):每个词的概率独立,P(w1,w2,w3) = P(w1)×P(w2)×P(w3)
- 2-gram(二元模型):每个词只和前一个词有关,P(w3|w1,w2) ≈ P(w3|w2)
- 3-gram(三元模型):每个词和前两个词有关
有了语言模型,分词就简单了:
- 找出所有可能的分词方式
- 用语言模型计算每种方式的概率
- 选概率最大的那个
还是”研究生命起源”的例子:
- 切法A:研究 / 生命 / 起源 → 计算P(研究)×P(生命|研究)×P(起源|生命)
- 切法B:研究生 / 命 / 起源 → 计算P(研究生)×P(命|研究生)×P(起源|命)
显然P(生命|研究)远大于P(命|研究生),所以切法A概率更高,选A。
这就是统计方法的威力——它不是机械地匹配,而是根据语言规律判断哪种切分更合理。
3.2.2 隐马尔可夫模型(HMM)
N-gram模型虽然好用,但计算所有可能的切分方式太慢了。句子越长,可能的切法指数级增长。
有没有更高效的方法?有,那就是把分词转化为序列标注问题。
B/M/E/S四标签体系:
- B(Begin):词的第一个字
- M(Middle):词中间的字
- E(End):词的最后一个字
- S(Single):单独成词的字
比如”我爱北京天安门”标注为: 我/S 爱/S 北/B 京/E 天/B 安/M 门/E
这样,分词问题就变成了:给每个字预测一个标签(B/M/E/S)。
隐马尔可夫模型(HMM)就是干这个的。HMM假设:
- 每个字(观测值)是由对应的标签(隐藏状态)生成的
- 标签之间有转移概率(比如B后面更可能接M或E,不太可能接B或S)
用HMM分词分两步:
- 训练:在大量标注好的分词语料上,统计两个概率:
- 发射概率:某个标签生成某个字的概率
- 转移概率:某个标签转移到下一个标签的概率
- 解码:对新句子,用Viterbi算法找出概率最大的标签序列
HMM的好处是计算快,而且能识别未登录词。比如一个人名,虽然词典里没有,但模型根据字的搭配规律,能猜出”这几个字应该组成一个词”。
3.2.3 条件随机场(CRF)
HMM有个局限:它假设每个位置的标签只和前一个位置有关,而且只能利用有限的特征。
条件随机场(CRF)解决了这个问题。CRF可以融合更多特征——比如当前字、前一个字、后一个字、字的偏旁部首、是不是数字等等。
CRF是统计分词时代的准确率天花板。在标准数据集上,纯CRF分词能达到96%-97%的F1值。
但CRF也有缺点:训练慢,特征工程麻烦。特征选得好不好,直接影响最终效果。而设计特征需要资深的NLP工程师。
这也是为什么后来深度学习火了——因为深度学习能自动学习特征,不用人手工设计。
3.2.4 序列标注法的通俗理解
可能B/M/E/S、HMM、CRF这些概念听起来有点抽象,我打个比方:
想象你在听一首外语歌,歌词是连在一起唱的,你不知道词和词之间从哪断开。但你听多了之后,会摸索出一些规律:
- 某些音经常出现在词开头
- 某些音经常出现在词结尾
- 某些音经常连在一起出现
听得多了,即使遇到一首新歌,你也能大概猜出歌词该怎么断词。
统计模型做的就是这件事。它在大量标注语料上”听”了无数句子,学会了汉字在词中的位置规律,然后就能给新句子”断词”了。
3.3 基于深度学习的分词方法
2015年之后,深度学习开始统治NLP领域,分词也不例外。
3.3.1 BiLSTM-CRF架构
最经典的深度学习分词模型是BiLSTM-CRF。
这个架构分两层:
- BiLSTM层:双向LSTM网络,自动学习每个字的向量表示,捕捉上下文信息。它能看到每个字左边和右边的内容,理解”这个字在当前语境下应该是什么标签”。
- CRF层:在LSTM输出的基础上,学习标签之间的转移约束,保证输出的标签序列是合法的(比如B后面不能接S)。
BiLSTM-CRF的好处是:
- 不需要人工设计特征,模型自动学习
- 能捕捉更长距离的上下文依赖
- 准确率比纯CRF更高
在标准数据集上,BiLSTM-CRF分词能达到97%-98%的F1值。
3.3.2 预训练模型时代
2018年BERT出来之后,分词又上了一个台阶。
用BERT做分词非常简单:在BERT输出的基础上加一个分类层,预测每个字的B/M/E/S标签就行了。
因为BERT已经在海量文本上预训练过,学到了丰富的语义知识,所以它做分词效果特别好。尤其是歧义词和未登录词,BERT凭借强大的语义理解能力,处理得比之前的模型都好。
但BERT也有缺点:速度慢、计算量大。所以工业界很多时候还是用CRF或更轻量的模型——毕竟分词只是第一步,后面还有更重的任务。
3.3.3 深度学习为什么更准
为什么深度学习分词比传统方法准?我再打个比方:
传统统计方法(CRF)就像一个经验丰富的老工人,他根据自己总结的几百条经验规则来判断怎么分词。经验很管用,但遇到超出经验的新情况就容易出错。
深度学习模型(BERT)就像一个读过亿万本书的学霸,它不仅记住了语言规律,还理解了语义。当遇到歧义句时,它能根据上下文的意思来判断该怎么切,而不是只看表面规律。
比如”结婚的和尚未结婚的”这个经典歧义:
- 错误切法:结婚 / 的 / 和尚 / 未 / 结婚 / 的
- 正确切法:结婚 / 的 / 和 / 尚未 / 结婚 / 的
传统模型可能会纠结,因为”和尚”确实是个常见词。但BERT能理解整句话的意思——这句话在说结婚的人和没结婚的人,所以”和尚”在这里说不通,应该是”和 / 尚未”。
这就是语义理解带来的质变。
3.4 传统分词方法总结
让我们对比一下三类传统分词方法:
| 方法类别 | 核心思想 | 代表算法 | 准确率 | 未登录词处理 | 速度 | 实现难度 |
|---|---|---|---|---|---|---|
| 基于词典 | 查字典匹配 | 最大匹配法 | ~90% | 差 | 极快 | 简单 |
| 基于统计 | 概率最大化 | HMM、CRF | 95%-97% | 一般 | 快 | 中等 |
| 深度学习 | 自动学习特征 | BiLSTM-CRF、BERT | 97%-99% | 好 | 慢 | 复杂 |
工业界的实际情况是:大多数系统采用”词典+统计”的混合方案。比如Jieba分词就是”前缀词典+最大概率路径+HMM未登录词识别”的组合——用词典保证速度和常见词的准确,用HMM处理未登录词。
这种混合方案在准确率和速度之间取得了很好的平衡,也是过去十几年中文分词的主流做法。
第四章:子词分词算法深度解析
如果说传统分词是中文NLP的特色话题,那么子词分词就是整个NLP领域的通用基础设施。
大模型时代,你听到的几乎所有模型——GPT、BERT、LLaMA、Qwen——都在使用子词分词。理解了子词分词,你就理解了大模型如何”读懂”文字。
4.1 为什么需要子词:OOV问题的痛点
在子词分词出现之前,NLP模型大多使用”词级分词”——也就是一个词对应一个Token。
比如词汇表里有”happy”、“sad”这些词,模型就能处理。但如果遇到”unhappiness”这个词,而词汇表里没有,那就麻烦了——这就是未登录词(Out-of-Vocabulary,简称OOV)。
OOV问题在以下场景特别严重:
形态丰富的语言:英语、俄语、德语等,一个词根能变出几十种形式。比如”run”有runs、ran、running、runner等多种变形。如果每个变形都单独进词表,词表会爆炸。
专有名词:人名、地名、品牌名无穷无尽。比如”马斯克”、“特斯拉”,不可能全部收录。
新词:网络时代新词层出不穷。“内卷”、“躺平”、“元宇宙”,词典永远追不上。
低资源语言:小语种没有足够的语料来构建完整词表。
OOV问题的后果很严重:模型遇到不认识的词,只能用一个特殊标记[UNK]代替,等于这个词的信息完全丢失了。如果关键信息恰好是个未登录词,模型就完全理解错了。
怎么解决呢?研究者想到了一个朴素的思路:既然整词不认识,那就拆成小块认识。
就像你不认识”unhappiness”这个词,但你认识”un-”(否定前缀)、“happy”(开心)、“-ness”(名词后缀),拼起来你也能大概猜出意思。
子词分词就是基于这个思想:把词拆成更小的单元(子词),用有限的子词组合出无限的词汇。
4.2 BPE(字节对编码):最经典的子词算法
BPE是目前应用最广泛的子词算法。GPT系列、LLaMA系列都在用它。
4.2.1 起源:从数据压缩到NLP
前面提到过,BPE最早是1994年提出的数据压缩算法。它的思路非常简单:
- 找出文本中出现最频繁的相邻字节对
- 用一个新字节替换这对字节
- 重复,直到达到压缩目标
比如文本里”ab”出现得最多,就用”z”代替所有”ab”。这样文本就变短了。
2015年,Sennrich等人把这个思想移植到了NLP领域。只不过压缩的目标换成了构建子词词表。
4.2.2 训练过程:手把手推演
BPE的训练过程就是”不断合并最高频相邻对”的过程。我用一个超简单的例子,带你一步步推演。
训练语料(4个单词,括号里是出现次数):
- low (5次)
- lower (2次)
- newest (3次)
- widest (1次)
目标:训练一个BPE词表
第一步:初始拆分 把每个单词拆成单个字符,末尾加特殊标记表示词结束。为什么要加结束标记?因为要区分”st”出现在词中还是词尾。
- low → l o w (5次)
- lower → l o w e r (2次)
- newest → n e w e s t (3次)
- widest → w i d e s t (1次)
初始词表就是所有单个字符:{l, o, w, n, e, r, i, d, s, t, }
第二步:统计所有相邻对的频率 我们来数一下每对相邻字符出现了多少次:
- l-o:5+2 = 7次
- o-w:5+2 = 7次
- w-:5次
- w-e:2+3 = 5次
- e-r:2次
- r-:2次
- n-e:3次
- e-s:3+1 = 4次
- s-t:3+1 = 4次
- t-:3+1 = 4次
- w-i:1次
- i-d:1次
- d-e:1次
出现频率最高的是”l-o”和”o-w”,都是7次。我们随便选一个,比如”l-o”。
第三步:合并最高频对 把”l”和”o”合并成”lo”,更新所有单词:
- low → lo w (5次)
- lower → lo w e r (2次)
- newest → n e w e s t (3次)
- widest → w i d e s t (1次)
词表新增一个Token:lo
第四步:重复合并 继续统计相邻对频率,找到最高的,合并。 比如下一个最高频的可能是”lo-w”(出现7次),合并成”low”。 然后可能是”e-s”(出现4次),合并成”es”。 然后是”es-t”(出现4次),合并成”est”。 ……
就这样一轮轮合并下去,每轮新增一个Token,直到词表达到目标大小。
最终效果: 词表里既有单个字符(兜底用的,保证任何词都能被拆分),也有常见的字母组合,还有完整的高频词。
4.2.3 编码过程:最长匹配
训练好词表之后,怎么用它来分词呢?
BPE的编码过程很简单——最长匹配。从左往右扫描单词,每次匹配词表里最长的子词。
比如遇到”lowest”这个词:
- 看整个”lowest”在不在词表里?可能不在
- 看”lowe”在不在?不在
- 看”low”在不在?在!
- 切出”low”,剩下”est”
- 看”est”在不在?在!
- 切出”est”
最终结果:low + est
因为词表里有单个字符兜底,所以任何单词都能被切分,永远不会出现OOV。
4.2.4 通俗类比:搭积木
BPE的思想特别像搭积木:
- 初始状态:你有一堆最小的积木块(单个字符)
- 训练过程:把经常粘在一起的两个块粘成一个大块
- 反复操作:最后你有各种大小的积木块
- 使用时:用这些积木块去拼任何单词
积木块的总数就是词表大小。词表越大,积木块越大,拼一个词需要的块越少;词表越小,积木块越小,拼一个词需要的块越多。
这就是词表大小和序列长度的权衡。
4.2.5 BBPE:字节级BPE
原始BPE是基于字符的。但遇到生僻字、emoji、特殊符号怎么办?Unicode字符太多了,初始词表会很大。
GPT-2做了一个巧妙的改进:不用字符,用字节。
UTF-8编码里,一个字节有256种可能。所以初始词表大小固定为256,不管是什么语言、什么符号,最终都能拆成字节。
这就是Byte-level BPE(BBPE),字节级BPE。
比如一个汉字”你”,UTF-8编码是3个字节:E4 BD A0。在BBPE看来,它初始就是3个Token。如果这个字出现频率很高,这3个字节可能被合并成一个Token。
BBPE的好处是:
- 真正的”开放词汇表”——任何文本都能处理,没有[UNK]
- 初始词表只有256,非常小
- 对各种语言、符号、emoji一视同仁
现在主流大模型基本都用BBPE,比如GPT系列、LLaMA系列。
4.3 WordPiece:BERT的选择
WordPiece是Google提出的子词算法,主要用于BERT系列模型。
它和BPE非常像,都是从字符开始逐步合并。核心区别只有一点:合并选择策略不同。
- BPE:合并出现频率最高的相邻对
- WordPiece:合并能最大化训练数据似然的相邻对
什么叫”最大化似然”?简单说就是:合并之后,整个语料的概率提升最多。
具体计算公式是:选择使 P(tok1,tok2) / (P(tok1) × P(tok2)) 最大的那一对。
这个值叫”互信息”。两个子词经常一起出现,但各自单独出现也很频繁,互信息就低;两个子词总是绑定在一起出现,互信息就高。
比如”的”和”是”经常相邻出现,但它们各自也经常和别的字搭配,互信息不高,不应该合并。 而”蜘”和”蛛”几乎总是一起出现,互信息很高,应该合并。
WordPiece还有一个特色标记:##前缀。
- 出现在词开头的子词,正常写
- 出现在词中间的子词,前面加##
比如”unhappiness”可能被切成:un + ##happy + ##ness
这个##标记的作用是告诉模型:这个子词是接在前面的子词后面的,它们共同组成一个完整的词。
BPE vs WordPiece 通俗对比:
- BPE就像”谁出现次数最多就合并谁”,简单粗暴
- WordPiece就像”谁最应该在一起就合并谁”,更讲究语义
实际使用中两者效果差别不大。BPE因为实现简单,用得更广泛一些。
4.4 Unigram:反向剪枝的概率模型
BPE和WordPiece都是”自底向上”的——从最小单元开始,一点点合并变大。
Unigram正好相反,它是”自顶向下”的——从一个很大的词表开始,一点点剪掉不重要的子词,直到达到目标大小。
4.4.1 训练过程:从大到小剪枝
Unigram的训练步骤:
初始化大词表:先生成一个很大的候选词表。怎么生成?可以把所有高频的字符串都放进去,也可以先用BPE生成一个较大的词表。
训练Unigram语言模型:假设每个子词独立出现,计算每个子词的出现概率。
评估每个子词的重要性:对于词表里的每个子词,计算”如果把它删掉,整个语料的困惑度会上升多少”。困惑度上升越少,说明这个子词越不重要。
剪枝:删掉最不重要的一批子词(比如删掉10%)。
重复:回到第2步,重新训练语言模型,再剪枝。直到词表缩小到目标大小。
保留基础字符:最后一定要保留所有单个字符,保证任何词都能被拆分。
4.4.2 编码过程:Viterbi求最优
分词的时候,Unigram不是用最长匹配,而是用Viterbi算法找出概率最大的切分路径。
比如”unbelievable”有很多种切法:
- u + n + b + e + l + i + e + v + a + b + l + e
- un + believable
- un + believ + able
- un + be + lie + v + able
- …
每种切法都有一个概率(所有子词概率的乘积),Unigram选概率最大的那种。
4.4.3 Unigram的独特优势
和BPE、WordPiece相比,Unigram有几个独特的优点:
全局最优倾向:因为是从大词表往下剪,避免了BPE早期错误合并导致的累积误差。BPE是贪心的,前面合错了后面没法改;Unigram有全局视角。
支持概率采样:Unigram可以输出多种可能的切分,每种切分有对应的概率。训练时可以随机采样不同的切分方式,起到数据增强的作用,提升模型鲁棒性。
天然支持无空格语言:Unigram不需要预分词,可以直接在原始字符流上工作。
SentencePiece默认就是用Unigram算法。T5、ALBERT、XLNet等模型也用的是Unigram。
4.4.4 通俗类比:修剪树木
如果说BPE是搭积木,那Unigram就是修剪树木:
- 一开始有一棵枝繁叶茂的大树(大词表)
- 你一根根剪掉最没用的树枝
- 最后剩下的都是最有价值的枝干
- 用这些枝干,也能组合出所有可能的形状
4.5 SentencePiece:语言无关的统一方案
前面讲的BPE和WordPiece,都有一个隐含前提——先按空格把文本切成单词,然后在单词内部做子词拆分。
这个前提对英文没问题,但对中文、日文这种没有空格的语言就麻烦了。你总不能先跑一遍中文分词,再做子词分词吧?那又回到传统分词的问题了。
Google的SentencePiece解决了这个问题。
4.5.1 核心创新:空格也是普通字符
SentencePiece最天才的设计是:它把空格也当作一个普通字符来处理。
具体做法是:
- 把输入文本中的空格替换成一个特殊符号”▁“(U+2581)
- 然后直接在整个字符序列上训练子词模型,不做任何预分词
- 解码的时候,再把”▁“换回空格
比如输入”hello world”:
- 变成”▁hello ▁world”
- 分词结果可能是:[“▁hello”, “▁world”]
对于中文”你好世界”:
- 变成”▁你好世界”
- 分词结果可能是:[“▁你好”, “世界”]
就这么简单!不需要区分中英文,不需要预先分词,所有语言一视同仁。
4.5.2 算法支持
SentencePiece本身不是一种算法,而是一个工具框架。它内部支持两种子词算法:
- BPE
- Unigram(默认)
4.5.3 为什么适合中日韩语言
传统的子词分词需要先做词级分词,而中日韩语言的词级分词本身就是个难题。
SentencePiece绕开了这个问题——它根本不关心什么是”词”,直接在原始字符流上做子词切分。对它来说,中文和英文没有本质区别,都是字符序列。
这也是为什么很多多语言大模型喜欢用SentencePiece——一套分词器处理所有语言。
4.6 四种子词算法对比总结
最后我们来总结对比一下四种子词分词方法:
| 算法 | 构建方向 | 合并/选择策略 | 编码方式 | 代表模型 | 特点 |
|---|---|---|---|---|---|
| BPE | 自底向上 | 频率最高优先 | 最长匹配 | GPT、LLaMA | 实现简单、应用最广 |
| BBPE | 自底向上 | 频率最高优先 | 最长匹配 | GPT-2/3/4 | 字节级、无OOV |
| WordPiece | 自底向上 | 互信息最大优先 | 最长匹配 | BERT系列 | 更注重语义关联 |
| Unigram | 自顶向下 | 剪枝损失最小优先 | Viterbi概率最优 | T5、ALBERT | 有概率、可采样 |
| SentencePiece | 框架级 | 支持BPE/Unigram | 取决于算法 | 多语言模型 | 语言无关、无预分词 |
这几种算法没有绝对的优劣,各有适用场景。工业界目前用得最多的是BBPE(GPT系)和SentencePiece-Unigram(多语言系)。
第五章:中英文分词的本质区别
很多人学习分词的时候会困惑:为什么中文分词讲了那么多算法,英文分词好像很简单?大模型时代的分词器,对中英文的处理一样吗?
这一章我们就来聊聊中英文分词的本质区别。
5.1 语言结构差异
中英文分词难度不同,根源在于两种语言的文字系统完全不同。
5.1.1 文字系统对比
| 维度 | 英文 | 中文 |
|---|---|---|
| 基本单位 | 字母(26个) | 汉字(常用约3500个) |
| 词汇边界 | 空格天然分隔 | 无显式分隔符 |
| 构词方式 | 字母组合成词 | 汉字组合成词 |
| 形态变化 | 丰富(时态、单复数、比较级) | 几乎无 |
| 最小表义单位 | 词 | 字/词 |
打个比方:
- 英文就像乐高积木,每块积木(字母)本身没意义,但拼在一起(单词)才有意义。积木之间有明显的缝隙(空格)。
- 中文就像七巧板,每块板(汉字)本身就有意义,可以组合出更多图案(词语)。但拼好之后板和板之间没有缝隙,你得自己判断哪几块组成一个图案。
5.1.2 分词难度差异
因为有空格,英文的”词边界”大部分时候是明确的。英文分词的主要工作不是找词边界,而是处理一些特殊情况:
- 缩写:don’t → do + n’t
- 连字符:state-of-the-art → state + of + the + art
- 所有格:Tom’s → Tom + ‘s
- 数字和单位:100kg → 100 + kg
而中文分词的核心难题恰恰是找词边界。汉字连续排列,哪里是词的开始,哪里是词的结束,全靠算法判断。这是一个本质上更难的问题。
5.2 传统分词时代:两条完全不同的路线
在传统分词时代,中英文走的是完全不同的技术路线。
英文:词形还原是重点
英文因为有空格,分词本身不难。真正的难点是词形变化。
比如”run”这个词,有runs、ran、running、runner、runners等多种形式。如果把它们当作不同的词,词表会非常大,而且模型无法共享它们之间的语义。
所以英文NLP有两个独特的步骤:
- 词干提取(Stemming):把词的后缀去掉,提取词干。比如running → run,flies → fli。简单粗暴,但不一定准确。
- 词形还原(Lemmatization):根据词性和词典,把词还原成原形。比如better → good,went → go。更准确,但需要词典和词性标注。
中文完全不需要这些——中文的”跑”就是”跑”,过去现在将来都是”跑”,没有形态变化。
中文:边界切分是重点
中文没有词形变化问题,但边界切分就够头疼了。
所以中文NLP的研究重点是:怎么切才对?歧义怎么消解?未登录词怎么识别?
整个中文分词研究了几十年,本质上都在解决边界问题。
通俗总结
- 英文分词:边界天然存在,重点是”词形归一”
- 中文分词:边界需要判断,重点是”边界识别”
5.3 子词时代的趋同与差异
大模型时代,子词分词成为主流。中英文分词的差异变小了,但依然存在。
5.3.1 预分词步骤不同
虽然都是子词分词,但第一步不一样:
英文子词分词流程:
- 按空格和标点,把文本切成单词片段(预分词)
- 在每个单词内部做子词拆分
比如”unhappiness” → 先识别为一个单词 → 再拆成”un + happy + ness”
中文子词分词流程(以SentencePiece为例):
- 不做预分词,整个句子就是一串字符
- 直接在字符序列上做子词拆分
比如”自然语言处理” → 直接拆成”自然 + 语言 + 处理”
当然,如果用的是BBPE(字节级BPE),那预分词步骤更不重要——反正最后都拆成字节。
5.3.2 中文Token更”贵”
这是一个非常实际的问题:同样的信息量,中文消耗的Token数比英文多。
比如GPT-3.5的词表约10万Token,其中:
- 英文常用词很多是整词Token,一个单词占1个Token
- 中文常用词很多是两字或三字Token,但也有大量单字Token
- 平均下来,一个英文Token约对应4个字符,一个中文Token约对应1.3个汉字
为什么会这样?因为GPT的词表是英文主导的,对中文优化不足。训练语料中英文占比高,常见英文组合都被合并成了整词Token;而中文出现频率不够高的组合,就停留在单字或两字级别。
这就是为什么大家说”中文比英文费Token”。同样的上下文窗口,中文能装的内容比英文少。
5.3.3 国产大模型的中文优化
国产大模型(比如通义千问、文心一言、DeepSeek等)普遍针对中文做了优化:
- 更大的词表:很多国产模型词表在12万-15万,比GPT的10万更大,放入了更多中文常用词
- 中文语料占比高:训练语料中中文比例高,中文子词合并更充分
- 常用词整词化:高频中文词尽量作为整词存在,减少Token数
效果就是:国产大模型处理中文时,Token利用率更高,同样的字数消耗的Token更少。
5.4 其他语言的分词特点
除了中英文,世界上还有很多语言,它们的分词各有特点:
- 日语:汉字+假名混合,比中文还复杂。日语有专门的分词工具MeCab,分词+词性标注一体化。
- 韩语:韩文是拼音文字,但单词之间也有空格,类似英文。但韩语的黏着语形态变化非常丰富。
- 泰语:没有空格,类似中文,但字母是拼音文字。泰语分词也是专门的研究领域。
- 德语:有空格,但喜欢把多个词拼成一个长复合词,比如”Donaudampfschifffahrtsgesellschaftskapitän”(多瑙河轮船公司船长)。这种词必须拆成子词。
可以说,每种语言都有自己的分词痛点。子词分词之所以成为主流,很大程度上是因为它能用一套统一的框架处理几乎所有语言。
5.5 本章总结
传统时代,中英文分词是两个完全不同的技术领域:
- 英文:空格分词 + 词形还原
- 中文:边界识别 + 歧义消解
子词时代,两者在技术框架上趋同了——都是把文本切成子词单元。但由于语言特性和词表优化的差异,实际表现仍然不同:
- 英文:子词合并充分,Token利用率高
- 中文:子词颗粒度偏细,Token利用率较低,但国产模型在不断优化
理解这些差异,能帮你更好地理解大模型的行为——比如为什么同样的问题,英文回答得更好,为什么中文感觉上下文窗口不够用。
第六章:主流分词工具全景对比
讲了这么多原理,我们来看看实际工作中能用哪些分词工具。工欲善其事,必先利其器。选择合适的分词工具,能让你的项目事半功倍。
6.1 中文分词工具
中文分词工具非常多,各有特色。我们从最常用的开始介绍。
6.1.1 Jieba:轻量之王
Jieba(结巴)应该是Python生态里最流行的中文分词库了。如果你做Python项目需要快速上手中文分词,Jieba几乎是首选。
核心特点:
- 纯Python实现,安装简单(pip install jieba)
- 轻量级,内存占用小
- 速度快,每秒能处理几十万字
- 支持三种分词模式
三种分词模式:
精确模式:最常用的模式,试图将句子最精确地切开,适合文本分析。
- 输入:“我来到北京清华大学”
- 输出:[“我”, “来到”, “北京”, “清华大学”]
全模式:把句子中所有可以成词的词语都扫描出来,速度非常快,但不能解决歧义。
- 输入:“我来到北京清华大学”
- 输出:[“我”, “来到”, “北京”, “清华”, “清华大学”, “华大”, “大学”]
搜索引擎模式:在精确模式的基础上,对长词再次切分,提高召回率,适合搜索引擎分词。
- 输入:“我来到北京清华大学”
- 输出:[“我”, “来到”, “北京”, “清华”, “华大”, “大学”, “清华大学”]
技术原理: Jieba采用”前缀词典+最大概率路径+HMM”的混合方案:
- 基于Trie树结构构建前缀词典,实现高效的词典匹配
- 用动态规划查找最大概率路径,找出最优切分
- 对于未登录词,采用HMM模型和Viterbi算法识别
适用场景:
- 快速原型开发
- 中小型项目
- 对速度要求高、对准确率要求不是极致的场景
不足:
- 准确率不如HanLP、THULAC等专业工具
- 对歧义和未登录词处理一般
- 功能相对单一,只有分词和关键词提取
6.1.2 HanLP:全能选手
HanLP是由何晗开发的一站式NLP工具包,号称”面向生产环境的多语种自然语言处理工具包”。
核心特点:
- 功能极其全面:分词、词性标注、命名实体识别、依存句法分析、语义角色标注……几乎你能想到的NLP基础任务都有
- 支持多种分词算法:最短路径分词、CRF分词、感知机分词、深度学习分词
- 支持Java和Python双语言
- 有完善的文档和社区
- 支持自定义词典、用户词典
分词模式: HanLP提供了多种分词器供选择:
- 标准分词:平衡速度与精度
- NLP分词:用CRF模型,准确率更高
- 索引分词:适合搜索引擎,对长词二次切分
- 极速词典分词:纯词典匹配,速度极快
适用场景:
- 工业级生产环境
- 需要完整NLP管线的项目
- 对准确率要求较高的场景
不足:
- 比Jieba重,内存占用大
- Python版比Java版功能少一些
- 学习曲线稍陡
6.1.3 THULAC:清华出品
THULAC(THU Lexical Analyzer for Chinese)是清华大学自然语言处理实验室出品的中文词法分析工具包。
核心特点:
- 准确率高:在CTB5数据集上F1值可达97.3%
- 速度快:只分词可达1.3MB/s,约每秒15万字
- 分词和词性标注一体化
- 训练语料规模大(5800万字人工标注语料)
适用场景:
- 学术研究
- 对准确率要求高的项目
不足:
- 更新较慢
- 功能相对HanLP少
- Python版性能不如C++版
6.1.4 LAC:百度的词法分析
LAC(Lexical Analysis of Chinese)是百度开源的词法分析工具。
核心特点:
- 基于深度学习联合模型
- 同时完成分词、词性标注、命名实体识别
- 分词F1值超过91%(百度官方数据)
- 支持Python、C++、Java等多种语言
- 有移动端版本
适用场景:
- 百度生态项目
- 需要同时做分词和实体识别的场景
6.1.5 IK Analyzer:搜索领域老兵
IK Analyzer是Java生态里最常用的中文分词器,广泛用于Elasticsearch等搜索引擎。
核心特点:
- 专门为搜索优化
- 支持细粒度和智能两种切分模式
- 支持自定义词典、停用词
- 和Elasticsearch、Solr集成良好
适用场景:
- 搜索引擎(Elasticsearch)
- Java后端项目
6.2 英文分词工具
英文分词工具就更多了,我们介绍几个最主流的。
6.2.1 NLTK:学术标配
NLTK(Natural Language Toolkit)是Python最经典的NLP库,几乎是NLP入门必学。
核心特点:
- 功能全面:分词、词性标注、句法分析、语义分析应有尽有
- 学术资源丰富:附带大量语料库和数据集
- 教学友好,文档完善
分词功能:
- word_tokenize:基于Penn Treebank分词规则
- sent_tokenize:句子切分
- 支持多种分词器
适用场景:
- 学习和研究
- 学术项目
- 快速原型
不足:
- 速度慢,不适合工业级大数据量
- 分词效果一般,不如spaCy
6.2.2 spaCy:工业级首选
spaCy是目前工业界最流行的Python NLP库,以速度快、准确率高著称。
核心特点:
- 速度极快,专为生产环境设计
- 准确率高,基于深度学习模型
- API设计优雅,易用性好
- 支持多语言
- 内置词向量
分词功能: spaCy的分词器基于规则+统计的混合方案,处理缩写、连字符、特殊符号等都非常出色。
适用场景:
- 工业级生产环境
- 英文NLP项目首选
6.2.3 Stanford CoreNLP
斯坦福大学出品的NLP工具包,Java语言,功能非常强大。
核心特点:
- 准确率高,学术认可度高
- 功能极其全面
- 支持多语言
不足:
- 速度慢,内存占用大
- Java为主,Python调用不方便
6.3 大模型时代的分词器
大模型时代,分词器不再是独立工具,而是和模型绑定在一起。每个大模型都有自己的分词器和词表。
6.3.1 HuggingFace Tokenizers
HuggingFace的Tokenizers库是目前最流行的子词分词工具。
核心特点:
- 用Rust实现,速度极快
- 支持BPE、WordPiece、Unigram等主流算法
- 和Transformers库无缝集成
- 支持训练自己的分词器
如果你用HuggingFace生态的模型,基本都是用这个库来做分词。
6.3.2 OpenAI tiktoken
OpenAI官方的分词器,专门用于GPT系列模型。
核心特点:
- 纯Rust实现,速度极快
- 准确还原GPT模型的分词结果
- 支持cl100k_base、p50k_base等多种词表
- 支持Python、Rust等语言
如果你需要精确计算GPT API的Token消耗,tiktoken是最准确的工具。
6.3.3 SentencePiece
Google出品的子词分词工具,前面已经详细介绍过。
核心特点:
- 语言无关,不需要预分词
- 支持BPE和Unigram两种算法
- 适合多语言和中日韩语言
- 速度快,轻量级
很多开源大模型(比如LLaMA、T5)都用SentencePiece来训练分词器。
6.4 工具选型指南
这么多工具,到底该怎么选?给你一个简单的决策树:
中文场景:
- 快速上手、Python项目 → Jieba
- 工业级、功能全 → HanLP
- 搜索场景、Java → IK Analyzer
- 极致准确率 → THULAC
- 百度生态 → LAC
英文场景:
- 学习研究 → NLTK
- 工业生产 → spaCy
- 学术高精度 → Stanford CoreNLP
大模型场景:
- HuggingFace生态 → Tokenizers库
- GPT系列 → tiktoken
- 自己训练分词器 → SentencePiece
通用建议:
- 优先选社区活跃、文档完善的工具
- 准确率和速度根据场景权衡,不是越高越好
- 垂直领域一定要加自定义词典
- 不要重复造轮子,成熟工具已经足够好用
第七章:分词器的应用场景
分词器是NLP的基础组件,它本身不产生直接价值,但几乎所有NLP应用都建立在它之上。这一章我们来看看分词器在各个领域的具体应用。
7.1 搜索引擎:最经典的应用
搜索引擎是分词技术最大、最经典的应用场景。可以说,没有中文分词,就没有好用的中文搜索引擎。
7.1.1 搜索分词的两个阶段
搜索引擎里,分词发生在两个阶段:
第一阶段:建索引的时候 爬虫爬回来的网页,先要分词,然后建立倒排索引。比如”苹果手机多少钱”这篇文章,分词后得到[“苹果”, “手机”, “多少”, “钱”],索引里就记录”苹果”这个词出现在这篇文章里。
第二阶段:用户查询的时候 用户输入搜索词,搜索引擎先对查询词分词,然后去倒排索引里匹配。比如用户搜”苹果手机价格”,分词得到[“苹果”, “手机”, “价格”],然后找出包含这些词的文档。
7.1.2 搜索分词的特殊要求
搜索场景的分词和普通文本分词不一样,有自己的特殊要求:
多粒度切分:既要匹配长词也要匹配短词。比如”北京大学”,既要能作为整词匹配,也要能拆成”北京”和”大学”分别匹配。所以搜索引擎常用”全模式”或”搜索引擎模式”分词。
高召回优先:搜索场景下,漏结果比多结果更严重。所以分词倾向于切得更细一些,保证尽量多地召回相关结果,然后靠排序来保证质量。
实时性要求高:用户输入后要立刻出结果,分词必须非常快,通常毫秒级完成。
同义词扩展:分词之后通常还要做同义词扩展,比如”电脑”扩展出”计算机”,“手机”扩展出”移动电话”。
7.1.3 通俗例子
用户搜索”苹果14手机壳”:
- 分词器切分:[“苹果”, “14”, “手机壳”]
- 如果有自定义词典,可能识别为:[“苹果14”, “手机壳”]
- 同义词扩展:“手机壳” → 手机壳、保护套、手机保护壳
- 去索引里匹配包含这些词的商品
如果分词器把”苹果14”切成了”苹果”和”14”,搜索结果里就会混入大量苹果水果和无关的14相关商品,用户体验就差了。所以电商搜索非常看重品牌词、型号词的识别准确。
7.2 机器翻译:子词分词的发源地
机器翻译是子词分词技术的发源地。正是因为机器翻译饱受OOV困扰,才催生了BPE等子词算法。
7.2.1 传统词级翻译的问题
早期的神经机器翻译用的是词级分词。词汇表里有多少词,模型就能翻译多少词。遇到词汇表里没有的词,模型就傻了。
比如翻译”张三去了北京”,如果”张三”不在词表里,模型就不知道怎么翻,可能直接跳过,或者翻译成[UNK]。
专有名词、专业术语、新词,这些都是词级翻译的噩梦。
7.2.2 子词分词如何解决问题
有了子词分词之后,情况就不一样了。
比如”张三”这个人名,虽然词表里没有完整的”张三”,但有”张”和”三”这两个子词。模型可以分别翻译这两个字,再组合起来。虽然效果不如整词好,但至少不会完全丢失信息。
更重要的是,子词分词能利用词根词缀的规律。比如”unhappiness”虽然没见过,但”un-”、“happy”、“-ness”模型都认识,组合起来就能猜个大概。
BPE应用到机器翻译之后,翻译质量提升非常明显,尤其是稀有词和专有名词的翻译。
7.2.3 实际效果
根据Sennrich的原始论文,使用BPE子词分词后:
- 稀有词的翻译准确率大幅提升
- 整体BLEU值提升了1-2个点
- 词汇表大小从几十万压缩到了几万
这在当时是非常大的突破。现在所有的机器翻译系统基本都用子词分词了。
7.3 文本分类与情感分析
文本分类、情感分析、主题识别这些任务,都需要先分词再提取特征。
7.3.1 特征提取的基础
传统的文本分类流程:
- 分词
- 去停用词(去掉”的”、“了”、“是”这些没意义的词)
- 特征提取(比如TF-IDF、词袋模型)
- 训练分类器
分词的质量直接影响特征质量。如果把”非小细胞肺癌”切成了”非 / 小 / 细胞 / 肺癌”,特征就散了,分类器可能识别不出这是一种癌症。
7.3.2 粒度对分类的影响
分词粒度和分类效果有密切关系:
- 粒度太粗:特征太稀疏,很多文本没有共同特征
- 粒度太细:噪声太多,无关特征干扰分类
一般来说,粗粒度的分词更适合分类任务。因为粗粒度的词语义更明确,区分度更高。
比如情感分析中,“非常满意”作为整词,情感倾向很明确;如果切成”非常”和”满意”,情感强度就弱了。
7.4 大语言模型:Tokenizer的核心作用
大模型时代,分词器(Tokenizer)的角色变得更加核心了。
7.4.1 文本到数字的转换
大模型本质上是一个处理数字序列的神经网络。它不认识文字,只认识数字。
Tokenizer就是负责转换的桥梁:
- 编码(Encoding):输入文本 → Tokenizer切分成Token → 每个Token映射成一个数字ID → 输入模型
- 解码(Decoding):模型输出数字ID → Tokenizer映射回Token → Token拼接成文本
没有Tokenizer,模型和人类语言之间就无法沟通。
7.4.2 词表大小的权衡
词表大小是大模型的一个核心超参数,它直接影响模型的多个方面:
| 词表大 | 词表小 |
|---|---|
| 每个Token语义多,序列短,推理快 | 每个Token语义少,序列长,推理慢 |
| Embedding参数量大,训练难 | Embedding参数量小,训练易 |
| 少见的词也能整词表示,效果好 | 少见的词拆成子词,效果差 |
| 词表稀疏,很多Token用得少 | 词表紧凑,每个Token都常用 |
模型设计者需要在这些因素之间找到平衡点。GPT-3选了5万,GPT-3.5选了10万,国产模型很多选12万-15万。
7.4.3 特殊Token的功能
大模型的词表里除了普通的文本Token,还有很多特殊Token:
<|endoftext|>:文本结束标记<|beginoftext|>:文本开始标记<|padding|>:填充标记<|unk|>:未知Token<|system|>、<|user|>、<|assistant|>:对话角色标记<|function_call|>:工具调用标记
这些特殊Token是模型控制逻辑的重要手段。比如看到<|endoftext|>,模型就知道该停止生成了。
7.5 垂直行业应用
通用分词器在专业领域往往不够用。医疗、金融、法律这些行业有大量专业术语,必须做领域适配。
7.5.1 医疗领域:病历分词
医疗是对分词准确度要求最高的领域之一。
核心痛点:
- 医学术语多且长:“急性下壁心肌梗死”、“头孢哌酮舒巴坦钠”
- 缩写多:“房颤”(心房颤动)、“糖网”(糖尿病视网膜病变)
- 拉丁药名、解剖部位名
- 病历书写口语化、不规范
解决方案:
- 构建医学专业词典,导入分词器
- 用医学语料微调分词模型
- 术语本体图谱辅助消歧
实际案例:某医疗问答系统,原始模型对”非小细胞肺癌靶向治疗”的实体召回率很低。引入Jieba加载医学术语词典后,实体召回率提升了18.7%。
7.5.2 金融领域:研报分析
金融领域有大量专业词汇:财报术语、金融产品名、股票代码、机构名称等等。
典型应用:
- 研报自动摘要
- 舆情监控
- 风险事件识别
- 财报信息抽取
分词要求:
- 金融术语完整识别:“EBITDA”、“市盈率”、“量化宽松”
- 股票代码和名称对应
- 机构名完整切分
优化效果:专门的金融领域分词器,对专业术语的Token消耗比通用分词器少30%-40%,处理效率和准确率都更高。
7.5.3 法律领域:法条与案例检索
法律文本的特点是用词严谨、固定搭配多、专有名词密集。
典型应用:
- 法律文书检索
- 法条引用识别
- 判例相似度匹配
- 合同信息抽取
分词难点:
- 法律术语:” habeas corpus”(人身保护令)、“certiorari”(调卷令)
- 法条引用格式:“42 U.S.C. § 1983”
- 案件名称、当事人名称
专业法律分词器能把复杂法律术语的Token数减少一半以上,显著提升检索和理解的效果。
7.5.4 电商领域:商品搜索与推荐
电商搜索是分词技术最成熟的商业应用之一。
核心挑战:
- 用户输入口语化、不规范
- 品牌词、型号词层出不穷
- 同义词、别称多(“手机壳”又叫”保护套”)
- 中英混合、拼音混杂
电商分词的特殊技巧:
- 品牌词库实时更新
- Query纠错(用户打错字也能识别)
- 同义词扩展
- 拼音分词支持
淘宝、京东的搜索系统背后,都有非常复杂的分词和Query理解模块。用户能搜到想要的商品,分词器功不可没。
7.6 其他应用场景
除了上面这些,分词器还活跃在很多地方:
- 智能客服:理解用户问题的关键词
- 舆情监控:从海量文本中提取话题和实体
- 自动摘要:先分词再计算词重要性
- 输入法:联想词、纠错都依赖分词
- 语音识别后处理:语音转文字后,分词帮助断句和加标点
可以说,只要涉及中文文本处理,就几乎离不开分词器。它就像空气一样,平时你感觉不到它的存在,但没有它寸步难行。
第八章:分词的三大挑战与解决方案
分词看起来简单,实则困难重重。经过几十年的研究,还有几个核心难题没有被完全解决。这一章我们来聊聊分词的”三大敌人”——歧义、未登录词、领域差异。
8.1 歧义消解:分词最难的问题
歧义是中文分词最古老、最核心的难题。同样一串字,有多种合法的切法,人根据语境能判断,但机器很难。
8.1.1 歧义的类型
分词歧义主要分为两类:
第一类:交集型歧义
一个字串,既是前一个词的结尾,也是后一个词的开头,形成交叉。
经典例子:
“南京市长江大桥”
- 切法A:南京市 / 长江大桥
- 切法B:南京 / 市长 / 江大桥
- “市”和”长”形成交集
“结婚的和尚未结婚的”
- 切法A:结婚 / 的 / 和 / 尚未 / 结婚 / 的
- 切法B:结婚 / 的 / 和尚 / 未 / 结婚 / 的
- “和”和”尚”形成交集
交集型歧义是最常见的歧义类型,大约占歧义总量的80%以上。
第二类:组合型歧义
一个字串,可以作为一个整词,也可以拆成几个词。
经典例子:
“马上”
- 副词:我马上就来 → 马上(立刻)
- 名词+动词:马上有个人 → 马 / 上(马的上面)
“将来”
- 名词:将来的日子 → 将来(未来)
- 副词+动词:将来北京 → 将 / 来(将要来)
组合型歧义更难,因为它需要真正理解语义才能判断。
8.1.2 真歧义和伪歧义
还有一个概念:真歧义和伪歧义。
- 伪歧义:虽然理论上有多种切法,但实际语言中只有一种是常用的。比如”乒乓球拍卖完了”,理论上可以切成”乒乓球 / 拍卖 / 完了”,但实际几乎没人这么说话。伪歧义词典里有,但语料中出现概率极低。
- 真歧义:两种切法在实际语言中都很常见。比如”马上”两种用法都很多。真歧义必须结合上下文才能判断。
真歧义才是真正的难点。
8.1.3 消解方法的演进
歧义消解技术经历了几代演进:
第一代:规则法 人工写歧义消解规则。比如”的”后面不接B标签之类的。规则法能处理一些简单歧义,但覆盖不了所有情况,而且规则之间容易冲突。
第二代:统计法 用N-gram、HMM、CRF等统计模型,根据上下文概率判断哪种切法更合理。统计法比规则法强很多,能处理大部分常见歧义。但它本质上是在”搭概率积木”,没有真正理解语义,遇到需要常识的歧义就不行了。
第三代:语义法 用预训练语言模型(BERT等),基于深层语义理解来判断。BERT读过海量文本,学到了丰富的世界知识,很多需要常识的歧义它也能判断对。
比如”他骑在马上”,BERT知道”骑”这个动作是骑在动物背上,所以”马上”应该是”马 / 上”而不是副词”马上”。
8.1.4 歧义消解的极限
虽然技术在进步,但歧义问题永远不可能100%解决。因为:
- 语言本身就有歧义,有些句子人看了也会歧义
- 有些歧义需要外部知识和常识才能判断
- 有些歧义需要结合更大的上下文甚至背景知识
分词准确率从95%提升到99%容易,从99%提升到99.9%就非常难了。最后那零点几个百分点,都是各种疑难歧义。
8.2 未登录词识别:永远追不上的新词
未登录词(OOV)就是词典里没有、模型没见过的词。这是分词的第二大挑战。
8.2.1 什么是未登录词
未登录词主要包括:
- 命名实体:人名、地名、机构名、品牌名
- 新词:新出现的词,比如”内卷”、“躺平”、“元宇宙”
- 专业术语:各行业的专有名词
- 外来词:音译词、字母词,比如”咖啡”、“GDP”
- 网络用语:“yyds”、“emo”、“破防”
语言是活的,每天都有新词产生。词典的更新速度永远赶不上新词出现的速度。
8.2.2 传统方法:基于字的识别
传统分词系统怎么识别未登录词?主要靠序列标注模型。
比如HMM或CRF,它们不是只看词典,而是根据字的位置规律来判断。比如:
- “张”、“李”、“王”这些字经常出现在人名开头
- “伟”、“芳”、“明”这些字经常出现在人名结尾
- 某些字搭配在一起很像人名
模型学多了之后,即使没见过的人名,也能大概猜出来这几个字应该组成一个词。
但这种方法准确率有限。遇到生僻人名、奇怪的品牌名,还是容易切错。
8.2.3 子词分词:从根本上缓解OOV
子词分词没有”解决”OOV问题,而是从根本上”消解”了OOV问题。
因为子词分词的词表里有最基础的字符/字节兜底,任何词都能被拆成子词,不存在”完全不认识”的情况。
当然,子词也不是完美的:
- 整词认识的效果最好
- 拆成子词也能懂,但效果打折扣
- 拆得越碎,效果越差
但比起传统词级分词遇到OOV直接变成[UNK],子词分词已经是巨大进步了。
8.2.4 新词发现技术
除了被动识别,还有主动发现新词的技术——新词发现。
新词发现就是从未标注的海量文本中,自动找出可能是新词的字符串。基本思路是:
- 统计所有可能的字符串的出现频率
- 计算”凝固度”:字符串内部字之间的结合有多紧密。比如”苹果”凝固度高,“的苹”凝固度低
- 计算”自由度”:字符串左右能接的字有多少种。比如”苹果”左右能接很多种字,自由度高;“巧克”后面几乎总是”力”,自由度低
- 凝固度高且自由度高的字符串,很可能是一个新词
新词发现可以用来自动更新词典,让分词系统与时俱进。
8.3 领域适配:通用vs专业
第三个挑战是领域差异。通用分词器在通用文本上表现不错,但放到专业领域就不行了。
8.3.1 领域差异带来的问题
不同领域的语言差异非常大:
- 词汇不同:医学有医学术语,金融有金融术语
- 分词标准不同:有的领域倾向于整词,有的领域倾向于细分
- 语言风格不同:法律文本严谨正式,网络文本口语随意
比如”关节”这个词:
- 通用场景:是一个词,指身体部位
- 机械领域:可能是”关 / 节”,指关闭阀门这个动作
通用分词器按照通用习惯切,到了专业领域就可能出错。
8.3.2 解决方案一:自定义词典
最简单也最有效的方法:加自定义词典。
把领域内的专业术语整理成词典文件,加载到分词器里。分词器遇到这些词就会优先按词典切。
比如医疗系统加载ICD-10疾病术语词典,法律系统加载法律术语词典,电商系统加载品牌商品词典。
自定义词典的优点是简单、见效快、可控性强;缺点是需要人工维护,新词加不进去。
8.3.3 解决方案二:领域微调
更进阶的方法:用领域语料重新训练或微调分词模型。
比如在通用CRF分词模型的基础上,用几千条标注好的医疗病历继续训练,模型就能学到医疗领域的分词规律。
微调比自定义词典效果好,能识别词典里没有的领域词汇。但需要标注数据,成本更高。
8.3.4 解决方案三:领域专属分词器
对分词质量要求极高的场景,可以直接训练领域专属的分词器。
比如:
- 医疗分词器:专门在病历语料上训练
- 法律分词器:专门在法律文书上训练
- 代码分词器:专门处理程序代码
领域专属分词器在特定领域的表现肯定比通用分词器好,但通用性差,换个领域就不行了。
8.4 本章总结
分词的三大挑战——歧义、未登录词、领域差异,本质上对应了三个层面的困难:
- 歧义:语义理解层面的困难
- 未登录词:词汇开放性层面的困难
- 领域差异:语言多样性层面的困难
这三个问题都没有终极解决方案,技术只能不断逼近完美。
从规则到统计到深度学习,每一代技术都在这三个问题上有所进步:
- 深度学习在歧义消解上进步最大
- 子词分词基本解决了未登录词问题
- 领域微调能较好地解决领域适配问题
但离”完美分词”还有很长的路要走。也许只有当模型真正理解语言的时候,分词这个问题才会彻底消失。
第九章:分词器的评估与优化
怎么判断一个分词器好不好?怎么优化分词器的性能?这一章我们聊聊评估和优化。
9.1 评估指标
评估一个分词器,主要看三个维度:准不准、快不快、占多少内存。
9.1.1 准确率指标
最常用的三个指标:精确率(Precision)、召回率(Recall)、F1值。
精确率:切出来的词里,有多少是切对的
- 公式:正确切分的词数 / 切分出来的总词数
召回率:应该切对的词里,有多少被切对了
- 公式:正确切分的词数 / 标准答案的总词数
F1值:精确率和召回率的调和平均,综合评价
- 公式:F1 = 2 × P × R / (P + R)
一般都用F1值来衡量分词准确率。
目前的水平:
- 基于词典:~90%
- CRF统计模型:95%-97%
- 深度学习:97%-99%
- 人类:约97%-98%(人与人之间也有分歧)
有意思的是,最好的分词模型准确率已经超过普通人了。因为普通人偶尔也会纠结某个地方该不该切。
9.1.2 速度指标
速度通常用”每秒处理多少字符”或”每秒处理多少词”来衡量。
不同算法速度差异很大:
- 纯词典匹配:每秒几百万字
- CRF:每秒几十万字
- BiLSTM:每秒几万字
- BERT:每秒几千字
速度和准确率往往是矛盾的。越准的模型越慢,越快的模型越不准。实际项目中要根据需求权衡。
9.1.3 内存指标
内存占用主要看词典大小和模型大小:
- 轻量级词典:几MB
- 统计模型:几十MB
- 深度学习模型:几百MB到几GB
嵌入式设备、移动端对内存比较敏感,需要选轻量的分词器。
9.1.4 子词分词的特殊评估
子词分词的评估不太一样,因为它没有标准答案。子词分词没有绝对的”对”和”错”,只有”好”和”不好”。
子词分词通常用这些指标评估:
- 压缩率:同样的文本,需要多少个Token。越少越好
- 困惑度:语言模型在该词表下的困惑度。越低越好
- 下游任务效果:最终翻译、生成等任务的效果。这是最根本的指标
毕竟子词分词是为下游任务服务的。下游任务效果好,分词就是好的。
9.2 常用评测数据集
中文分词有几个标准评测数据集,都是SIGHAN Bakeoff竞赛流传下来的:
- MSR:微软亚洲研究院标注,新闻语料,约400万字
- PKU:北京大学标注,新闻语料,约110万字
- CTB:中文树库,宾夕法尼亚大学标注
- AS:台湾中研院标注,繁体中文
这些数据集有标准的训练集和测试集,不同算法在上面跑,结果可以横向对比。
不过这些数据集都是新闻领域的,和实际业务场景可能有差异。最好的评估还是在自己的业务数据上测。
9.3 性能优化技巧
实际项目中,分词往往是性能瓶颈之一。尤其是搜索、推荐这些需要高并发的场景。
这里介绍一些常用的性能优化技巧。
9.3.1 算法层面优化
Trie树(字典树) 这是词典分词最经典的数据结构。Trie树把词典组织成一棵树,每个字是一个节点。查找的时候从根节点往下走,匹配非常快。
Trie树的好处:
- 查找速度快,和词典大小关系不大
- 前缀匹配天然支持最大匹配法
- 内存占用比哈希表小
AC自动机 如果需要同时匹配大量模式串,AC自动机比Trie树更高效。IK Analyzer等搜索分词器常用。
动态规划优化 计算最大概率路径的时候,可以用各种动态规划优化技巧减少计算量。
9.3.2 工程层面优化
缓存 很多场景下,重复的文本会被多次分词。比如搜索系统里,热门查询每天被搜成千上万次。加个缓存,分词结果直接复用,性能提升巨大。
并行处理 分词是天然可并行的——文本切成好几段,每段同时分,最后拼起来。多核CPU上能线性提速。
Rust/C++实现 Python写的分词器再怎么优化也有天花板。用Rust或C++重写核心逻辑,速度能提升几倍到几十倍。HuggingFace Tokenizers、tiktoken都是Rust实现的。
流式处理 处理大文件时,不要一次性全部读进内存,边读边处理,降低内存峰值。
9.3.3 词典优化
裁剪词典 不是词越多越好。很多低频词几乎用不上,占内存还影响匹配速度。根据业务场景裁剪掉没用的词,又省内存又快。
热更新 业务词典经常需要更新。好的分词器支持词典热更新——不用重启服务,动态加载新词典。这对线上系统很重要。
分层词典 核心常用词放第一层,快速匹配;低频词放第二层,第一层匹配不到再查第二层。兼顾速度和覆盖率。
9.3.4 子词优化
词表大小选择 根据业务场景选择合适的词表大小。不是越大越好——太大了Embedding层参数多,模型训练慢;太小了序列太长,推理慢。
合并规则优化 BPE的合并顺序对最终效果有影响。可以针对特定语言或领域优化合并策略,让常用组合更早被合并。
9.4 避坑指南
最后说几个初学者常踩的坑:
坑1:只看准确率不看速度 很多人上来就选最准的模型,结果上线后扛不住并发。一定要先搞清楚性能要求。
坑2:用通用评测代替业务评测 标准数据集上分数高,不代表在你的业务数据上好。一定要用自己的数据测一遍。
坑3:词典加得越多越好 自定义词典不是越多越好。加得太多会引入错误,反而降低准确率。词典要精选,定期清理。
坑4:过度追求准确率 分词只是第一步,后面还有更重要的任务。分词准确率从97%提升到98%,可能花费巨大成本,但对最终业务效果提升很小。够用就好。
第十章:分词器的未来发展趋势
分词技术发展到今天,是不是已经到头了?未来会往哪个方向走?这一章我们来聊聊趋势。
10.1 语义感知与动态分词
现在的分词器都是”一刀切”——同一个词,不管什么上下文,切法都一样。但实际上,分词粒度应该根据语境动态调整。
10.1.1 固定粒度的局限
举个例子:“北京大学”
- 在”我考上了北京大学”这句话里,作为一个整体理解更好
- 在”北京大学校园很美”这句话里,切成”北京 / 大学”也能理解
- 在搜索场景,既需要整词匹配也需要分词匹配
固定粒度的分词,无法同时满足所有需求。
10.1.2 上下文自适应分词
未来的分词器可能是动态的:
- 理解当前任务是什么
- 理解上下文语义
- 自动决定该切多细
比如做文本分类时切粗一点,做机器翻译时切细一点;遇到常见词切粗一点,遇到生僻词切细一点。
这需要分词器和下游任务深度结合,而不是一个独立的预处理步骤。
10.2 多模态统一分词
多模态大模型兴起后,分词的概念正在扩展。
10.2.1 从文本Token到统一Token
以前,Token只针对文本。现在图像、音频也有了Token的概念:
- 图像:把图片切成一个个Patch,每个Patch就是一个视觉Token
- 音频:把音频切成一个个片段,每个片段就是一个音频Token
未来的多模态模型,可能会有一套统一的Token体系——文本、图像、音频都被编码到同一个Token空间里。
10.2.2 分词器的新使命
传统分词器只处理文本。未来的”分词器”可能要处理各种模态:
- 文本怎么切
- 图像怎么分块
- 音频怎么分段
- 不同模态的Token怎么对齐
这已经超出了传统分词的范畴,但本质思想是一样的——把连续的信号切成离散的单元,供模型处理。
10.3 低资源语言适配
现在的分词技术,对中文、英文这些高资源语言支持得很好,但对小语种、少数民族语言支持还很差。
10.3.1 少样本/零样本分词
未来的方向是:不需要大量标注数据,只用很少的样本甚至零样本,就能让分词器适应新语言。
比如用大模型的跨语言迁移能力——大模型已经学会了通用的语言规律,稍微给点提示就能做新语言的分词。
10.3.2 无监督分词
能不能完全不用标注数据,让模型自己从原始文本中学会分词?
这是一个很有吸引力的方向。人类小孩学说话,没人教他分词,听多了自然就知道哪里是词边界。模型能不能也这样?
目前已有一些无监督分词的研究,但效果还不如监督方法。随着技术进步,这可能是下一个突破点。
10.4 “去分词化”的探索
最激进的方向是:彻底取消分词器这个步骤。
10.4.1 为什么要去分词化
支持”去分词化”的理由:
- 分词是人为的预处理,不一定是最优的。模型也许有更好的切分方式,只是人类没想到。
- 误差传播:分词错了,后面全错。如果不分词,就没有分词错误。
- 语言公平:所有语言一视同仁,不用为每种语言设计分词器。
- 鲁棒性强:对噪声、乱码、特殊字符不敏感。
10.4.2 代表性研究
ByT5(Google,2021) 直接在UTF-8字节上做T5模型,完全不需要分词。输入就是字节序列,输出也是字节序列。
ByT5的特点:
- 没有[UNK],任何文本都能处理
- 对拼写错误、噪声更鲁棒
- 在形态学、音译等任务上表现更好
- 缺点:序列太长,计算量大,速度慢
MEGABYTE(Meta,2023) 针对字节序列太长的问题,MEGABYTE提出了层次化方案:
- 全局模型:处理块级别的长距离依赖
- 局部模型:处理块内的字节细节
用分而治之的思路,解决了纯字节模型计算量大的问题。
H-Net(CMU,2025) 通过动态分块机制,自适应地将字节流分割成语义单元。分块不是固定的,而是根据内容动态决定的。
这已经非常接近”模型自己学会分词”的理想了。
ByteFlow(亚马逊,2025) 基于压缩率的自适应分块策略。哪里信息密度高就切得细,哪里信息密度低就切得粗。
10.4.3 去分词化的挑战
去分词化虽然美好,但目前还有很多挑战:
- 计算效率:字节序列比Token序列长好几倍,计算量太大
- 训练难度:直接在字节上学语义更难,需要更多数据和算力
- 推理速度:生成速度慢,不实用
目前来看,子词分词在效率和效果的平衡上还是最优的。去分词化还处在实验室阶段。
10.5 分词技术的终极形态猜想
最后,我来大胆猜想一下分词技术的终极形态。
猜想一:分词器消失,融入模型 未来的模型可能不再有独立的分词步骤。输入原始字节流,模型内部自动学习合适的表示单元。分块是动态的、自适应的,甚至是不可解释的。
猜想二:统一的多模态Tokenizer 未来会有一个统一的Tokenizer,能处理文本、图像、音频、视频等所有模态。所有模态都被编码到同一个Token空间,模型在统一的表示上推理。
猜想三:语义Token取代符号Token 现在的Token是符号级的(字符、子词、词)。未来可能是语义级的——每个Token对应一个概念,而不是一串字符。模型直接在概念层面运算。
这些猜想听起来有点科幻,但技术发展往往超出想象。二十年前的人想不到今天的大模型,我们也很难想象二十年后的NLP会是什么样子。
但有一点可以确定:只要人类还在和机器用语言交流,“如何把语言切分成机器能处理的单元”这个问题就会一直存在。形式可能会变,但本质不变。
第十一章:动手实践:从零理解分词
光说不练假把式。这一章我们动手写点代码,实际感受一下分词器是怎么工作的。
11.1 手写一个最简单的正向最大匹配
我们来实现一个最基础的正向最大匹配分词器。十几行代码就能搞定。
def forward_max_match(text, word_dict, max_len=4):
"""
正向最大匹配分词
text: 待分词文本
word_dict: 词典(集合)
max_len: 词典中最长词的长度
"""
result = []
index = 0
while index < len(text):
# 取当前位置开始的max_len个字,如果不够就取到末尾
end = min(index + max_len, len(text))
# 从长到短尝试匹配
while end > index:
word = text[index:end]
if word in word_dict:
break
end -= 1
# 如果都没匹配上,就取单个字
if end == index:
result.append(text[index])
index += 1
else:
result.append(text[index:end])
index = end
return result
# 测试一下
word_dict = {"研究", "研究生", "生命", "起源", "南京", "市长", "长江", "大桥", "南京市"}
text1 = "研究生命起源"
text2 = "南京市长江大桥"
print("正向最大匹配结果:")
print(f"{text1} → {' / '.join(forward_max_match(text1, word_dict))}")
print(f"{text2} → {' / '.join(forward_max_match(text2, word_dict))}")运行结果:
正向最大匹配结果:
研究生命起源 → 研究生 / 命 / 起源
南京市长江大桥 → 南京市 / 长江 / 大桥你看,“研究生命起源”切错了,但”南京市长江大桥”切对了。这就是正向最大匹配的特点——有时候对有时候错,完全看句子结构。
你可以试试把方向反过来,实现一个逆向最大匹配,看看”研究生命起源”是不是就能切对了。
11.2 Jieba分词入门实战
接下来我们试试工业界最常用的Jieba分词。
首先安装:
pip install jieba然后体验三种模式:
import jieba
text = "我来到北京清华大学,喜欢自然语言处理技术"
# 精确模式
seg_list = jieba.lcut(text, cut_all=False)
print("精确模式:", " / ".join(seg_list))
# 全模式
seg_list = jieba.lcut(text, cut_all=True)
print("全模式:", " / ".join(seg_list))
# 搜索引擎模式
seg_list = jieba.lcut_for_search(text)
print("搜索引擎模式:", " / ".join(seg_list))运行结果大概是这样:
精确模式: 我 / 来到 / 北京 / 清华大学 / , / 喜欢 / 自然语言 / 处理 / 技术
全模式: 我 / 来到 / 北京 / 清华 / 清华大学 / 华大 / 大学 / 喜欢 / 自然 / 自然语言 / 言语 / 处理 / 技术
搜索引擎模式: 我 / 来到 / 北京 / 清华 / 华大 / 大学 / 清华大学 / , / 喜欢 / 自然 / 言语 / 自然语言 / 处理 / 技术可以看到三种模式的区别:
- 精确模式最干净,每个词只出现一次
- 全模式把所有可能的词都扫出来,有重叠
- 搜索引擎模式介于两者之间,对长词做了二次切分
再试试加载自定义词典:
# 假设我们有一个专业领域的词
text = "非小细胞肺癌靶向治疗效果不错"
# 默认分词
print("默认:", " / ".join(jieba.lcut(text)))
# 加载自定义词典
jieba.add_word("非小细胞肺癌")
jieba.add_word("靶向治疗")
print("加词典后:", " / ".join(jieba.lcut(text)))加了自定义词典后,专业术语就能被完整识别了。这在垂直领域非常有用。
11.3 用HuggingFace体验子词分词
我们来体验一下大模型的子词分词器。需要安装transformers库:
pip install transformers以BERT的分词器为例:
from transformers import BertTokenizer
# 加载BERT-base中文分词器
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
text = "我爱自然语言处理"
# 分词
tokens = tokenizer.tokenize(text)
print("Token:", tokens)
# 转成ID
ids = tokenizer.convert_tokens_to_ids(tokens)
print("Token ID:", ids)
# 完整编码(自动加特殊标记)
encoded = tokenizer.encode(text)
print("完整编码:", encoded)
print("解码回来:", tokenizer.decode(encoded))运行结果大概是:
Token: ['我', '爱', '自', '然', '语', '言', '处', '理']
Token ID: [2769, 4263, 5632, 4197, 6427, 6241, 1905, 4415]
完整编码: [101, 2769, 4263, 5632, 4197, 6427, 6241, 1905, 4415, 102]
解码回来: 我 爱 自 然 语 言 处 理注意到了吗?BERT中文分词器把”自然”、“语言”、“处理”都拆成了单字。这是因为BERT-base中文词表比较小(约2.1万个Token),很多词没有整词,只能拆成单字。
我们再试试GPT的分词器,用tiktoken:
import tiktoken
# 加载GPT-3.5的分词器
enc = tiktoken.encoding_for_model("gpt-3.5-turbo")
text = "我爱自然语言处理"
# 编码
tokens = enc.encode(text)
print(f"Token数量: {len(tokens)}")
print(f"Token ID: {tokens}")
# 看看每个Token对应的文本
for token in tokens:
print(f" {token} → {enc.decode([token])}")你会发现,GPT处理中文时,很多是两字一个Token,比BERT的单字要好一些。这就是词表更大带来的好处。
11.4 可视化:看看一句话是怎么被切分的
为了更直观地理解,我们可以写个简单的可视化函数,把不同分词器的结果并排对比:
def compare_tokenizers(text, tokenizers_dict):
print(f"原文: {text}")
print("-" * 50)
for name, tokenize_func in tokenizers_dict.items():
tokens = tokenize_func(text)
print(f"{name:15s}: {' | '.join(tokens)}")
print()
# 定义各种分词函数
import jieba
import tiktoken
from transformers import BertTokenizer
enc = tiktoken.encoding_for_model("gpt-3.5-turbo")
bert_tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
def jieba_tokenize(text):
return jieba.lcut(text)
def gpt_tokenize(text):
return [enc.decode([t]) for t in enc.encode(text)]
def bert_tokenize(text):
return bert_tokenizer.tokenize(text)
# 对比
compare_tokenizers("自然语言处理很有趣", {
"Jieba分词": jieba_tokenize,
"GPT子词": gpt_tokenize,
"BERT子词": bert_tokenize,
})运行之后,你就能清晰地看到不同分词器对同一句话的切分差异。
11.5 常见坑与避坑指南
最后分享几个实际使用中的常见坑:
坑1:中英文混合处理不好 很多分词器对中英混合的文本处理一般。比如”iPhone14手机”,可能切得乱七八糟。
- 避坑:加自定义词典,把品牌型号都加进去
- 或者用专门的混合语言分词器
坑2:数字和单位被切开 “100元”被切成”100”和”元”,“50kg”被切成”50”和”kg”。有时候你可能希望它们在一起。
- 避坑:用正则后处理,把数字和单位合并
- 或者自定义词典强制合并
坑3:停用词没处理好 分词之后通常要去停用词,但停用词表不是通用的。不同场景停用词不一样。
- 避坑:根据业务场景定制停用词表
- 不要随便用网上随便找的停用词表
坑4:繁体字、异体字 很多分词器对简体支持好,对繁体支持差。
- 避坑:先做繁简转换,再分词
- 或者用支持繁体的分词器
坑5:特殊符号、emoji 特殊符号、emoji处理不好常常导致分词错误。
- 避坑:预处理时先做规范化
- 子词分词器(BBPE)在这方面表现更好
第十二章:总结与展望
写到这里,我们的分词器之旅就要告一段落了。让我们最后总结一下。
12.1 核心要点回顾
- 分词是什么:把连续的文本切分成有意义的语言单元,是NLP的第一步。
- 为什么重要:计算机不认识词,只认识数字。分词是人类语言到机器表示的桥梁。
- 发展历程:从规则匹配→统计模型→深度学习→子词分词→去分词化探索。
- 传统中文分词:最大匹配、HMM、CRF、深度学习,核心是解决边界问题。
- 子词分词:BPE、WordPiece、Unigram、SentencePiece,核心是解决OOV问题。
- 中英文差异:英文有空格,重点是词形还原;中文无分隔,重点是边界识别。
- 三大挑战:歧义消解、未登录词、领域适配。
- 应用场景:搜索引擎、机器翻译、文本分类、大模型、各行各业。
- 未来趋势:语义感知、多模态统一、低资源适配、去分词化。
12.2 分词技术的本质
回顾整个发展史,你会发现一个有趣的规律:分词技术一直在”分”与”合”之间摇摆。
- 最早是字符级——每个字单独处理,太散
- 然后是词级——整词处理,语义完整,但OOV严重
- 现在是子词级——介于字和词之间,平衡了语义和开放性
- 未来可能回到字节级——但这次是模型自动学习,不是人工切分
这不是简单的轮回,而是螺旋式上升。每一次回到更小的粒度,都是因为模型能力变强了,能处理更细粒度的信息了。
分词的本质是什么?我认为是在”表示粒度”和”处理能力”之间找平衡。
- 模型能力弱的时候,需要更大的粒度(整词),因为模型处理不了太细的单元
- 模型能力强的时候,可以用更小的粒度(子词、字节),因为模型能自己组合语义
模型能力越来越强,分词粒度就越来越细。直到有一天,模型强大到可以直接处理原始字节流,分词器这个角色就可以退场了。
12.3 给学习者的建议
如果你正在学习NLP,关于分词我有几个建议:
- 理解原理,但不要死钻:分词是基础,但不是NLP的全部。理解核心思想和主流算法就够了,不用深入到每个细节。
- 善用工具,不要重复造轮子:工业界有大量成熟的分词工具,直接用就好。绝大多数场景下,你不需要自己写分词器。
- 关注业务效果,不要追求极致准确率:分词准确率97%还是98%,对最终业务影响可能很小。把精力放在更重要的地方。
- 子词分词是现在的重点:传统中文分词的重要性在下降,子词分词是大模型时代的核心,值得深入理解。
- 保持好奇,关注前沿:去分词化、多模态这些新方向很有意思,代表了未来的趋势。
12.4 最后的话
分词是一个非常经典的NLP问题。它看起来简单,似乎就是把句子切成词而已。但深入进去你会发现,这里面蕴含着语言的复杂性、人工智能的局限性,以及几代研究者的智慧。
从最早的查字典,到统计模型,到深度学习,再到子词分词,每一步前进都来之不易。每一个百分点的准确率提升,背后都是无数论文和实验。
也许有一天,分词技术会被更先进的方案取代,成为历史。但它在NLP发展史上的地位不会被忘记——它是计算机理解人类语言的第一道门槛,是无数NLP研究者的起点。
希望这篇长文能帮你真正理解分词器。当你下次使用搜索引擎、和AI对话的时候,能想起背后有这么一个默默工作的小角色,它把人类的语言切碎、编码,送进模型的世界。
这就是分词器的故事——平凡,却不可或缺。
