文本预处理基础:中文分词技术

做搜索引擎,第一步就是处理文本。而中文文本处理,最基础也最头疼的,就是分词。

英文天然用空格隔开,中文呢?字连着字,词连着词。比如「南京市长江大桥」,到底是「南京/市长/江大桥」还是「南京市/长江大桥」?

我刚开始做搜索时,就被这种歧义坑过。用户搜「长江大桥」,结果因为分词错了,返回了一堆「南京市长的新闻」。嗯,这体验就很糟糕了。

为什么中文必须分词?

搜索引擎的核心是建立倒排索引。说白了,就是「词 → 文档」的映射。

如果不分词,把整句话当索引项,那「北京欢迎你」和「欢迎你来北京」就完全匹配不上。用户搜「北京」,你返回不了任何结果。

分词之后,索引的是「北京」「欢迎」「你」这些词。用户搜任何一个,都能命中。这就是分词的底层逻辑。

中文分词的核心难点

你想想看,中文分词难在哪?我总结了三座大山:

  • 歧义切分:同一个句子,多种切法。比如「乒乓球拍卖完了」,是「乒乓球/拍卖/完了」还是「乒乓球拍/卖完了」?
  • 未登录词:词典里没有的词。人名「特朗普」、新词「给力」、专业术语「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))
# 输出:['我', '在', '北京大学', '北京', '大学', '学习', '机器学习', '机器', '学习']

我的建议:不要追求「完美分词」。搜索引擎里,召回比准确更重要。宁可多切几个词,也不要漏掉可能的查询意图。我一般会保留所有可能的切分结果,让后续的排序模块去决定哪个词更重要。

好了,这一章的内容就到这里。分词是搜索引擎的基石,看似简单,实则坑多。下一章我们聊聊词性标注和实体识别,看看怎么让机器「读懂」这些词的含义。