4、文本预处理(下):停用词过滤、词干提取与词形还原、文本标准化
好,咱们接着聊文本预处理。上一节我们把分词和词性标注搞定了,但说实话,那只是开胃菜。真正的「脏活累活」,全在这一节里。
你想想看,分词之后拿到的词,有多少是真正有用的?像「的」、「了」、「在」、「是」这些词,几乎每句话都有,但它们能帮我们区分文档主题吗?基本不能。还有,英语里的「running」、「ran」、「runs」,本质上都是「run」这个动作,我们非得把它们当成三个不同的词吗?
嗯,这就是我们今天要解决的三个核心问题:停用词过滤、词干提取与词形还原、以及文本标准化。
核心观点: 文本预处理的质量,直接决定了后续模型的天花板。数据干净了,模型才能学到真东西。
4.1 停用词过滤:把「废话」扔掉
停用词是什么?说白了,就是那些出现频率极高、但信息量极低的词。中文里的「的、了、在、是、我、你、他」,英文里的「the、a、an、is、are」——这些词在大多数任务里都是噪声。
我个人习惯,在做文本分类或情感分析时,第一件事就是加载停用词表。为什么?因为你不去掉它们,模型很容易被这些高频词带偏。我记得有一次做舆情分析,模型把「的」字出现次数当成了一个重要特征,结果分类效果一塌糊涂。去掉停用词后,准确率直接提升了8个点。
💡 我的经验: 不要只用一份停用词表。不同领域、不同任务,停用词是不一样的。比如做金融文本分析,「股票」、「基金」这些词就不能随便停。我通常会准备三份表:通用停用词、领域停用词、任务定制停用词。
代码实现其实很简单,以Python为例:
# 加载停用词表
stop_words = set()
with open('stopwords.txt', 'r', encoding='utf-8') as f:
for line in f:
stop_words.add(line.strip())
# 过滤停用词
tokens = ['我', '今天', '去', '了', '公司', '上班']
filtered_tokens = [word for word in tokens if word not in stop_words]
print(filtered_tokens) # 输出:['今天', '去', '公司', '上班']
⚠️ 避坑指南: 我曾经犯过一个错误——把否定词也加进了停用词表。比如「不」、「没」、「别」这些词,在情感分析里是极其重要的信号。去掉它们,模型就分不清「喜欢」和「不喜欢」了。所以,加停用词表之前,先想清楚你的任务目标。
4.2 词干提取 vs 词形还原:一个粗暴,一个精细
这两个概念经常被混淆,我简单给你捋一捋。
词干提取(Stemming),就是粗暴地砍掉单词的词缀。比如「running」砍掉「-ing」变成「run」,「flies」砍掉「-ies」变成「fli」。注意,它不关心这个词是不是真实存在的,只要规则匹配就砍。
词形还原(Lemmatization),则要优雅得多。它会根据词典,把单词还原成它的基本形式。比如「better」还原成「good」,「ran」还原成「run」。它需要知道这个词的词性,才能正确还原。
你想想看,哪个更好?其实没有绝对的好坏,取决于你的场景。
| 对比维度 | 词干提取 | 词形还原 |
|---|---|---|
| 速度 | 快(规则匹配) | 慢(需要词典查询) |
| 准确性 | 低(可能产生非词) | 高(保证是真实单词) |
| 依赖资源 | 少(规则集即可) | 多(需要词典和词性标注) |
| 典型工具 | PorterStemmer, SnowballStemmer | WordNetLemmatizer, spaCy |
| 适用场景 | 信息检索、大规模文本聚类 | 情感分析、问答系统、机器翻译 |
我在项目中遇到过这样的情况:做搜索引擎的召回阶段,用词干提取就够了,速度快,还能容忍一些错误。但到了排序阶段,必须用词形还原,因为用户输入的查询词需要精确匹配。
# 词干提取示例(NLTK)
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
words = ['running', 'flies', 'better', 'studies']
print([stemmer.stem(w) for w in words])
# 输出:['run', 'fli', 'better', 'studi'] —— 注意 'flies' 变成了 'fli',这不是一个真实单词
# 词形还原示例(NLTK)
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
print(lemmatizer.lemmatize('flies', pos='n')) # 输出:'fly'
print(lemmatizer.lemmatize('better', pos='a')) # 输出:'good'
关键点: 词形还原需要词性标注(POS tagging)作为输入。如果你没有做词性标注,词形还原的效果会大打折扣。我一般会先用spaCy做一次完整的pipeline,把分词、词性标注、词形还原一次性搞定。
4.3 文本标准化:让数据「说人话」
文本标准化,听起来高大上,其实核心就一句话:把各种「变体」统一成标准形式。
举个例子,你在社交媒体上看到的文本,那叫一个五花八门:
- 「u」代替「you」
- 「2moro」代替「tomorrow」
- 「牛逼」写成「NB」、「牛B」、「niubility」
- 数字「0」写成字母「O」
- 全角半角混用:「,」和「,」
这些如果不处理,模型会认为它们是不同的词,白白增加了词汇表的规模,还稀释了语义。
我个人习惯,在文本标准化这一步做三件事:
- 统一大小写:英文全部转小写,避免「Apple」和「apple」被当成两个词。
- 统一数字格式:把「1,234.56」转成「1234.56」,把「2024年」转成「2024」。
- 处理特殊符号:去除HTML标签、@提及、URL链接等。
import re
def normalize_text(text):
# 1. 统一小写
text = text.lower()
# 2. 去除URL
text = re.sub(r'http\S+|www\S+', '', text)
# 3. 去除@提及
text = re.sub(r'@\w+', '', text)
# 4. 统一数字格式(去掉逗号)
text = re.sub(r'(\d),(\d)', r'\1\2', text)
# 5. 去除多余空格
text = re.sub(r'\s+', ' ', text).strip()
return text
sample = "Check out our new product! Visit https://example.com @john 1,234 units sold!"
print(normalize_text(sample))
# 输出:'check out our new product! visit 1234 units sold!'
💡 一个小技巧: 对于中文文本,我建议把全角符号转成半角。比如全角逗号「,」转成半角「,」,全角括号「()」转成半角「()」。这样在做分词和匹配时,能省去很多麻烦。
4.4 实战中的「组合拳」
好了,知识点都讲完了。但在实际项目中,这些步骤不是孤立执行的。我一般会按这个顺序来:
- 原始文本 → 2. 文本标准化(去噪、统一格式) → 3. 分词 → 4. 词性标注 → 5. 词形还原(如果需要) → 6. 停用词过滤 → 7. 干净文本
你可能会问:为什么停用词过滤要放在最后?因为词形还原可能会改变单词的形式,如果你提前过滤了,可能会漏掉一些应该被还原的词。而且,有些停用词在特定语境下是有意义的,比如「not」在情感分析里就不能随便过滤。
⚠️ 我曾经踩过的坑: 有一次做英文文本处理,我先做了停用词过滤,再去词形还原。结果发现「running」被过滤掉了(因为「run」在停用词表里?不,是「running」本身不在停用词表里,但「is」被过滤了,导致「is running」变成了「running」,然后词形还原把它变成了「run」——但「run」本身是停用词!你看,顺序一乱,整个逻辑就崩了。
所以,我的建议是:先标准化,再分词,然后做词形还原,最后过滤停用词。这个顺序我用了很多年,基本没出过问题。
好了,文本预处理的核心内容就这些。你把这些步骤练熟了,后面做特征工程、模型训练,就会顺手很多。数据干净了,模型才能跑得稳——这是我在无数项目中验证过的真理。
公众号:蓝海资料掘金营,微信deep3321