文本预处理:让机器读得懂中文

熟悉我的朋友都知道,我做过不少从原始文档里抽知识图谱的项目。每次开始前,最花时间的往往不是搭建图谱,而是「洗数据」。说白了,你扔进一段“苹果很好吃”,机器怎么知道这说的是水果还是新发布的手机?

这就是文本预处理要解决的。中文没有天然空格,词和词之间挤在一起。所以想让后面的句法解析、命名实体识别发挥作用,预处理这步必须做扎实。嗯,这章我们就聊分词、词性标注和去停用词。这三个动作,是后续所有抽取代价的基础。

文本预处理核心流程 原始文本 分词 (Tokenization) 词性标注 (POS Tagging) 去除停用词 干净语料 后续抽取 (NER/关系) 🚧 常见坑 词典未更新 → 切分错误 停用词过狠 → 丢失语义

3.1 分词:第一步,也是最关键的一步

分词就是把连续汉字切分成独立的词语。你想想看,“南京市长江大桥”要是切分成“南京/市长/江大桥”,后面抽出来的知识图谱就全歪了。我个人习惯用 jieba,因为它够快,而且支持自定义词典。

基础用法很简单:

import jieba

text = "知识图谱技术正在快速发展"
words = jieba.lcut(text)
print(words)
# 输出: ['知识', '图谱', '技术', '正在', '快速', '发展']

但实际项目里文本会很复杂。比如“华为Mate60 Pro上市了”,如果不加词典,jieba很可能会把“Mate60”切成“Mate”和“60”。

解决方案?加自定义词典:

jieba.load_userdict("brand_dict.txt")
words = jieba.lcut("华为Mate60 Pro上市了")
print(words)
# 输出: ['华为', 'Mate60', 'Pro', '上市', '了']

brand_dict.txt 内容长这样:

华为Mate60 Pro 5 nz
小米14 Ultra 5 nz
iPhone 16 Pro 5 nz

每一行格式是「词语 词频 词性」。加了自定义词典后,准确率会明显提升。

小提示:如果遇到长文本,记得先删掉全角符号和多余空格。不然分词结果里会夹带一堆“ 。” “ , ”之类的垃圾。

3.2 词性标注:给每个词贴上标签

词性标注就是给每个词标记名词、动词、形容词等。为什么要做这一步?因为抽取实体关系时,你需要知道哪些词是人名,哪些是地点。

继续用 jieba:

import jieba.posseg as pseg

text = "我去北京参加人工智能大会"
words = pseg.lcut(text)
for word, flag in words:
    print(f"{word}: {flag}", end="  ")
# 输出: 我: r  去: v  北京: ns  参加: v  人工智能: n  大会: n

标记含义:r 是代词,v 是动词,ns 是地名,n 是名词。你可以在后续做实体抽取时直接按词性过滤——比如只保留 ns 和 n。

我在项目中遇到过,有时同一个词在不同语境下词性完全不同。比如“把门关上”和“一把刀”中的“把”。jieba 的词性标注基于统计模型,遇到这种歧义时可能会翻车。

关键点:词性标注的错误会直接传导到下游。如果你发现实体识别结果莫名其妙,先回头检查一下词性标注是不是错了。

3.3 去除停用词:去掉噪音

停用词就是“的、了、是、在”这类高频但没啥实际语义的词。留着它们会影响词频统计和 TF-IDF 计算。为啥?因为它们出现次数太多了,会淹没真正的关键词。

一个简单的实现:

stopwords = set()
with open("stopwords_zh.txt", "r", encoding="utf-8") as f:
    for line in f:
        stopwords.add(line.strip())

text = "这本书是真的好看"
tokens = jieba.lcut(text)
filtered = [w for w in tokens if w not in stopwords]
print(filtered)
# 输出: ['本书', '好看']   (假设 '的' 在停用词表中)

当然,停用词表怎么选是有讲究的。我见过有人直接拿网上 2000 词的通用停用词表套上去。结果?“不”、“很”、“非常”全被滤掉了。情感分析直接崩了。

注意:对于知识图谱抽取场景,你要保留否定词(比如“不”、“没”)。因为这些词会反转三元组的关系指向。比如“他不吃苹果”去掉“不”之后,事实就反了。

所以,建议根据场景定制停用词表。比如抽取科技文献时,“提出”、“方法”、“基于”这些词其实也经常出现,但它们对语义有贡献,不应该被删掉。

3.4 三者配合:完整的预处理管线

实际项目中,不会单独用某一个步骤。我把它们串起来:

def preprocess(text, stopwords, custom_dict_path=None):
    # 1. 加载自定义词典
    if custom_dict_path:
        jieba.load_userdict(custom_dict_path)

    # 2. 分词 + 词性标注
    words = pseg.lcut(text)

    # 3. 过滤停用词 + 只保留名词/动词/专有名词
    keep_flags = {"n", "v", "vn", "ns", "nt", "nz"}
    filtered = []
    for word, flag in words:
        if word not in stopwords and flag in keep_flags:
            filtered.append((word, flag))

    return filtered

输出类似这样:

[("知识图谱", "n"), ("建立", "v"), ("自然语言", "n"), ("处理", "vn")]

这个结果就可以丢给后面的实体链接和关系抽取模块了。

经验之谈:预处理管线要写成可配置的。词性过滤规则、停用词表路径、自定义词典路径都放配置文件里。这样换一个领域,改个配置就行,不用改代码。

3.5 常用工具对比

除了 jieba,还有几个常用工具。我列举一下,方便你选型:

工具 分词 词性标注 速度 典型场景
jieba 好 好 快 快速原型、小规模
HanLP 很好 很好 中等 学术、复杂词性
LTP (哈工大) 很好 很好 慢 高精度需求
PaddleNLP 好 好 中等 端到端管线

我个人习惯,小项目用 jieba,快速;大项目用 HanLP,准确度高。如果你要处理百万级文献,建议先做预测试,看看哪个工具的 OOV(未登录词)覆盖率更符合你的语料。

好,文本预处理的三个基本步骤就这些。从分词、词性标注到去停用词,每一步都影响着知识图谱的最终质量。下次拿到原始文本,可以先按这个流程走一遍,把噪音去掉,让机器看到真正有价值的信息。