本文目录导读:

这是一个非常经典且完整的文本分类案例,我将以一个具体的场景——垃圾短信(Spam)与正常短信(Ham)分类为例,使用Python和机器学习库(Scikit-learn)从头到尾演示一遍。
这个案例涵盖了文本分类的核心步骤:数据加载、文本预处理、特征提取(TF-IDF)、模型训练(朴素贝叶斯)、评估与预测。
问题定义
目标:给定一条短信内容,判断它是“垃圾短信”(Spam)还是“正常短信”(Ham)。 数据集:使用公开的 SMS Spam Collection 数据集(通常包含约 5574 条短信)。
环境准备
需要安装以下库:
pip install pandas scikit-learn nltk
完整代码实现
1 导入库与加载数据
import pandas as pd
import re
import nltk
from nltk.corpus import stopwords
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
# 下载停用词(第一次运行需要取消注释)
# nltk.download('stopwords')
# 加载数据(假设数据文件为 'spam.csv',常见格式:两列:label, message)
df = pd.read_csv('spam.csv', encoding='latin-1')
# 通常数据集会有多余的无名列,只保留前两列
df = df[['v1', 'v2']]
df.columns = ['label', 'message']
# 查看数据分布
print(df['label'].value_counts())
# 输出示例:
# ham 4825
# spam 747
2 文本预处理
我们需要将文本清洗干净:删除标点、数字、转换为小写、去除停用词(如 “the”, “is”, “a” 等)。
nltk.download('stopwords')
stop_words = set(stopwords.words('english'))
def clean_text(text):
# 1. 只保留字母和空格(去除标点和数字)
text = re.sub('[^a-zA-Z]', ' ', text)
# 2. 全部转为小写
text = text.lower()
# 3. 分词并去除停用词
words = text.split()
words = [w for w in words if w not in stop_words]
return ' '.join(words)
# 应用清洗函数
df['cleaned_message'] = df['message'].apply(clean_text)
print(df[['message', 'cleaned_message']].head())
3 划分训练集与测试集
# 将标签转换为数值:ham -> 0, spam -> 1
df['label_num'] = df['label'].map({'ham': 0, 'spam': 1})
X = df['cleaned_message'] # 特征:清洗后的文本
y = df['label_num'] # 标签
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
4 特征工程:TF-IDF向量化
文本必须转化为数值向量。TF-IDF(词频-逆文档频率)能有效降低常见词(如 “hello”, “ok”)的权重,突出特定文档中的关键词。
# 初始化 TF-IDF 向量化器
# max_features=5000 表示只取前5000个最重要的词,避免维度爆炸
vectorizer = TfidfVectorizer(max_features=5000)
# 在训练集上学习并转换
X_train_tfidf = vectorizer.fit_transform(X_train)
# 在测试集上仅转换(使用训练集学到的词汇表)
X_test_tfidf = vectorizer.transform(X_test)
print(f"训练集向量形状: {X_train_tfidf.shape}") # 输出: (4457, 5000)
5 训练模型(朴素贝叶斯)
朴素贝叶斯在文本分类(如垃圾邮件过滤)中表现优异,因为它假设特征之间条件独立,非常适合高维稀疏数据。
model = MultinomialNB() model.fit(X_train_tfidf, y_train)
6 预测与评估
# 预测测试集
y_pred = model.predict(X_test_tfidf)
# 评估指标
print("准确率:", accuracy_score(y_test, y_pred))
print("\n分类报告:\n", classification_report(y_test, y_pred, target_names=['Ham', 'Spam']))
print("混淆矩阵:\n", confusion_matrix(y_test, y_pred))
典型输出(因为随机拆分,数值可能略有不同):
准确率: 0.9802
分类报告:
precision recall f1-score support
Ham 0.98 1.00 0.99 965
Spam 1.00 0.86 0.92 150
accuracy 0.98 1115
macro avg 0.99 0.93 0.96 1115
weighted avg 0.98 0.98 0.98 1115
混淆矩阵:
[[962 3]
[ 21 129]]
解读:模型整体准确率 98%,对于正常短信(Ham)几乎没有误判(965个中只错了3个),对于垃圾短信(Spam)查全率(Recall)86%,意味着有14%的垃圾短信未被识别(漏网之鱼),查准率(Precision)100%,说明模型预测为垃圾短信的案例中,全部是真正的垃圾短信。
7 使用模型进行单条预测
def predict_message(message):
cleaned = clean_text(message)
vectorized = vectorizer.transform([cleaned])
prediction = model.predict(vectorized)[0]
prob = model.predict_proba(vectorized)[0] # [ham_prob, spam_prob]
label = 'Spam' if prediction == 1 else 'Ham'
return label, prob[1] # 返回标签和垃圾概率
# 测试几条新的消息
print(predict_message("Congratulations! You've won a $1000 Walmart gift card. Click here to claim now."))
print(predict_message("Hey, are we still meeting for lunch at 12?"))
输出示例:
('Spam', 0.9998)
('Ham', 0.0001)
关键技巧与总结
| 步骤 | 常用方法 | 说明 |
|---|---|---|
| 数据清洗 | 正则表达式、停用词 | 去除噪音,保留有效词根 |
| 特征提取 | TF-IDF | 比单纯词袋模型(CountVectorizer)效果更好 |
| 模型选择 | 朴素贝叶斯(首选)、SVM、Logistic回归 | 对于线性可分的高维稀疏数据,朴素贝叶斯速度快且效果好 |
| 模型优化 | 调整max_features、使用n-gram(如2-gram) | 捕获短语特征 (“win prize”) 能提升垃圾识别 |
| 评估指标 | 准确率、召回率、F1-score | 对于不平衡数据(本例Ham:Spam ≈ 6:1),建议关注Spam类别的召回率 |
扩展建议
- 使用词干提取(Stemming)或词形还原(Lemmatization):将“running”变为“run”,进一步统一词形。
- 引入深度学习方法:使用Word2Vec + 神经网络(LSTM/CNN),处理更复杂的语义,如讽刺、上下文。
- 处理中文文本:需要分词(如jieba库),停用词为中文常见字词,向量化一样使用TF-IDF。
这个案例可以直接复制到 Jupyter Notebook 或 Python 环境中运行,如果你有特定的数据集(如中文新闻分类、情感分析),核心流程完全一致,只需调整预处理环节即可。