1. 从字到词的鸿沟中文分词到底难在哪如果你做过搜索、推荐、知识图谱或者文本分类大概率绕不开一个前置环节把一串连续的中文汉字切成有意义的词。这件事在英文里几乎不算问题空格天然就是词的边界tokenizer.split( )一行代码搞定。但中文不一样句子是字字相连的词与词之间没有任何显式分隔符。于是南京市长江大桥这七个字到底该切成南京市/长江大桥还是南京/市长/江大桥就成了一个既考验语言学功底、又考验工程能力的问题。这就是**中文分词Chinese Word Segmentation简称CWS**要解决的核心任务。它的输入是一段连续汉字序列输出是带边界标记的词序列。别看任务描述简单它几乎是所有中文自然语言处理任务的第一道关卡。分词切错了后面的词性标注、命名实体识别、句法分析、语义匹配全都会跟着错误差会像滚雪球一样往下游传导。我见过太多团队在项目初期对分词不上心随手调个默认分词器就往下走结果到了模型效果上不去的时候回头排查才发现是分词把关键实体切碎了。比如深度学习框架被切成深度/学习/框架语义上勉强能接受但北京大学生前来应聘如果切成北京/大学生/前来/应聘和北京大学/生前/来/应聘意思完全是两回事。这种歧义切分就是中文分词最经典的难题之一。除了歧义还有两个老大难未登录词OOV和分词粒度。未登录词指的是词典里没有的新词比如新出现的网络热词、人名、品牌名、专业术语模型没见过就容易切错。分词粒度则是说同一个句子在不同场景下需要不同的切分标准——搜索引擎索引时希望粒度细一点召回更全而文本分类时希望粒度粗一点保留完整语义单元。这两个需求本身就存在张力没有一种切法能通吃所有场景。所以这篇内容我想系统地把中文分词这件事讲透。从最传统的基于词典的方法到基于统计的序列标注方法再到当下主流的深度学习方案以及大模型时代分词角色的变化我会把每一类方法的核心原理、适用场景、实操细节和踩坑经验都摊开来讲。不管你是刚接触NLP的新手还是想给现有系统换一套分词方案的工程师都能从中找到可以直接参考的东西。2. 基于词典的分词最朴素也最容易被低估的方案2.1 最大匹配法的三种变体与它们的致命伤基于词典的分词思路非常直白我手里有一本词典拿句子去词典里比对能匹配上就切出来。最经典的是最大匹配法Maximum MatchingMM又分正向、逆向和双向三种。正向最大匹配FMM的做法是从左到右扫描每次取尽可能长的、能在词典里找到的词。举个例子词典里有南京南京市市长长江长江大桥大桥句子是南京市长江大桥。FMM从南开始尝试最长匹配发现南京市在词典里于是切出南京市接着从长开始匹配到长江大桥切出。结果就是南京市/长江大桥正确。但换个句子就翻车了。他说的确实在理FMM可能切成他/说/的确/实在/理而正确切分应该是他/说/的/确实/在理。问题出在的确这个词优先被匹配了导致后面的边界全乱。逆向最大匹配BMM从右往左扫对上面这个例子反而能切对但遇到研究生命起源这种句子逆向又可能切成研究生/命/起源而正确是研究/生命/起源。于是有了双向最大匹配两种都跑一遍然后按规则选一个更优的结果。常用规则是谁切出来的词数少选谁因为长词优先通常更符合语义词数相同时选单字词少的那个。这套规则在大多数情况下能兜住但它本质上还是启发式没有真正理解语义。我实测下来的感受是最大匹配法在领域词典维护得好的前提下对短句、专有名词密集的场景比如地址解析、商品标题切分效果其实不差而且速度极快纯字符串匹配单机每秒能处理几十万甚至上百万字。但它的天花板也很明显——完全依赖词典词典里没有的词一律切不对歧义处理全靠规则硬扛。2.2 词典构建才是真正的工程量所在很多人以为基于词典的分词简单其实真正的工作量在词典上。一个可用的通用词典动辄几十万词条还要处理词频、词性、领域标签。更麻烦的是新词发现——网络热词、新品牌、新术语每天都在冒出来词典不更新分词就会持续出错。我一般会这样搭一套词典维护流程先用通用词典打底然后从业务语料里跑新词发现算法。最常用的是基于左右邻接熵和内部凝固度的无监督方法。内部凝固度衡量的是这几个字绑在一起的概率有多高左右邻接熵衡量的是这个词的左右两边能接多少种不同的字。两个指标都高说明它很可能是一个独立的词。具体计算上假设候选词是深度学习内部凝固度可以近似用点互信息PMI来算import math from collections import Counter def compute_pmi(word, char_freq, total_chars, word_freq, total_words): # 计算词内部各字之间的最小PMI作为凝固度 pmi_scores [] for i in range(len(word) - 1): left word[i] right word[i 1] p_left char_freq[left] / total_chars p_right char_freq[right] / total_chars p_pair word_freq[word] / total_words if p_left 0 and p_right 0 and p_pair 0: pmi math.log(p_pair / (p_left * p_right)) pmi_scores.append(pmi) return min(pmi_scores) if pmi_scores else 0左右邻接熵则是统计候选词左边和右边出现的字的分布熵越大说明它越像一个独立边界def compute_entropy(neighbor_counter): total sum(neighbor_counter.values()) entropy 0.0 for count in neighbor_counter.values(): p count / total entropy - p * math.log(p) return entropy把这两个指标设个阈值就能从海量语料里筛出一批候选新词再人工过一遍补充进词典。这套流程我在几个项目里都用过召回新词的效果比纯人工维护高一个数量级。注意阈值不要设太死宁可多召回一些让人工筛也别漏掉真正的新词因为漏掉的代价是下游持续出错。2.3 什么时候该用词典法什么时候该放弃我的经验判断标准很简单如果你的业务词典相对封闭、更新频率低、对速度要求极高词典法完全够用。比如做快递地址解析全国省市区街道的词典是有限的、可枚举的用词典法加规则准确率能到95%以上还省算力。但如果你面对的是开放域文本——社交媒体、新闻、用户评论新词层出不穷歧义满天飞那词典法就会力不从心。这时候必须上统计或深度学习方案。下面几节我会详细展开。3. 统计学习时代把分词变成序列标注问题3.1 从切词到打标签的思维转换统计方法最大的贡献是把分词问题重新定义成了序列标注问题。不再纠结这个词该不该切出来而是给每个字打一个标签标签决定了它和前后字的关系。最常用的是BMES标注体系BBegin词的首字MMiddle词的中间字EEnd词的末字SSingle单字成词比如我爱北京标注成S/S/B/E解码出来就是我/爱/北京。这个转换看似简单但它把分词变成了一个标准的机器学习任务可以套用各种成熟的序列标注模型。为什么这个转换这么重要因为它让分词从规则驱动变成了数据驱动。只要有标注语料模型就能自己学出切分规律不再依赖人工写规则。而且标注体系天然处理了未登录词——哪怕北京不在词典里只要模型见过类似的BE模式也能正确切出来。3.2 HMM与CRF两代统计模型的取舍最早被广泛使用的是隐马尔可夫模型HMM。它假设每个字的标签只依赖于前一个标签状态转移每个字本身由标签生成发射概率。训练时统计转移概率和发射概率预测时用维特比算法找最优标签序列。HMM的优点是训练快、解码快、实现简单在标注语料不大的时候表现稳定。但它有个硬伤观测独立性假设——它认为每个字只和当前标签有关和上下文其他字无关。这在中文里明显不成立的后面大概率跟名词了前面大概率是动词这些长距离依赖HMM抓不住。于是**条件随机场CRF**登场了。CRF直接对标签序列的联合概率建模可以灵活地引入各种特征当前字、前一个字、后一个字、是否在词典里、字的偏旁部首等等。它不做独立性假设能充分利用上下文。在相当长一段时间里CRF是中文分词的主流方案在标准评测集上F1能到95%以上。但CRF的代价是特征工程重、训练慢。你得手工设计一堆特征模板特征选得好不好直接决定效果。而且CRF训练时要跑迭代优化语料大了训练时间会很长。我早期做项目时用CRF训一个分词模型几百万字的语料要跑好几个小时调参更是折磨。下面是一个用CRF做分词的简化特征模板示例帮助理解它到底在看什么def extract_features(sentence, i): char sentence[i] features { char: char, char_prev: sentence[i-1] if i 0 else BOS, char_next: sentence[i1] if i len(sentence)-1 else EOS, char_prev2: sentence[i-2] if i 1 else BOS, char_next2: sentence[i2] if i len(sentence)-2 else EOS, is_digit: char.isdigit(), is_punct: not char.isalnum(), } return features可以看到CRF的特征是人工枚举的你能想到的上下文组合都可以加进去。这也是它的双刃剑灵活但费人力。3.3 标注语料从哪来一个绕不开的现实问题统计方法再优雅没有标注语料都是空谈。而中文分词的标注语料恰恰是稀缺资源。公开的标注数据集规模有限领域覆盖也未必匹配你的业务。自己标成本高、周期长、一致性还难保证。我的实操建议是分层获取语料第一层用公开标注数据打底让模型先有个基础能力第二层用远程监督拿现有词典去自动标注一批业务语料虽然噪声大但量大管饱第三层做主动学习让模型挑出它最不确定的样本人工只标这些标注效率能提升好几倍。主动学习的核心是不确定性采样。对每个句子模型预测出标签序列的概率分布如果某个位置的标签概率接近0.5说明模型很纠结这种样本信息量最大。把这些样本挑出来人工标注比随机标注的收益高得多。我在一个垂直领域项目里用这套方法只标了不到两万句就把分词F1从89%拉到了94%。4. 深度学习方案让模型自己学会什么是词4.1 BiLSTM-CRF曾经的标准答案深度学习进入NLP后分词方案很快被刷新。最经典的架构是BiLSTM-CRF用双向LSTM提取每个字的上下文表示再接一个CRF层做标签解码。为什么是双向LSTM因为中文里一个字的标签既取决于它前面的字也取决于它后面的字。研究生命里生这个字到底属于研究生还是生命光看前面看不出来必须结合后面的命才能判断。双向LSTM正好能同时捕捉前后文信息。为什么还要接CRF因为LSTM输出的每个位置的标签是独立预测的可能出现B后面直接跟B这种非法序列。CRF层引入了标签之间的转移约束保证输出的标签序列是合法的。这个组合在当时几乎是分词任务的标配在多个评测集上刷新了记录。用PyTorch搭一个简化版的BiLSTM-CRF核心结构大概是这样import torch import torch.nn as nn class BiLSTMCRF(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_tags): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim) self.lstm nn.LSTM(embed_dim, hidden_dim, bidirectionalTrue, batch_firstTrue) self.fc nn.Linear(hidden_dim * 2, num_tags) self.crf CRFLayer(num_tags) def forward(self, x, tagsNone, maskNone): emb self.embedding(x) lstm_out, _ self.lstm(emb) emissions self.fc(lstm_out) if tags is not None: return -self.crf.log_likelihood(emissions, tags, mask) return self.crf.decode(emissions, mask)这套架构的优点是端到端、不需要手工特征只要给够标注数据效果通常比CRF好。但它的训练成本也更高而且LSTM的序列建模能力有限长距离依赖还是抓得不够好。4.2 预训练模型微调把分词当成下游任务BERT这类预训练模型出来之后分词的做法又变了。不再从零训练而是拿预训练好的语言模型做底座在分词标注数据上微调。因为预训练模型已经在大规模语料上学到了丰富的语言知识微调时只需要少量标注数据就能达到很好的效果。具体做法很简单把分词当成逐字分类任务BERT输出每个字的向量表示接一个分类头预测BMES标签。损失函数用交叉熵优化器用AdamW学习率设小一点比如2e-5到5e-5训练几个epoch就能收敛。我实测下来用预训练模型微调在同样标注数据量下F1通常比BiLSTM-CRF高1到2个百分点而且对未登录词的泛化能力明显更强。因为预训练模型见过海量文本对这个词像不像一个词有更强的直觉。但这里有个容易踩的坑预训练模型的分词粒度可能和你的业务需求不一致。比如BERT自带的tokenizer对中文是按字切的你微调出来的分词器会继承它的某些偏好。如果你的业务需要特定粒度一定要在标注数据里体现出来让模型学到你的标准。4.3 大模型时代分词还需要单独做吗现在很多人会问大模型都能直接理解中文了还需要单独做分词吗我的答案是取决于你的场景。如果你只是调用大模型API做问答、生成那确实不用关心分词模型内部有自己的处理方式。但如果你要做搜索召回、倒排索引、关键词抽取、文本匹配这些任务分词依然是绕不开的基础设施。倒排索引必须基于词来建你不可能拿整句话去建索引。而且在这些场景里分词的质量直接决定了召回率和准确率。另一个变化是大模型可以辅助分词标注。你可以让大模型对一批语料做初步切分人工审核修正后再用来训练小模型。这样标注效率能提升不少。但要注意大模型的切分标准未必稳定同一句话问两次可能给出不同结果所以它更适合做预标注最终还得人工把关。5. 评测、调优与落地那些文档里不会写的细节5.1 评测指标怎么选才不误导自己分词评测最常用的指标是精确率P、召回率R和F1值而且通常按词来算不是按字。也就是说一个词的边界和标签完全正确才算对错一个字就算错。这里有个容易被忽略的细节评测时要区分**登录词IV和未登录词OOV**的指标。很多模型在登录词上F1能到97%以上但OOV上可能只有80%出头。如果你只看总体F1会误以为模型很好实际上它在真正难的地方表现很差。我建议评测报告里一定要把IV和OOV分开列这样才知道模型的短板在哪。另外不同数据集的标注标准不一样跨数据集比较F1意义不大。比如有的数据集把中华人民共和国当一个词有的切成中华/人民/共和国。你在A数据集上训的模型直接拿到B数据集评测F1掉几个点是正常的不一定是模型变差了。5.2 粒度对齐业务需求和标注标准的一致性前面提过分词粒度问题这里展开讲。同一个句子北京大学粗粒度切成一个词细粒度切成北京/大学。你的业务到底要哪种这个必须在标注阶段就定死否则模型学出来的东西是混乱的。我的做法是先和业务方对齐分词规范文档明确哪些情况切、哪些不切。比如人名职务是切开还是合并数字单位怎么处理英文中文混合怎么切。把这些规则写清楚标注员按规则标模型才能学到一致的标准。如果业务场景需要多种粒度可以考虑训练多个模型或者用多粒度分词方案——在标注时同时标出粗粒度和细粒度边界模型一次输出两套结果。这个方案实现复杂一些但能省去维护多个模型的麻烦。5.3 后处理规则小改动带来大提升模型输出之后加一层后处理规则往往能带来意想不到的提升。最常见的几类规则数字、日期、时间的合并模型可能把2024年3月切成2024/年/3/月但业务上希望它是一个整体。用正则匹配合并即可。英文单词和URL的保护模型对英文和符号的处理可能不稳定用正则先把它们识别出来避免被切碎。领域词典强制匹配对业务关键实体产品名、专有名词用词典做强制匹配保证不被切错。这些规则写起来不复杂但在实际业务里能显著提升体验。我在一个电商项目里光是加了商品品牌名的强制匹配规则搜索准确率就提升了3个百分点。注意规则不要写太多太杂否则维护成本会失控优先加那些高频、影响大的。5.4 部署与性能分词服务的工程考量分词通常是在线服务对延迟敏感。一个请求进来要在几十毫秒内返回结果。这时候模型大小和推理速度就很关键。如果用的是BERT类模型直接部署可能延迟偏高。可以考虑几个优化方向模型蒸馏用大模型教一个小模型速度能快好几倍效果损失有限量化把FP32转成INT8速度提升明显缓存对高频查询的分词结果做缓存避免重复计算。还有一个容易被忽视的点分词服务要能热更新词典。业务词典经常变如果每次更新都要重启服务体验很差。我的做法是把词典加载到内存里提供一个更新接口词典变了就重新加载服务不中断。6. 不同场景下的分词选型没有最好只有最合适6.1 搜索场景召回优先粒度偏细搜索引擎的分词核心目标是召回。用户搜苹果手机壳你希望既能召回苹果手机壳这个整体也能召回包含苹果手机壳的文档。所以搜索场景通常用细粒度分词甚至做多粒度索引——同一篇文档用多种粒度建索引查询时匹配任意一种都能召回。另外搜索场景要特别处理同义词和近义词。手机和移动电话要能互相召回这需要在分词之后接一层同义词扩展。还有停用词像的了是这些词在索引里意义不大可以过滤掉减小索引体积。6.2 文本分类与情感分析语义完整优先分类任务关心的是整体语义分词粒度太细反而会破坏语义单元。比如不太满意如果切成不/太/满意情感分析可能误判为正面切成不太/满意或者整体保留才能正确识别负面情感。所以分类场景我一般建议用粗粒度分词或者干脆用字级别输入配合模型自己学表示。现在很多文本分类模型直接用字向量效果不比词向量差还省去了分词的麻烦和误差传导。6.3 知识图谱与实体抽取边界准确是生命线做知识图谱实体边界的准确性至关重要。北京大学生到底是一个实体某类学生群体还是北京大学生直接影响图谱的节点和关系。这种场景下分词要和**命名实体识别NER**联合做不能分开。我通常的做法是分词和NER用同一个序列标注框架标签体系里同时包含分词标签和实体标签模型一次输出。这样能保证实体边界和分词边界一致避免分词切对了但实体边界错了的尴尬。6.4 方案对比速查表为了让你快速做选型决策我把几类方案的关键维度整理成表方案类型准确率速度未登录词维护成本适用场景词典最大匹配中极快差中词典维护封闭领域、地址、专名HMM中快中低资源受限、快速上线CRF高中中高特征工程有标注数据、追求效果BiLSTM-CRF高中好中通用场景、端到端预训练微调最高慢最好低微调即可效果优先、算力充足大模型预标注小模型高快好中标注成本敏感这张表不是绝对的实际选型还要看你的数据量、算力预算、延迟要求。我的建议是先用简单方案快速上线再根据badcase逐步升级不要一上来就上最复杂的模型。7. 我踩过的那些坑几条用教训换来的经验做分词这些年踩过的坑不少挑几个最有代表性的说说。第一个坑盲目追求SOTA模型。早期我总想用最新最复杂的模型结果发现业务数据量根本撑不起来模型过拟合严重还不如简单的CRF。后来我明白了模型复杂度要和数据量匹配。数据少的时候简单模型加好特征反而更稳。第二个坑忽略标注一致性。有一次项目里多个标注员标同一批数据结果发现他们对的字是否单独成词的标准不一致导致模型学得混乱。后来我们做了标注一致性检查定期抽样复核Kappa系数低于0.9就重新培训标注员。这个环节看似麻烦但能省下大量返工时间。第三个坑上线后不监控。分词模型上线不是终点。业务语料在变新词在冒模型效果会慢慢衰减。我现在的做法是定期抽样人工评估同时监控OOV比例和用户反馈一旦发现指标下滑就触发模型更新。这个机制能保证分词服务长期稳定。第四个坑把分词当成孤立环节。分词不是孤立的它和下游任务强相关。我见过团队分词F1很高但下游NER效果差原因是分词粒度和NER的实体边界不匹配。后来我们把分词和NER联合优化整体效果才上来。做分词一定要有全局视角盯着下游指标而不是只盯分词自己的F1。8. 写在最后分词这件事的变与不变从词典匹配到深度学习再到预训练模型和大模型辅助中文分词的技术方案一直在演进。但有些东西没变对语言的理解、对业务场景的把握、对数据质量的重视这些才是决定分词效果的根本。工具和模型会过时但把问题定义清楚、把数据搞干净、把评测做扎实这套方法论不会过时。我在实际项目里越来越觉得分词做得好不好技术方案只占三成剩下七成是对业务的理解和对细节的打磨。如果你正在做分词相关的项目我的建议是先用最简单的方案跑通全流程拿到baseline然后针对badcase逐个击破。不要一上来就追求完美也不要迷信某个最强模型。适合你业务场景的才是最好的。后续如果业务扩展了再考虑升级方案技术选型永远是为业务服务的。