
jieba和BERT-wwm这两件事放在一起讲看起来跨度有点大一个是讲究规则统计的传统分词工具一个是预训练大模型中间隔着一整个深度学习时代。但放在这个系列的第十二篇来聊恰恰是想把一个关键问题说清楚中文NLP的底层逻辑从“词”到“向量”再到“语义”到底是怎么演进的以及你在实际项目里应该怎么选。很多人刚接触中文NLP的时候第一步就是用jieba分词做完分词、去停用词、统计词频感觉流程很顺。但等到开始接触BERT、想用预训练模型做文本分类、NER、语义匹配的时候突然发现之前的“分词经验”好像用不上了——BERT根本不需要jieba分词它自己用WordPiece把句子拆成子词甚至单字。于是很多人就懵了那我学jieba还有意义吗直接学BERT不就行了这篇文章把这两条路线放在一起讲就是要解决这个困惑。我会结合这几年做过的实际项目把jieba分词的原理边界、BERT-wwm的设计思路、两者之间怎么衔接配合以及落地时的参数细节和坑都摊开聊。1. 为什么要把jieba和BERT-wwm放在一起讲1.1 从词到向量的两步跨越中文NLP项目的处理管线本质上是在做一件事把一段人类语言变成机器能算的东西。这个“变”的过程传统路线是“分词 → 词向量”深度学习的路线是“序列 → 上下文动态向量”。这两条路线不是替代关系而是站在不同的抽象层次上理解语言。jieba分词解决的是“词在哪里切分”的问题。中文不像英文天然用空格分好词了计算机要处理中文第一步得知道哪些汉字组合起来是有意义的单元。传统方法做文本挖掘、关键词提取、情感分析都依赖这一步的准确性。BERT-wwm解决的是“切分出来的单元在特定上下文里是什么意思”的问题。这比“词在哪里切”复杂得多。同一个词在不同句子里语义是动态变化的——比如“苹果”在“苹果熟了”和“苹果发布了新手机”里指向完全不同静态的词向量解决不了这种问题但预训练模型天然就是为这个设计的。这两步并不是非此即彼的关系。理解jieba能帮你理解中文的语言学结构和词法规律理解BERT-wwm能帮你理解深度语义建模是怎么绕过显式分词直接学出隐含边界的。实际项目里两条路线也经常配合使用后面我会细讲。1.2 本系列前11篇的演进脉络这个系列从最基础的文本读写、正则清洗讲起中间花了很大篇幅讲jieba分词的DAG构建、HMM新词发现、TF-IDF和TextRank关键词提取。那些方法到今天依然是很多生产系统的主力方案——轻量、可解释、不依赖GPU尤其在资源受限的场景下传统方法依然是最务实的选择。但说句实话传统方法的天花板也很明显。基于词频和共现统计的方法没法建模长距离依赖也没法区分“我喜欢这个苹果”和“这个苹果喜欢我”这种语序差异背后的语义区别。统计模型对多义词、口语化表达、反讽语气基本束手无策。所以从这一篇开始我们把视角切换到预训练模型。这个转折点选择得很自然——因为BERT系列模型的输入层其实还在做某种意义上的“分词”只是粒度从“词”变成了“子词”。理解过jieba的分词逻辑再来看BERT的WordPiece分词会更容易理解它为什么拆成那样。2. jieba分词的边界为什么传统方案不够用了2.1 基于规则与统计的经典实现jieba的本质是一个带词典的统计分词器核心机制是构建有向无环图DAG然后使用动态规划求解最大概率路径。它结合了词典匹配和基于HMM隐马尔可夫模型的新词发现能力能够识别不在词典里的人名、地名、音译词。这套机制在实际工业场景里经过了充分验证。在配置了良好自定义词典的前提下jieba在通用领域文本上的分词准确率可以做到95%以上处理速度能达到每秒几十万字的级别完全不依赖GPU。这个性价比让它在日志分析、舆情监控、垂直搜索等系统里用了很多年。但它的短板从诞生那天就注定了。统计模型的本质是“猜”基于词频和转移概率猜测最可能的切分方式它不理解“词语在语境中的真实意义”。比如“研究生命科学”这句话人是通过语义来判断该切成“研究生命科学”还是“研究生命科学”而jieba只能靠概率选一个选错也不会有任何语义层面的察觉。2.2 语义理解这道坎统计模型迈不过去我做过一个真实的项目做的是某电商平台的用户评论细粒度情感分析。用jieba分词加情感词典的方案基础的情感倾向判断没问题但一旦涉及“这个手机续航还行就是充电慢得让人崩溃”这种句式基于词典的方法就崩了——因为“还行”和“崩溃”出现在同一个句子里得分抵消后系统判断不出这条评论本质上是对续航不满还是对充电不满。这就是传统方案的边界没有上下文语境建模能力。而预训练模型通过Transformer里的自注意力机制每个词在编码时能看到整句话的所有其他词并根据任务动态调整词的表征。这在架构层面解决了“一词多义”和“长距离依赖”两个核心问题。这也是为什么今天稍微复杂一点的NLP任务主流方案几乎都转向了预训练模型。不过这并不意味着jieba就没用了。具体什么场景下还要用我会在最后一节专门讲清楚。3. BERT-wwm的核心原理全词掩码到底改了什么3.1 从WordPiece到中文全词掩码要对中文使用BERT先得了解它是怎么切词的。BERT使用的是WordPiece分词方法这是一种介于词和字符之间的切分方式。它会根据训练语料里的统计信息把常用词保留为完整token罕见词则拆成更小的片段。对中文由于汉字本身就是最小语义单元BERT大多数情况下直接把每个汉字当作一个token处理。原始BERT的预训练任务之一是掩码语言模型Masked Language ModelMLM做法是先遮挡句中15%的token然后让模型根据上下文预测被遮挡的内容。在英文里被遮挡的可能是完整的词但在中文里因为默认按字切分所以遮挡的是单个汉字。比如“我喜欢吃苹果”随机遮住“苹”字让模型预测这个字是什么。这里的问题在于中文的语义单位是词而不是字。“苹果”是一个语义整体如果你只遮住“苹”模型可以从“果”字轻易推断出答案这降低了任务的难度也让模型不需要真正理解“苹果”这个词的整体语义。这就让原始BERT在中文上的预训练效果打了折扣。BERT-wwm的改进思路很直接把同一个词的所有汉字一起遮住。这里的“词”就需要分词工具来界定——论文中用的正是与机器学习工具库兼容的分词工具把文本切分成词后对属于同一个词的汉字统一遮住。这样模型就只能通过上下文信息来推断整个词的语义而不是通过词内部的字来“作弊”。3.2 为什么全词掩码对中文更有效全词掩码在中文场景下的效果提升是实实在在的。按字掩码的时候模型学到的更多是汉字间的共现规律而按词掩码之后模型被迫学习的是词汇层面的语义组合能力。这个差异在一系列中文NLP基准测试中都有体现BERT-wwm相比原始BERT在文本分类、命名实体识别、阅读理解等任务上都有稳定提升尤其在需要精细理解词汇语义的任务上更为明显。实际用的时候还有一个明显感受全词掩码让模型对词边界的感知更强。虽然BERT在下游任务微调时不做MLM了但预训练阶段学到的词级语义表征是“带在参数里”带过来的。用BERT-wwm做句向量或者序列标注对词语边界的敏感性比基础BERT要好这对中文任务命名实体识别、分词等本身就需要边界判断的任务是实打实的优势。打个不严谨但容易理解的比方原始BERT在预训练时看到了很多“被挖掉一个字”的句子像一个学生总是做选词填空题而BERT-wwm看到的则是“被挖掉一个完整词”的句子像是被要求根据上下文写出句子中某个词组的意思。第二种训练方式显然逼着学生更深地理解语义。4. 实操落地在项目里使用BERT-wwm4.1 环境准备与模型加载使用BERT-wwm做项目最省事的方案是基于Hugging Face的Transformers框架。我目前的推荐组合是Python 3.8以上环境 PyTorch 1.10以上 Transformers 4.x 对应版本的CUDA驱动的GPU。如果没有GPUCPU也能跑只是速率会明显慢一些。模型选择上中文项目我一般从Hugging Face模型库中加载HFL发布的中文预训练模型全称为chinese-bert-wwm-ext。相比基础版的BERT-wwm它用了更大的训练数据集和更多训练步数实测效果更稳定。加载模型和分词器的代码非常简单from transformers import BertTokenizer, BertModel model_name hfl/chinese-bert-wwm-ext tokenizer BertTokenizer.from_pretrained(model_name) model BertModel.from_pretrained(model_name) # 设置为评估模式 model.eval()这里需要注意一个容易犯的错Hugging Face的模型下载需要访问境外模型仓库很多团队的网络环境会导致下载中断。建议在服务器配置好镜像源比如把HF_ENDPOINT环境变量指向可访问的镜像地址提前把模型文件下载后转存到内部文件服务器或对象存储然后从本地加载# 从本地路径加载模型 model BertModel.from_pretrained(/ data/models/chinese-bert-wwm-ext)这个看似不起眼的细节在实际团队协作里能省下大量时间——尤其是项目需要打包上线、多机部署的时候不要指望每台机器都能临时去外网下载模型。4.2 句子向量化的完整流程把一个句子变成BERT-wwm的向量表示标准的流程是分词tokenize→ 加上特殊标记 [CLS] 和 [SEP] → 生成attention mask → 过模型 → 取 [CLS] 向量或对token向量做池化。完整代码如下import torch def encode_sentence(text, max_len128): # tokenizer自动完成添加 [CLS] 和 [SEP] 的工作 encoded tokenizer( text, max_lengthmax_len, truncationTrue, paddingmax_length, return_tensorspt ) input_ids encoded[input_ids] attention_mask encoded[attention_mask] # token_type_ids 在单句任务中不需要 with torch.no_grad(): outputs model(input_ids, attention_maskattention_mask) # outputs[0] 是每个token的向量表示shape: [batch, seq_len, hidden_size] # outputs[1] 是句子的 [CLS] 向量shape: [batch, hidden_size] sentence_embedding outputs[1] return sentence_embedding这里有两个实操要点值得展开。第一个是max_len的选择。BERT的输入长度上限是512个token但对大多数文本分类、相似度计算任务来说128长度已经覆盖了绝大部分场景。如果你处理的文本很长放弃前面128个token而截断后面的内容是不合理的——建议做策略性的截断开头和结尾都保留一部分因为很多关键信息出现在句首和句尾。第二个是取向量的方式。代码里取的是[CLS]位置的向量这是BERT设计的语义汇总位。实践中有时候把最后一层所有token向量做mean pooling或者max pooling效果会比[CLS]更好——具体来说文本匹配任务中mean pooling在短文本场景会稳定一些分类任务中[CLS]表现不错。建议在项目里把两种方式都跑一下对比选择验证集表现更好的成本很低但收益往往不小。4.3 下游任务微调的例子文本分类实际的业务系统里直接用预训练输出做下游任务比如算句向量相似度是常见用法但要做高精度的分类模型就需要微调。微调的本质是在预训练模型基础上加一个任务头让模型的参数在“理解中文”的基础上进一步适配你的特定任务。以文本分类为例BertForSequenceClassification这个类帮你封装了模型和分类层from transformers import BertForSequenceClassification, AdamW model BertForSequenceClassification.from_pretrained( hfl/chinese-bert-wwm-ext, num_labels4 ) # 数据加载部分省略假设 train_dataloader 已准备好 optimizer AdamW(model.parameters(), lr2e-5)训练时的核心参数有几个很关键学习率、batch size、训练轮数。学习率在微调时建议设置在2e-5到5e-5之间不要用大学习率因为预训练模型的参数已经收敛得很好学习率过大容易把学好的语义表征冲掉。BERT系列模型常用Adam优化器并配合warmup策略——先线性增加学习率再缓慢衰减这能让模型在训练初期更稳定。batch size方面BERT模型的显存占用比传统模型高出很多。一个典型的配比是12GB显存、max_len128的情况下batch size大概能开到16到32之间。如果显存不够可以用梯度累积模拟更大的batch size效果在多数情况下一致。训练轮数不要贪多2到4个epoch在大多数中文分类任务上足够。轮数过多容易过拟合且出现过拟合时模型在训练集上的准确率继续上升但验证集开始下降。建议在验证集上保存最优模型训练完成后加载最优ckpt做推理而不是直接用最后一轮的权重。5. 分词器和BERT模型怎么配合实战要点5.1 中文BERT实际上怎么切词很多入门者会下意识地用jieba先切好词再把词列表输入BERT。这个做法属于画蛇添足而且会降低效果。BERT自带的WordPiece分词器拿到一个句子后如果是中文它会按照字符进行切分并且会保留一些常用词作为完整token比如很多词典中包含的“我们”、“中国”等词会直接作为整体出现。理论上讲你可以对BERT的分词器传预分词的结果比如用jieba切好词之后用空格连接再传入这样WordPiece会按空格来切分。但实践下来的结论是不要这么做。原因是BERT在预训练时见过的输入是没有空格分隔的原始中文句子你用空格改变了输入分布模型反而会不适应表现会更差。正确的做法是让BERT分词器按自己的方式处理句子。WordPiece对中文按字切分的本质意味着模型不依赖于显式的词边界信息而是在自注意力机制中通过大量语料学出了隐含的词边界。5.2 自定义词典在BERT场景下的替代方案jieba项目里最大的优势之一是可以加载自定义词典让领域术语、人名、产品名正确切分。换到BERT场景这个需求变成了“领域词汇的语义表达不准确”的问题。比如医药领域的专业术语、工业场景的零部件名称通用BERT模型的词表中没有对应词按字切分后每个字单独表示语义信息就碎了。解决这个问题的思路和jieba加自定义词典其实异曲同工如果你的业务文本中有大量专有词汇考虑在通用模型基础上用领域语料继续预训练。这叫做领域自适应预训练Domain-Adaptive Pretraining用你所在领域的无标注文本对通用BERT-wwm继续做MLM训练让模型熟悉领域文本的用词习惯和表达方式。具体操作起来没想象中那么重不需要从头训练一个大模型。加载现有权重后在你的行业语料上再跑几轮MLM一般几千条几万条文本就能带来比较明显的领域适配效果。我做过一个医疗器械领域的项目用一万多条产品说明书和技术文献继续预训练了两轮在下游的实体识别和意图分类任务上F1值分别提升了3到5个百分点。这个投入产出比相当划算。6. 常见问题与排查指南6.1 显存不足与推理速度慢用BERT系列模型最先遇到的坑就是显存。我见过很多入门者在8GB显存的显卡上跑BERT-large训练直接OOMOut of Memory。解决思路从易到难排序先减小batch size降到4甚至2使用梯度累积模拟更大的batch如果还不行考虑换成BERT-base模型速度提升明显而精度损失通常在可接受范围内再不行就只能用模型蒸馏或者轻量化模型方案了。推理速度也是一个需要考虑的问题。BERT-base在CPU上的单句推理耗时大约在几十毫秒到上百毫秒之间在GPU上的推理时间会缩短很多但也没有想象中那么快。如果在高并发的在线接口里直接用BERT需要加缓存、做批处理或考虑用蒸馏小模型。除非你的系统并发不高或者对响应时间容忍否则还是建议把BERT放在离线分析链路里在线实时接口用轻量模型。6.2 分词结果与模型输出的“不一致”有朋友在项目里做文本预处理时先用jieba分词提取了关键词做特征又用BERT做深度语义特征最后把两者拼接起来输入到下游任务模型。结果发现效果反而变差了。这个现象其实很常见。原因可以拆成两层。第一层jieba分词后的词频、TF-IDF特征代表的是“关键词统计视角”的信息BERT的句向量代表的是“深层语义视角”的信息两者关注的维度不同简单拼接会产生冗余。第二层也是最容易被忽视的就是BERT处理的是整句对切分错误并不敏感而jieba切错词产生的噪声会直接影响统计特征的质量。建议的做法是要么只用传统方法做可解释的特征工程要么直接用BERT做端到端的语义建模。如果一定要二者结合不要简单拼接向量可以在BERT的输出基础上用jieba提取的词性、词边界信息作为辅助特征在分类层做融合而不是在特征层粗暴concat。维度/对比项jieba分词BERT-wwm核心原理词典匹配统计模型DAG动态规划HMMTransformer预训练MLM全词掩码输入粒度词子词中文场景基本按字语义建模无上下文建模能力自注意力全句上下文感知硬件需求CPU即可速度极快推荐GPU显存要求高适用场景关键词提取、文本清洗、可解释的统计特征分类、NER、语义匹配、阅读理解等深度任务部署成本极低可内嵌移动端较高需要模型服务化可解释性分词边界直观可查端到端黑盒解释困难最后再分享一个扩展思路如果你手头已经有了一整套基于jieba分词的统计NLP系统想引入BERT-wwm但又不确定从哪一步切入我的建议是先从最能让系统明显变强的环节下手。最典型的是给关键词提取结果做语义消歧——用jieba做初筛再用BERT-wwm对候选词做上下文语义验证把不符合语义的词过滤掉。这样既保留了原系统的速度优势又借助BERT-wwm补上了语义建模的短板。在项目演进的路线上这是一种成本更低、落地更平滑的方式比推倒重来稳得多。另外BERT-wwm在预训练阶段用了全词掩码这给了我们一个提示中文模型对词边界的先验认知很重要。如果业务场景中能拿到高质量的分词工具或领域词典不妨先对语料做词边界标注再输入到BERT做领域预训练。这个方式比单纯增加语料量更能带来模型能力的提升——分词信息和Transformer的结合其实还有不少值得挖掘的空间。