文本预处理实战:中文分词与文本清洗

各位同学,今天我们来聊聊文本预处理。说实话,这是整个NLP流水线里最枯燥、但也是最关键的一环。我在做智能客服系统的时候,遇到过太多因为预处理没做好,导致模型效果一塌糊涂的案例。你想想看,垃圾进垃圾出,这个道理大家都懂。

好,我们直接进入正题。今天要讲的核心内容有四个:中文分词、去停用词、文本清洗、标准化处理。我会结合Jieba这个工具,带大家一步步走通整个流程。

一、中文分词:Jieba的三种模式

中文分词,说白了就是把连续的汉字序列切分成有意义的词语。英文天然有空格分隔,中文可没有。我记得刚入行时,有个同事直接用单字切分去做意图识别,结果准确率惨不忍睹。嗯,这里要注意,分词是中文NLP的基石。

Jieba分词器,我个人习惯用它来做快速原型开发。它支持三种模式,我们一个个来看。

1. 精确模式

精确模式试图把句子最精确地切开,适合文本分析。我一般在做关键词提取、文本分类时用这个模式。

import jieba

text = "我在智能客服系统里做自然语言处理"
seg_list = jieba.cut(text, cut_all=False)
print("精确模式: " + "/".join(seg_list))
# 输出: 我/在/智能/客服/系统/里/做/自然语言/处理

你看,它把"自然语言处理"切成了一个词。为什么?因为Jieba的词典里有这个词。我在项目中遇到过一个问题:很多专业术语Jieba默认词典里没有,比如"意图识别"、"槽位填充"。这时候就需要自定义词典了。

小技巧: 加载自定义词典用 jieba.load_userdict("dict.txt"),格式是"词语 词频 词性",每行一个。我习惯把客服系统里的高频业务词都加进去。

2. 全模式

全模式会把所有可能的词都扫描出来。速度很快,但有冗余。说白了就是"宁可错杀一千,不可放过一个"。

seg_list = jieba.cut(text, cut_all=True)
print("全模式: " + "/".join(seg_list))
# 输出: 我/在/智能/客服/系统/里/做/自然/自然语言/语言/处理

看到没?"自然"和"语言"也被单独切出来了。全模式我一般不用在最终分析里,但做搜索引擎的召回阶段时,它很有用。

3. 搜索引擎模式

这个模式是在精确模式的基础上,对长词再切分。适合搜索引擎的分词场景。

seg_list = jieba.cut_for_search(text)
print("搜索引擎模式: " + "/".join(seg_list))
# 输出: 我/在/智能/客服/系统/里/做/自然/语言/自然语言/处理

你看,"自然语言"被保留,但"自然"和"语言"也被作为子词输出。这样搜索引擎既能匹配长词,也能匹配短词。我曾经在做一个FAQ检索系统时,就用了这个模式,召回率提升了15%。

避坑指南: 我曾经在线上环境直接用了默认词典,结果用户说"我要退款",Jieba把"退款"切成了"退"和"款"。后来我加了一个自定义词典,把业务高频词都加了进去。记住,词典质量直接决定分词效果。

二、去停用词

停用词,就是那些高频但没啥实际意义的词,比如"的"、"了"、"在"、"是"。你想想看,这些词在文本里到处都是,但对意图识别、情感分析几乎没有贡献。

我一般会维护一个停用词表。网上有现成的,但我会根据业务场景做调整。比如在客服系统里,"您好"、"请问"这些礼貌用语,在意图识别阶段也可以去掉。

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

words = jieba.cut(text)
filtered = [w for w in words if w not in stopwords and len(w) > 1]
print("去停用词后: " + "/".join(filtered))

这里我加了一个条件 len(w) > 1,过滤掉单字词。为什么?因为在客服场景里,单字词大多是语气词或标点符号,留着反而增加噪声。

个人经验: 停用词表不是一成不变的。我建议每三个月review一次,看看哪些词被误杀了,哪些词应该加进去。比如"不"这个字,在很多否定句里很重要,不能随便去掉。

三、文本清洗:正则表达式实战

文本清洗,说白了就是把脏数据洗干净。用户输入的东西,什么乱七八糟的都有。我记得有一次,用户发来一条消息:"我 要 退 款!!!!!!",里面全是空格和感叹号。如果不做清洗,分词结果会非常难看。

正则表达式是文本清洗的利器。我常用的几个模式:

清洗目标 正则表达式 说明
去除URL r'http[s]?://\S+' 用户经常发链接
去除@用户 r'@\w+' 微博、论坛数据常见
去除多余空格 r'\s+' 替换为单个空格
去除特殊符号 r'[^\u4e00-\u9fa5a-zA-Z0-9]' 只保留中文、英文、数字
import re

def clean_text(text):
    # 去除URL
    text = re.sub(r'http[s]?://\S+', '', text)
    # 去除@用户
    text = re.sub(r'@\w+', '', text)
    # 去除多余空格
    text = re.sub(r'\s+', ' ', text)
    # 只保留中文、英文、数字
    text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', text)
    return text.strip()

raw_text = "你好 @小明 我要退款!!!详情请看 https://example.com"
cleaned = clean_text(raw_text)
print(cleaned)  # 输出: 你好 小明 我要退款详情请看
注意: 正则表达式不要写得太激进。我曾经为了省事,把所有非中文字符都去掉了,结果用户说"iPhone 12"变成了"iPhone12","VIP会员"变成了"VIP会员"。嗯,后来我改成了保留英文和数字。

四、标准化处理

标准化,就是把文本统一成标准格式。比如全角转半角、繁体转简体、大小写统一。这些看似小事,但影响很大。

我举个例子。用户输入"hello"(全角),和"hello"(半角),在模型看来是两个不同的词。如果不做标准化,模型会认为这是两个不同的意图。

def normalize_text(text):
    # 全角转半角
    result = []
    for char in text:
        code = ord(char)
        if code == 0x3000:
            code = 0x0020
        elif 0xFF01 <= code <= 0xFF5E:
            code -= 0xFEE0
        result.append(chr(code))
    text = ''.join(result)
    
    # 繁体转简体(需要zhconv库)
    # from zhconv import convert
    # text = convert(text, 'zh-cn')
    
    # 统一小写
    text = text.lower()
    
    return text

test = "hello,世界!"
print(normalize_text(test))  # 输出: hello,世界!

标准化这一步,我建议放在所有预处理的最前面。先标准化,再清洗,最后分词。这个顺序是我在实践中摸索出来的,效果最好。

我的习惯: 我会把整个预处理流程封装成一个函数,叫 preprocess_pipeline()。这样每次处理新数据时,只需要调用这一个函数就行。代码复用,不容易出错。

五、完整预处理流水线

好,我们把上面所有步骤串起来,形成一个完整的预处理流水线。

def preprocess_pipeline(text):
    # 1. 标准化
    text = normalize_text(text)
    # 2. 文本清洗
    text = clean_text(text)
    # 3. 分词
    words = jieba.cut(text, cut_all=False)
    # 4. 去停用词
    stopwords = load_stopwords()
    words = [w for w in words if w not in stopwords and len(w) > 1]
    return " ".join(words)

# 测试
sample = "您好 @客服 我要退款!!!!!!详情请看 https://example.com"
result = preprocess_pipeline(sample)
print(result)  # 输出: 退款 详情 请看

你看,经过预处理后,"我要退款"变成了"退款"。干净、简洁,模型处理起来也轻松多了。

最后说一句,预处理没有银弹。不同的业务场景,预处理策略可能完全不同。我建议你在实际项目中,先拿100条数据做测试,看看预处理效果,再逐步调整。嗯,今天就到这里,下节课我们讲特征工程。