2026/9/11 15:50:24

垃圾短信识别全流程:从文本分类到阈值调优的数据挖掘闭环

垃圾短信识别全流程:从文本分类到阈值调优的数据挖掘闭环 简介面向网络数据挖掘课程设计/实训场景的垃圾短信识别系统完整工程涵盖数据预处理、特征构建、模型训练与分类等环节配有实验报告、操作说明及可运行的Python和MATLAB代码适合高校学生用于课设、大作业或毕业设计参考复现。压缩包共25个文件类型包括Python源码、MATLAB脚本、SVM/GBDT等模型文件pkl、文本语料txt/json/mtx以及PDF/doc说明文档整体大小53.93MB结构清晰便于按模块查阅。已有29人学习浏览。该课程设计答辩平均分达96分代码经实际测试可运行可直接复现实验效果基础较好的读者还可基于现有框架替换算法或扩展功能用于学科竞赛或进一步研究。1. 垃圾短信识别不只是分类一个课程设计背后的数据挖掘闭环提到垃圾短信识别多数人第一反应是“用朴素贝叶斯分个类”代码跑完准确率九十几课设就算交差了。但如果你真拿这份思维去做网络数据挖掘课程设计多半会在答辩时被问住准确率高是因为测试集里垃圾短信占比本来就不低还是模型真的学到了“转账”“中奖”“退订回T”这些信号换一批真实短信精确率掉多少这个问题的核心其实不在分类器而在于你是否把网络数据挖掘的完整闭环走通了——从数据采集、标注、清洗、分词到特征表示、模型训练、评估、调优再到一个能被老师或评委现场点开的演示界面。这篇文章按“理论到落地”的顺序把这个闭环里的每一步拆开讲所有代码都基于 Python 和 sklearn可以直接对着跑。适合正在做课设、实训、大作业的学生也适合想快速搭一套短信识别基线系统的工程师。2. 把短信识别建模成文本分类标签、噪声与评估口径2.1 垃圾短信识别本质上是什么问题从网络数据挖掘的角度看垃圾短信识别属于典型的短文本二分类任务给定一条短信内容预测它属于“正常短信ham”还是“垃圾短信spam”。但和通用文本分类不同的是短信有三个天然约束长度短通常不超过100字、噪声多大量数字、链接、特殊符号、火星文、口语化严重“亲在吗”“【XX银行】”。这些约束直接决定了后续预处理和特征工程的策略。常见的误区是一上来就调模型先跑个深度学习再说。但课程设计和工程落地的区别在于你要能解释每一步为什么这么做。短信分类的基线方案是分词 TF-IDF 线性分类器朴素贝叶斯/逻辑回归/SVM这个组合在公开数据集上通常能到97%以上的F1。深度学习如TextCNN、FastText在短文本上确实可以再涨零点几个点但训练时间和调参成本高且不容易在答辩时讲清楚特征重要性。我的建议是先跑通基线再考虑是否用深度模型做对比实验。2.2 数据标注多数课设死在这一步模型效果的上限由数据决定。网上能找到的公开短信数据集不少但存在两个问题一是年代久远垃圾短信的语义已经变了——以前是“恭喜您中奖”现在是“【京东】您的验证码为XXXX”“加V信领免费课程”二是类别分布极度不平衡正常短信远多于垃圾短信。如果你打算自己爬取或收集数据标注规范要提前定好。以下是我常用的标注口径含营销推广链接且用户无法一键退订的标为垃圾银行、运营商、政务的验证码和通知短信即使带链接也标为正常包含“退订回T”但内容为营销推广的标为垃圾含辱骂、色情、赌博等明显违法内容的标为垃圾同一号码的短信内容重复度极高如刷屏式广告标为垃圾代码层面数据格式统一成两列 CSVimport pandas as pd df pd.read_csv(sms_raw.csv) # 只保留标签和文本两列其余列丢弃 df df[[label, text]] # 标签统一为0正常和1垃圾避免字符串映射出错 df[label] df[label].map({ham: 0, spam: 1}) # 去重同一文本重复出现多次如批量群发保留一条即可 df df.drop_duplicates(subsettext, keepfirst) print(df[label].value_counts())逻辑说明先读入原始数据只保留标签和文本两列再用map把字符串标签转为数值这是 sklearn 训练前的必要步骤最后按文本内容去重——群发短信在原始数据里往往出现很多次不去重会让模型把“高频”误学成“垃圾”的特征。2.3 评估指标只看准确率会翻车当数据不平衡时准确率是欺骗性最强的指标。假设数据里 90% 是正常短信模型什么都不学全预测成正常短信准确率也有 90%。所以垃圾短信识别必须看三个指标精确率Precision预测为垃圾的短信里真的是垃圾的比例。精确率低意味着误杀正常短信代价很高召回率Recall真实垃圾短信里被正确找出来的比例。召回率低意味着漏放F1 值精确率和召回率的调和平均综合衡量在线下评估时我一般把数据按 7:3 划分训练集和测试集并用分层抽样保证两边的类别比例一致from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( df[text], df[label], test_size0.3, random_state42, stratifydf[label] # 分层抽样保证训练/测试的类别比例相同 ) print(X_train.shape, X_test.shape)逻辑说明stratify参数按标签比例分层抽样避免随机划分时测试集里恰好没有垃圾短信。random_state42固定随机种子保证每次跑出来的结果可复现这在你写实验报告时要贴多次实验结果时会很有用。提示答辩时被问“为什么用F1不用准确率”就回答“类别不平衡下准确率无法反映少数类垃圾短信的识别效果而F1同时惩罚误杀和漏放”。3. 数据清洗与中文分词垃圾短信识别的第一道槛3.1 中文短信为什么必须清洗短信数据里有三类东西对分类是纯噪声HTML实体nbsp;、amp;、URL 链接、以及“【】”包裹的签名。URL 本身不能说明短信是否垃圾——银行短信也带链接——但 URL 的特征短链、免费域名是有用的。所以在清洗时我通常把 URL 归一化成URL占位符而不是直接删掉让模型自己学习带链接的短信和垃圾之间的相关性。3.2 清洗规则和代码实现import re def clean_sms(text): # 去除HTML实体 text re.sub(r[a-zA-Z];, , text) # 统一把URL替换为占位符 text re.sub(rhttp[s]?://\S|www\.\S, URL , text) # 去除和#话题符号微博类数据常见 text re.sub(r[#][\w\u4e00-\u9fa5], , text) # 数字统一替换手机号、QQ号、验证码都可能是垃圾短信特征 text re.sub(r\d, NUM , text) # 连续重复的标点符号合并如变成 text re.sub(r([!?。])\1, r\1, text) # 去除多余空白 text re.sub(r\s, , text).strip() return text df[clean_text] df[text].apply(clean_sms)参数说明\S匹配不包含空格的一串字符能覆盖大多数 URL 形态[\w\u4e00-\u9fa5]同时匹配中英文和数字数字归一化是短信场景的关键——验证码短信和诈骗短信都大量出现数字如果保留原始数字会引入海量稀疏特征。3.3 分词jieba 在短信上要加自定义词典中文分词是特征工程的前置步骤。jieba.lcut是最常见的做法但默认词典有两个短板一是互联网新词如“加V”“秒回”“tg”分不出来二是短信里大量品牌名如“支付宝”“京东”会被切碎。解决方案是维护一个自定义词典把短信语料里反复出现的高频词加进去。import jieba # 自定义词典每行一个词可带词频和词性加V 100 n jieba.load_userdict(sms_dict.txt) # 开启并行分词适合大批量预处理 jieba.enable_parallel(4) def tokenize(text): # jieba.lcut返回词列表过滤单字符词和无意义符号 words [w for w in jieba.lcut(text) if len(w.strip()) 1] return words df[tokens] df[clean_text].apply(tokenize)逻辑说明load_userdict加载自定义词典后词典里的词在分词时会被优先识别为完整词enable_parallel(4)用4个进程并行分词几万条短信数据能明显提速。过滤单字符词的目的是减少特征维度——“的”“了”“在”这类词既不携带分类信息又会把TF-IDF矩阵撑大。停用词表要不要用我的结论是在短信场景下不要用通用中文停用词表。因为“退订”“回复”“免费”这些词在通用语料里不算停用词但在短信里恰恰是强特征。真正需要过滤的是标点符号和单个数字占位符NUM以及“的、了、吗、呢”这类纯语气词。如果用了完整停用词表有可能把“免费”这种词给误删——你需要对照停用词表确认一下再决定。4. 特征工程与TF-IDF让模型看见“转账”“中奖”“退订”的信号4.1 TF-IDF 为什么适合短文本词袋模型Bag of Words会给每个词赋予一个计数但“的”“了”这类高频词会淹没真正有区分度的词。TF-IDF 的核心思想是一个词在一条短信里出现次数越多越重要TF大但在所有短信里出现次数越少越有区分度IDF大。垃圾短信识别里“转账”在几万条正常短信里几乎不出现所以在垃圾短信中出现一次TF-IDF 值会被放大而“的”几乎每条短信都有IDF 趋近于0自然被压下去。4.2 特征向量化的参数怎么调在 sklearn 中TfidfVectorizer是标准入口。下面这组参数是我的默认配置经过多个数据集验证效果稳定from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( tokenizertokenize, # 使用jieba分词函数 ngram_range(1, 2), # 保留单个词和相邻两个词的组合 max_features20000, # 限制最大特征数防止维度爆炸 min_df2, # 至少在2条短信中出现过才保留 sublinear_tfTrue # TF用1log(DF)替代原始计数 ) X_train_vec vectorizer.fit_transform(X_train) X_test_vec vectorizer.transform(X_test) print(特征维度:, X_train_vec.shape[1])参数说明ngram_range(1, 2)让“免费”和“免费 领取”都成为特征能捕捉短语级的语义信息短文本上通常比纯单个词提升1-2%的F1max_features20000截断最不重要的低频词显著降低内存占用sublinear_tfTrue用对数缩放词频防止某个词在一条短信里反复出现时权重被过度放大。4.3 从向量到可视化的特征筛选做完向量化后有个非常实用的诊断方法把权重最高的特征打印出来肉眼判断模型学到了什么。import numpy as np # 计算每个特征在所有垃圾短信中的平均TF-IDF值 spam_indices np.where(y_train 1)[0] mean_tfidf X_train_vec[spam_indices].mean(axis0).A1 top_features np.argsort(mean_tfidf)[::-1][:20] feature_names vectorizer.get_feature_names_out() for idx in top_features: print(f{feature_names[idx]}: {mean_tfidf[idx]:.4f})逻辑说明先从训练标签中找出所有垃圾短信的行索引再对TF-IDF矩阵按列求均值得到每个特征在垃圾短信中的平均权重最后取Top20。如果打印出来的词是“中奖、领取、加V、免费、点击、链接、退款、保险”说明特征工程是合理的如果出现“今天、你好、我们”这类无意义词说明清洗环节可能出了问题。提示这一步和 final 答辩时的“如何验证模型学到了有效特征”密切相关。把这个Top20特征列表截图放到报告里说服力远大于一张ROC曲线图。5. 模型选型与阈值调优朴素贝叶斯、逻辑回归和SVM的实际差异5.1 三种线性模型的定位与选择TF-IDF 矩阵的特点是稀疏、高维、离散。适合这种数据分布的经典模型有三个朴素贝叶斯MultinomialNB假设特征之间相互独立训练极快在短文本分类上是强基线对稀疏数据友好逻辑回归LogisticRegression不假设特征独立可解释性强能输出概率值适合做阈值调整线性SVMLinearSVC在文本分类上通常优于朴素贝叶斯但默认只输出类别不输出概率我的建议是课程设计至少要跑朴素贝叶斯和逻辑回归两个模型做对比因为它们在sklearn里都是几行代码的事。SVM可以作为进阶对比项但没必要在上面花太多时间调参线性核就够了。5.2 训练与评估的完整代码from sklearn.naive_bayes import MultinomialNB from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, confusion_matrix # 朴素贝叶斯alpha是拉普拉斯平滑系数 nb MultinomialNB(alpha1.0) nb.fit(X_train_vec, y_train) y_pred_nb nb.predict(X_test_vec) # 逻辑回归max_iter要调大否则可能不收敛C控制正则化强度 lr LogisticRegression(C2.0, max_iter1000, solverliblinear) lr.fit(X_train_vec, y_train) y_pred_lr lr.predict(X_test_vec) for name, y_pred in [(Naive Bayes, y_pred_nb), (Logistic Regression, y_pred_lr)]: print( * 20, name, * 20) print(classification_report(y_test, y_pred, target_names[正常, 垃圾]))参数说明alpha1.0是朴素贝叶斯的平滑参数防止某个词在训练集中未出现导致概率为0solverliblinear适合小数据集和二分类问题线性SVM相关的方法也可以直接用C2.0是正则化系数的倒数C越大正则化越弱模型越容易过拟合——短信特征维度很高C不建议超过10。5.3 混淆矩阵找到误杀还是漏放是关键F1 只能告诉你好不好混淆矩阵才能告诉你怎么改import matplotlib.pyplot as plt import seaborn as sns cm confusion_matrix(y_test, y_pred_lr) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabels[正常, 垃圾], yticklabels[正常, 垃圾]) plt.xlabel(预测标签) plt.ylabel(真实标签) plt.savefig(confusion_matrix.png, dpi150)如果右下角真实垃圾预测垃圾远大于右上角真实垃圾预测正常说明漏放很少如果左下角真实正常预测垃圾不为0说明存在误杀。实际业务中误杀一条正常短信的代价远高于漏放一条垃圾短信——被误杀的可能是一条银行验证码。所以下一步要用概率阈值来调节误杀和漏放的平衡。6. 概率校准与阈值选择把精确率召回率调到你想要的点6.1 使用 predict_proba 而不是 predictpredict默认以0.5为阈值但业务上往往需要更保守或更激进的拦截策略。这时要改用predict_proba输出每条短信属于垃圾的概率然后自己设定阈值prob_lr lr.predict_proba(X_test_vec)[:, 1] # 取垃圾短信的概率 threshold 0.6 # 只有概率超过0.6才判定为垃圾 y_pred_tuned (prob_lr threshold).astype(int) # 对比不同阈值下的精确率和召回率 for t in [0.3, 0.4, 0.5, 0.6, 0.7]: pred_t (prob_lr t).astype(int) tn, fp, fn, tp confusion_matrix(y_test, pred_t).ravel() precision tp / (tp fp) if tp fp 0 else 0 recall tp / (tp fn) if tp fn 0 else 0 print(f阈值{t:.1f} 精确率{precision:.3f} 召回率{recall:.3f} F1{2*precision*recall/(precisionrecall):.3f})逻辑说明阈值提高只有模型非常有把握时才判定为垃圾误杀减少但漏放增加阈值降低则相反。打印出来的表格可以直接放进实验报告里是“阈值调优”这个环节最有力的证据。6.2 从离线到线上用 joblib 保存模型和向量器模型训练结束后向量器和分类器必须一起保存因为线上预测时要用同一个词表和IDF值去变换输入数据import joblib # 保存向量器和模型到一个pipeline from sklearn.pipeline import make_pipeline pipeline make_pipeline(vectorizer, lr) pipeline.fit(df[text], df[label]) # 用全量数据再训练一次 joblib.dump(pipeline, sms_spam_pipeline.pkl) # 加载后预测新短信 loaded joblib.load(sms_spam_pipeline.pkl) new_sms [恭喜您被选为幸运用户点击链接领取iPhone15] print(垃圾概率:, loaded.predict_proba(new_sms)[0][1])参数说明make_pipeline把向量化和分类器组装成一个完整对象避免预测时忘记做分词或IDF变换predict_proba返回的是一个二维数组第一个索引是样本序号第二个索引是标签为垃圾的概率。6.3 最后一公里给课设加一个命令行交互界面课程设计和实际系统的差别在于交付形态。哪怕不上Web框架一个最简单的命令行循环就能让评委现场输入短信、看到识别结果while True: sms input(输入短信回车退出: ) if not sms: break prob loaded.predict_proba([sms])[0][1] label 垃圾短信 if prob 0.6 else 正常短信 print(f分类: {label} | 垃圾概率: {prob:.2%})这一段代码加上前面的离线评估结果已经可以构成网络数据挖掘课程设计的完整交付数据清洗、分词、特征工程、模型训练、评估、阈值调优、成果落地。如果评委问“垃圾短信识别系统的核心难点是什么”可以回答“短文本噪声大、特征稀疏、类别不平衡导致评估困难”而不是“模型越复杂越好”。最后记得把vectorizer、模型、混淆矩阵图、阈值对比表全部打包进报告这套体系足够撑起一次高分答辩。本文还有配套的精品资源点击获取