ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python垃圾短信分类实战:从TF-IDF到模型压缩的完整流程

Python垃圾短信分类实战:从TF-IDF到模型压缩的完整流程 简介这份资源面向Python初学者、机器学习入门者以及需要完成课程设计的学生围绕垃圾短信分类这一经典文本分类任务提供了一套可复现的完整实现方案。内容涵盖数据预处理、词干提取、TF-IDF特征向量转换以及逻辑回归建模预测等关键环节适合用来理解自然语言处理从原始文本到数值向量的基本流程。压缩包共17个文件包含py脚本、csv数据集、docx实验报告、m文件及md说明等整体约6.19MB其中脚本负责核心算法实现csv提供训练与测试样本文档则记录实验思路与结果分析。目前已有347人学习下载说明该方案在同类课程设计中具有一定参考价值。读者可借助这份资料快速搭建实验环境对照代码理解特征工程与模型训练细节并参考报告结构完成自己的课程作业或项目实践。1. 垃圾短信分类到底在分什么从一条“中奖通知”说起你手机里一定收到过这种短信“恭喜您被抽中一等奖点击链接领取”。它和正常验证码短信在字符层面高度重叠——都含数字、都带链接、都可能是短文本。垃圾短信分类要解决的核心问题就是让模型在几十个字符里判断出这条短信该进收件箱还是垃圾箱。这个任务在 Python 生态里落地成本极低一个 CSV 文件加几十行代码就能跑出 95% 以上的准确率但真正上线时会遇到类别不平衡、对抗变体、误杀正常短信三类硬骨头。适合有 Python 基础、想完整走一遍文本分类流程的读者也适合需要快速搭一个可解释基线系统的从业者。下面从数据准备一路讲到调参和排错所有代码可直接复现。2. 数据从哪来、怎么洗垃圾短信分类的第一道分水岭2.1 公开数据集的选择与标签体系垃圾短信分类最常用的公开数据是 SMS Spam Collection约 5574 条英文短信标签只有 ham 和 spam 两类。中文场景没有同等规模的公开集常见做法是自己爬取或使用企业内部工单数据。我一般会先确认三件事标签是否人工复核过、正负样本比例、是否含时间戳。时间戳很关键如果数据按时间排序随机划分会导致同一条短信的变体同时出现在训练集和测试集准确率虚高。标签体系上二分类够用但不够细。实际业务里我会把 spam 再拆成营销、诈骗、色情引流三类因为诈骗类误判为正常短信的代价远高于营销类。如果只做二分类至少要在评估时单独看诈骗子集的召回率。数据格式建议统一成两列label和text。中文短信要做编码归一化把全角数字、繁体字、火星文统一转成简体否则同一个词会被拆成多个 token。2.2 用 pandas 做最小可复现的清洗流程import pandas as pd import re # 读取数据假设是 tab 分隔的两列 df pd.read_csv(sms.tsv, sep\t, headerNone, names[label, text]) # 1. 去重完全相同的短信只保留一条 df df.drop_duplicates(subset[text]) # 2. 标签映射ham-0, spam-1 df[label] df[label].map({ham: 0, spam: 1}) # 3. 文本归一化转小写、去多余空白、保留中英文数字和基本标点 def normalize(text): text text.lower() text re.sub(rhttp\S, URL , text) # 链接统一替换为占位符 text re.sub(r\d{6,}, LONGNUM , text) # 长数字串替换 text re.sub(r[^\w\s\u4e00-\u9fff], , text) # 去掉特殊符号 text re.sub(r\s, , text).strip() return text df[text] df[text].apply(normalize) # 4. 过滤空文本 df df[df[text].str.len() 0] print(df[label].value_counts())这段代码的逻辑是先去重防止同一短信重复计入再把标签转成模型可读的 0/1然后用正则做轻量归一化。链接和长数字替换成占位符是关键一步因为垃圾短信里的链接域名和号码是高频变化特征保留原始值会让模型记住具体域名而不是“含链接”这个模式。参数上\d{6,}的阈值按业务调整验证码通常是 4 到 6 位设 6 以上可以避免把正常验证码误替换。最后过滤空文本防止后续向量化报错。清洗完要检查标签分布。如果 spam 占比低于 5%后面必须用类别权重或重采样否则模型会倾向于全预测 ham。3. 特征工程与模型选型为什么 TF-IDF 加线性模型仍是首选3.1 从词袋到 TF-IDF 的参数怎么定文本分类的特征表示有三条路词袋、TF-IDF、词向量。垃圾短信场景我优先用 TF-IDF原因是短文本里关键词的区分度极高“中奖”“点击”“退订”这类词出现与否几乎直接决定类别TF-IDF 能把这种信号放大而词向量在短文本上反而容易把稀有词平滑掉。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( analyzerchar_wb, # 字符级 n-gram对中文和变体更鲁棒 ngram_range(1, 3), # 1 到 3 元 max_features50000, # 控制维度 min_df2, # 至少出现 2 次才保留 sublinear_tfTrue # 对词频做对数缩放 ) X vectorizer.fit_transform(df[text]) y df[label].valuesanalyzerchar_wb是中文场景的关键选择。中文没有天然空格按词切分依赖分词工具而垃圾短信里大量谐音、拆字、插入符号分词器很容易切错。字符级 n-gram 直接绕过分词把“中奖”拆成“中”“奖”“中奖”等组合对变体更稳。ngram_range(1,3)覆盖单字到三字组合再高会稀疏且过拟合。min_df2过滤只出现一次的特征降低噪声。sublinear_tfTrue防止某个词重复出现几十次主导权重。如果数据量超过十万条max_features可以降到 20000训练速度会明显提升准确率通常只掉不到一个点。3.2 线性模型、朴素贝叶斯和树模型的对比选型上我一般同时跑三个基线多项式朴素贝叶斯、逻辑回归、线性 SVM。朴素贝叶斯训练最快适合做第一版逻辑回归输出概率方便设阈值线性 SVM 在 TF-IDF 高维稀疏特征上通常表现最好。from sklearn.linear_model import LogisticRegression from sklearn.svm import LinearSVC from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) models { NB: MultinomialNB(alpha0.1), LR: LogisticRegression(C1.0, max_iter1000, class_weightbalanced), SVM: LinearSVC(C0.5, class_weightbalanced) } for name, model in models.items(): model.fit(X_train, y_train) pred model.predict(X_test) print(f--- {name} ---) print(classification_report(y_test, pred, digits4))class_weightbalanced在 spam 占比低时必加它会让模型对少数类样本的损失加权提升 spam 召回。MultinomialNB的alpha是平滑系数设 0.1 比默认 1.0 在短文本上更合适因为默认平滑过强会抹掉稀有词信号。LinearSVC的C控制正则强度0.5 到 1.0 之间通常够用C 越大越容易过拟合。实测中 LinearSVC 的 macro F1 往往比朴素贝叶斯高 2 到 4 个点但朴素贝叶斯训练时间只有它的十分之一。如果要做在线学习或频繁重训朴素贝叶斯更实际。3.3 评估指标不能只看准确率垃圾短信分类的评估必须看混淆矩阵。正常短信被误判为垃圾的代价远高于垃圾短信漏判。我一般把 ham 的召回率作为第一指标要求不低于 99.5%在这个约束下再看 spam 的召回。from sklearn.metrics import confusion_matrix pred models[SVM].predict(X_test) cm confusion_matrix(y_test, pred) print(cm) # 输出 [[tn fp] # [fn tp]] # ham 召回 tn / (tn fp)spam 召回 tp / (tp fn)如果 ham 召回不达标优先调低 spam 的判定阈值或者对 ham 类样本上采样。不要直接调模型参数先确认是特征问题还是阈值问题。4. 避坑与排查垃圾短信分类上线前必须过的五道坎4.1 准确率 99% 但上线就翻车现象离线评估准确率 99%上线后用户投诉正常短信被拦截。原因训练集和测试集随机划分但线上数据分布随时间漂移且测试集里没有包含用户最新的正常短信模式。解决按时间划分训练集和测试集用最近一周的数据做验证同时建立误杀样本回流机制每周把误判的 ham 加入训练集重训。4.2 中文短信分词后特征全丢现象用 jieba 分词后TF-IDF 矩阵里大量特征只出现一次模型几乎学不到东西。原因垃圾短信里谐音、拆字、插入符号导致分词器把“中奖”切成“中”“奖”两个无意义单字。解决改用字符级 n-gram或者分词前先做归一化把常见变体还原比如把“中 奖”“中-奖”统一成“中奖”。4.3 模型把“退订”当成垃圾特征现象含“退订”的正常营销短信被大量误判。原因训练集里“退订”几乎只出现在 spam 中模型学到了虚假相关。解决检查训练集标签质量把含“退订”的正常短信补进 ham 类或者在特征里加入否定词窗口但更根本的是扩充 ham 类的多样性。4.4 类别不平衡导致 spam 召回极低现象spam 占比 3%模型全预测 ham准确率仍有 97%但 spam 召回为 0。原因默认损失函数对多数类友好。解决设置class_weightbalanced或用 SMOTE 对 spam 过采样但过采样在文本高维空间容易生成无意义样本优先用类别权重。4.5 模型文件太大无法部署现象max_features50000加字符 3-gram模型文件超过 200MB移动端无法加载。解决降到max_features10000、ngram_range(1,2)或者用哈希技巧HashingVectorizer固定维度代价是失去特征可解释性。实测 10000 维字符 2-gram 在短信场景下 F1 只掉 1 个点左右。5. 把模型压到 10MB 以内并保持召回的三个技巧第一个技巧是特征裁剪加模型量化。把max_features从 50000 降到 8000ngram_range从 (1,3) 降到 (1,2)然后用joblib保存时开压缩。import joblib from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.svm import LinearSVC from sklearn.pipeline import Pipeline pipe Pipeline([ (tfidf, TfidfVectorizer(analyzerchar_wb, ngram_range(1,2), max_features8000, min_df2, sublinear_tfTrue)), (clf, LinearSVC(C0.5, class_weightbalanced)) ]) pipe.fit(df[text], df[label]) joblib.dump(pipe, sms_clf.pkl, compress3)compress3能把文件压到原始大小的三分之一左右。8000 维字符 2-gram 在 5000 条数据上训练时间不到 2 秒模型文件约 3 到 5MB。第二个技巧是用LinearSVC的dualTrue配合max_iter控制收敛。短文本上dualTrue比dualFalse快但max_iter设太小会不收敛设 2000 到 5000 之间比较稳。第三个技巧是阈值校准。LinearSVC没有predict_proba但可以用decision_function的输出做阈值调整。我一般把 ham 的判定阈值从 0 调到 -0.3牺牲一点 spam 召回换 ham 召回。scores pipe.decision_function(X_test) # 默认阈值 0调低到 -0.3 让更多样本判为 ham pred_custom (scores -0.3).astype(int)调完阈值后重新看混淆矩阵确认 ham 召回达标再上线。这套流程我跑了不下十次每次最耗时的不是调参而是清洗误杀样本。模型本身很简单难的是让它在真实流量里不误伤。希望帮到你。本文还有配套的精品资源点击获取
返回列表