2. 文本预处理基础:分词、去停用词、词干提取与词形还原、文本清洗
大家好,欢迎来到文本预处理这一章。
说实话,很多做 NLP 的新手,上来就急着调模型。结果数据一喂进去,全是乱码和噪音。我见过太多这样的案例了。文本预处理,说白了就是给模型「喂饭」之前,先把菜洗干净、切好块。这一步做不好,后面再牛的 Transformer 也白搭。
2.1 文本清洗:正则表达式是基本功
先聊聊文本清洗。你想想看,从网上爬下来的数据,什么乱七八糟的都有。HTML 标签、特殊符号、多余的空格、URL 链接……这些东西对模型来说就是噪音。
我个人习惯,拿到文本第一件事,就是用正则表达式做一次「大扫除」。
import re
def clean_text(text):
# 去掉 HTML 标签
text = re.sub(r'<[^>]+>', '', text)
# 去掉 URL
text = re.sub(r'http\S+|www\S+', '', text)
# 只保留中文、英文、数字和基本标点
text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s\.\,\!\?]', '', text)
# 合并多余空格
text = re.sub(r'\s+', ' ', text).strip()
return text
sample = "这是一段<b>测试</b>文本!访问 https://example.com 了解更多。"
print(clean_text(sample))
# 输出:这是一段测试文本!访问 了解更多。
2.2 分词:Jieba 与 NLTK 的实战选择
分词是中文 NLP 的第一步,也是最关键的一步。英文天然有空格分隔,中文可没有。你想想看,「南京市长江大桥」这句话,分错了意思就完全变了。
2.2.1 Jieba:中文分词的首选
Jieba 是我用得最多的中文分词工具。它支持精确模式、全模式和搜索引擎模式。我个人习惯用精确模式,准确率最高。
import jieba
text = "我在项目中遇到过文本摘要的难题,最终通过优化分词解决了。"
# 精确模式
words = jieba.lcut(text)
print(words)
# 输出:['我', '在', '项目', '中', '遇到', '过', '文本', '摘要', '的', '难题', ',', '最终', '通过', '优化', '分词', '解决', '了', '。']
# 添加自定义词典
jieba.add_word("文本摘要")
words_custom = jieba.lcut(text)
print(words_custom)
# 输出:['我', '在', '项目', '中', '遇到', '过', '文本摘要', '的', '难题', ',', '最终', '通过', '优化', '分词', '解决', '了', '。']
2.2.2 NLTK:英文分词的利器
做英文文本时,我一般用 NLTK。它内置了分词器,还能处理缩写、标点等特殊情况。
import nltk
from nltk.tokenize import word_tokenize
text = "I'm working on NLP, and it's fascinating!"
tokens = word_tokenize(text)
print(tokens)
# 输出:['I', "'m", 'working', 'on', 'NLP', ',', 'and', 'it', "'s", 'fascinating', '!']
嗯,这里要注意。NLTK 第一次用需要下载 punkt 模型。别问我怎么知道的,我第一次跑的时候报错报了半天。
2.3 去停用词:给文本做减法
分词之后,你会发现很多词其实没什么用。「的」、「了」、「在」、「是」……这些词出现频率极高,但对语义贡献几乎为零。去停用词,就是把这些「废话」删掉。
我一般会准备一个停用词表,网上有很多现成的。中文常用的有哈工大停用词表、百度停用词表。我个人习惯把几个表合并一下,去重后自己再补一些领域相关的词。
def load_stopwords(filepath):
with open(filepath, 'r', encoding='utf-8') as f:
return set([line.strip() for line in f])
stopwords = load_stopwords('stopwords.txt')
def remove_stopwords(tokens, stopwords):
return [word for word in tokens if word not in stopwords and len(word) > 1]
tokens = ['我', '在', '项目', '中', '遇到', '过', '文本摘要', '的', '难题']
filtered = remove_stopwords(tokens, stopwords)
print(filtered)
# 输出:['项目', '遇到', '文本摘要', '难题']
2.4 词干提取与词形还原
这两个概念容易搞混。我简单解释一下:
- 词干提取(Stemming): 粗暴地砍掉词缀。比如 "running" → "run","flies" → "fli"。速度快,但可能不准确。
- 词形还原(Lemmatization): 根据词典还原单词原型。比如 "better" → "good","ran" → "run"。准确率高,但速度慢。
你想想看,做文本摘要时,我们通常希望把不同形态的同一个词统一起来。比如 "running"、"ran"、"runs" 都应该归一化为 "run"。
2.4.1 词干提取实战
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
words = ["running", "flies", "easily", "better"]
stems = [stemmer.stem(word) for word in words]
print(stems)
# 输出:['run', 'fli', 'easili', 'better']
看到了吗?"flies" 变成了 "fli","easily" 变成了 "easili"。这就是词干提取的「粗暴」之处。不过在很多场景下,够用了。
2.4.2 词形还原实战
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
words = ["running", "flies", "easily", "better"]
lemmas = [lemmatizer.lemmatize(word, pos='v') for word in words]
print(lemmas)
# 输出:['run', 'fly', 'easily', 'better']
我个人习惯,在做文本摘要时优先用词形还原。虽然慢一点,但准确率更高。尤其是做英文摘要时,词形还原能保证语义的完整性。
2.5 完整的预处理流程
好了,我们把上面所有步骤串起来,形成一个完整的预处理流水线。这是我个人项目中常用的模板:
def preprocess_pipeline(text, lang='zh'):
# 1. 文本清洗
text = clean_text(text)
# 2. 分词
if lang == 'zh':
tokens = jieba.lcut(text)
else:
tokens = word_tokenize(text)
# 3. 去停用词
tokens = remove_stopwords(tokens, stopwords)
# 4. 词形还原(仅英文)
if lang == 'en':
tokens = [lemmatizer.lemmatize(token, pos='v') for token in tokens]
return ' '.join(tokens)
# 测试
zh_text = "我正在做一个关于文本摘要的项目,它非常有趣!"
en_text = "I am working on a text summarization project, and it is very interesting!"
print(preprocess_pipeline(zh_text, 'zh'))
# 输出:项目 文本摘要 有趣
print(preprocess_pipeline(en_text, 'en'))
# 输出:I work text summarization project interesting
2.6 本章小结
文本预处理,说白了就是「清洗 + 分词 + 去噪 + 归一化」。每一步看起来简单,但细节决定成败。
我记得刚入行时,总觉得预处理是「体力活」,想跳过直接上模型。结果模型训练出来,效果差得离谱。后来老老实实把预处理做好,效果立竿见影。
下一章我们会聊特征提取,也就是把文本变成模型能理解的数字。到时候你会发现,预处理做得越好,特征提取就越轻松。