文本预处理基础:中文分词技术
做搜索引擎,第一步就是处理文本。而中文文本处理,最基础也最头疼的,就是分词。
英文天然用空格隔开,中文呢?字连着字,词连着词。比如「南京市长江大桥」,到底是「南京/市长/江大桥」还是「南京市/长江大桥」?
我刚开始做搜索时,就被这种歧义坑过。用户搜「长江大桥」,结果因为分词错了,返回了一堆「南京市长的新闻」。嗯,这体验就很糟糕了。
为什么中文必须分词?
搜索引擎的核心是建立倒排索引。说白了,就是「词 → 文档」的映射。
如果不分词,把整句话当索引项,那「北京欢迎你」和「欢迎你来北京」就完全匹配不上。用户搜「北京」,你返回不了任何结果。
分词之后,索引的是「北京」「欢迎」「你」这些词。用户搜任何一个,都能命中。这就是分词的底层逻辑。
中文分词的核心难点
你想想看,中文分词难在哪?我总结了三座大山:
- 歧义切分:同一个句子,多种切法。比如「乒乓球拍卖完了」,是「乒乓球/拍卖/完了」还是「乒乓球拍/卖完了」?
- 未登录词:词典里没有的词。人名「特朗普」、新词「给力」、专业术语「Transformer」。词典跟不上时代。
- 切分粒度:粗粒度还是细粒度?「北京大学」是当一个词,还是拆成「北京」和「大学」?搜索引擎里,我一般两种都要。
避坑指南:我曾经在做一个新闻搜索项目时,用户搜「特朗普访华」,结果分词把「特朗普」切成了「特朗」和「普」。因为词典里没有这个人名。从那以后,我养成了一个习惯——任何分词系统上线前,先跑一遍最新的人名、地名、产品名测试集。
主流分词工具对比
市面上分词工具很多。我这些年用过 jieba、HanLP、LAC,还有百度的、阿里的。今天重点聊三个最常用的。
jieba:入门首选,轻量快速
jieba 是我最早用的分词工具。它基于词典 + 统计,核心算法是前缀词典 + 动态规划。
优点很明显:安装简单、速度快、社区活跃。适合快速原型开发。
缺点呢?对未登录词处理一般,专业领域效果差一些。
import jieba
text = "我在北京天安门广场看升旗仪式"
words = jieba.lcut(text)
print(words)
# 输出:['我', '在', '北京', '天安门广场', '看', '升旗', '仪式']
# 添加自定义词典
jieba.add_word("天安门广场")
words = jieba.lcut(text)
print(words)
# 输出:['我', '在', '北京', '天安门广场', '看', '升旗', '仪式']
我的经验:jieba 的精确模式适合搜索场景。全模式虽然召回多,但噪声太大。我个人习惯用精确模式,再配合自定义词典做领域适配。
HanLP:功能全面,学术派
HanLP 是自然语言处理的全家桶。分词只是它的一小部分。它支持多种模型,包括基于感知机、CRF、甚至 BERT 的分词器。
我为什么推荐它?因为它的分词准确率确实高。尤其是对歧义的处理,比 jieba 好不少。
from hanlp import HanLP
text = "乒乓球拍卖完了"
# 使用默认分词器
words = HanLP.segment(text)
print(words)
# 输出:['乒乓球拍', '卖完', '了']
# 使用感知机分词器(更准确)
words = HanLP.segment(text, model='perceptron')
print(words)
# 输出:['乒乓球拍', '卖完', '了']
HanLP 的缺点?部署稍重,模型文件大。而且对新手来说,API 设计有点复杂。
LAC:百度的轻量利器
LAC 是百度开源的词法分析工具。它把分词、词性标注、实体识别打包在一起。我最近几个项目都在用。
它的优势是:速度快、模型小、支持自定义。而且对百度系的产品(如搜索、推荐)有天然适配。
from LAC import LAC
lac = LAC(mode='lac')
text = "我爱自然语言处理"
result = lac.run(text)
print(result)
# 输出:(['我', '爱', '自然语言处理'], ['r', 'v', 'n'])
# 自定义词典
lac.load_customization('custom.txt')
工具对比表
| 特性 | jieba | HanLP | LAC |
|---|---|---|---|
| 安装难度 | ⭐(极简) | ⭐⭐⭐(中等) | ⭐⭐(简单) |
| 分词准确率 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 处理速度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 未登录词处理 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 自定义词典 | 支持 | 支持 | 支持 |
| 词性标注 | 支持 | 支持 | 支持 |
| 适用场景 | 快速原型、小项目 | 学术研究、高精度需求 | 工业级应用、百度生态 |
注意:不要迷信任何一个工具。我见过有人用 jieba 做医疗搜索,结果「心肌梗死」被切成了「心肌」和「梗死」。虽然意思差不多,但索引时「心肌梗死」作为一个整体,召回率会更高。所以,一定要根据你的领域数据做评估。
分词歧义处理
分词歧义,是搜索引擎里最坑的地方。我把它分成两类:
交集型歧义
比如「研究生命科学」。可以切「研究/生命/科学」,也可以切「研究生/命/科学」。两种切法都合理,但意思完全不同。
怎么处理?我一般用两种方法:
- 统计方法:基于大规模语料,计算哪种切法的概率更高。比如「研究生命科学」在新闻语料中,「研究/生命/科学」的概率远高于「研究生/命/科学」。
- 规则方法:人工编写消歧规则。比如「生命科学」是一个固定搭配,优先保留。
组合型歧义
比如「把手」。可以是一个词(门把手),也可以是两个词(把/手)。
这种歧义更难处理。因为「把手」作为整体和分开,在语法上都成立。
我的做法是:
- 利用上下文信息。比如「请把手放在桌上」,「把手」是动词+名词,应该分开。
- 利用词性标注。如果「把手」后面跟着「放在」,那「把」大概率是介词。
避坑指南:我曾经做一个电商搜索,用户搜「苹果手机壳」。分词结果有「苹果/手机壳」和「苹果手机/壳」两种。前者认为用户要手机壳(苹果品牌),后者认为用户要苹果手机的壳。最后我们用了用户点击数据来训练消歧模型——用户搜「苹果手机壳」后,点击的是手机壳商品还是苹果手机?数据会告诉你答案。
实际项目中的分词策略
在搜索引擎里,我一般不会只用一种分词策略。而是采用「粗粒度 + 细粒度」的混合方案:
- 粗粒度:保留完整短语,比如「北京大学」「机器学习」。
- 细粒度:拆成最小单元,比如「北京」「大学」「机器」「学习」。
这样,用户搜「北京」能命中「北京大学」,搜「大学」也能命中。召回率和准确率都兼顾了。
# 混合分词示例
def mixed_segment(text):
# 粗粒度:使用自定义词典保留专有名词
jieba.load_userdict("domain_dict.txt")
coarse = jieba.lcut(text)
# 细粒度:使用标准词典
fine = jieba.lcut(text, cut_all=True)
# 合并去重
result = list(set(coarse + fine))
return result
text = "我在北京大学学习机器学习"
print(mixed_segment(text))
# 输出:['我', '在', '北京大学', '北京', '大学', '学习', '机器学习', '机器', '学习']
我的建议:不要追求「完美分词」。搜索引擎里,召回比准确更重要。宁可多切几个词,也不要漏掉可能的查询意图。我一般会保留所有可能的切分结果,让后续的排序模块去决定哪个词更重要。
好了,这一章的内容就到这里。分词是搜索引擎的基石,看似简单,实则坑多。下一章我们聊聊词性标注和实体识别,看看怎么让机器「读懂」这些词的含义。