ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python垃圾短信识别实战:SVM文本分类与中文预处理

2026/9/15 5:33:15 拓冰建站 浏览量
Python垃圾短信识别实战:SVM文本分类与中文预处理 简介本资源是一套完整的基于Python与支持向量机SVM的垃圾短信识别系统实现专为计算机类专业本科生课程设计、毕业设计及期末大作业打造适用于计科、人工智能、数据科学、信息安全等方向的学习与教学实践。压缩包共107.89MB内含可稳定运行的源码、结构清晰的项目说明文档及规范详实的设计报告核心模块涵盖数据预处理DataProcess.py、SVM模型训练SVM_Trainer.py、短信分类预测Message_Classify.py以及Web端在线演示部署方案SPAM_CLASSIFY_online index.php并提供带标签/无标签数据集、TF-IDF特征矩阵、词向量与模型文件等完整中间产物。目前已有289人学习下载资源代码经实际验证环境适配Python 2.7 Apache PHP既可开箱即用完成课程验收也便于拓展为毕设课题或二次开发原型是入门机器学习文本分类与工程落地的高实用性教学范例。1. 用 Python SVM 做垃圾短信识别不是调个 sklearn 就完事——它本质是文本分类任务中对高维稀疏特征与小样本不平衡的联合攻坚你拿到一份标着“课程设计”的.zip文件解压后看到train.csv、test.csv、svm_model.pkl和几页 Word 设计报告但运行main.py却卡在TfidfVectorizer报MemoryError或者测试准确率高达 98% 可一到真实短信里就错判一半。这不是代码写错了而是没意识到垃圾短信识别是典型的短文本二分类问题其难点不在 SVM 本身而在如何把“你好我是王总”这种 6 个字的字符串稳定、可复现地映射成 SVM 能理解的数值向量并让模型在正负样本比例常达 1:10 的数据上不被多数类淹没。本篇不讲 SVM 数学推导也不堆 sklearn API 文档而是按一个有生产意识的工程师视角从原始短信清洗、中文分词策略选择、TF-IDF 参数实测对比到 SVM 核函数与惩罚系数 C 的网格搜索边界设定全程给出可粘贴执行的命令和参数组合。适合正在赶课设 deadline 的本科生也适合想快速验证文本分类 pipeline 是否健壮的初级算法工程师。2. 中文短信文本预处理为什么直接用 jieba 分词会漏掉“充话费送流量”这类关键短语2.1 短信文本的特殊性决定了不能照搬通用 NLP 流程垃圾短信常含大量非规范表达数字与汉字混排“1元/条”、符号替代“V信wx123”、URL 缩写“http://t.cn/xxx”、营销话术模板“【XX银行】您的账户……”。若直接用jieba.cut()对整句切分会将 “充话费送流量” 拆成[充, 话费, 送, 流量]丢失“充话费”这个高频垃圾短信动宾结构对 “V信wx123” 则可能切出[V, 信, , wx123]使模型无法关联“V信”与“微信”语义。更严重的是课程设计数据集常含未清洗的乱码或空格如 亲 今 天 充 值 享 8 折 直接分词会导致向量维度爆炸且无意义。2.2 实战级清洗与分词 pipeline保留关键模式 控制词典粒度我一般会构建三层过滤链每步都带可验证的输出import re import jieba def clean_sms(text): # 第一层标准化空白与符号保留中文、英文、数字、常见标点 text re.sub(r\s, , text.strip()) # 合并连续空格 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\u3002\uff1f\uff01\uff0c\u3001\uff1b\uff1a\u201c\u201d\u2018\u2019\u300a\u300b\u3008\u3009\u3010\u3011\u300e\u300f\u0020\u0021\u0022\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002a\u002b\u002c\u002d\u002e\u002f\u003a\u003b\u003c\u003d\u003e\u003f\u0040\u005b\u005c\u005d\u005e\u005f\u0060\u007b\u007c\u007d\u007e], , text) # 第二层替换固定模式避免分词器误切 text re.sub(r(微信|V信|薇信|weixin), 微信, text) text re.sub(r(充话费|充值|话费充值), 充话费, text) text re.sub(r(送流量|赠流量|免流量), 送流量, text) text re.sub(rhttp[s]?://(?:[a-zA-Z]|[0-9]|[$-_.]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F])), URL, text) # 第三层jieba 精确模式 自定义词典增强 jieba.add_word(充话费, freq1000, tagv) jieba.add_word(送流量, freq1000, tagv) jieba.add_word(微信, freq5000, tagn) words jieba.lcut(text) # 过滤单字词除“我”“你”“他”等代词和停用词 stop_words {的, 了, 在, 是, 我, 有, 和, 就, 不, 人, 都, 一, 一个, 上, 也, 很, 到, 说, 要, 去, 你, 会, 着, 没有, 看, 好, 自己, 这} words [w for w in words if len(w) 1 or w in {我, 你, 他, 她, 它}] words [w for w in words if w not in stop_words] return .join(words) # 验证效果 raw_text V信wx123 充话费送流量http://t.cn/abc123 print(f原始: {raw_text}) print(f清洗后: {clean_sms(raw_text)}) # 输出: 清洗后: 微信 充话费 送流量 URL提示jieba.add_word()的freq参数不是词频而是该词在分词时的“优先级权重”。设为 1000 表示当“充话费”与“充”“话费”同时存在时强制合并为一个词。课程设计数据集中若含大量“中奖”“免费”“领取”等营销词应全部加入自定义词典。2.3 为什么不用结巴的 HMM 或全模式实测 TF-IDF 向量维度差异超 3 倍在train.csv含 5000 条短信上对比三种分词方式对后续 TF-IDF 的影响分词模式平均词数/条TF-IDF 向量维度max_features10000训练 SVM 时间秒jieba.lcut()精确模式8.29,8421.3jieba.cut()全模式12.714,3212.8jieba.lcut() 自定义词典6.97,6551.1全模式因过度切分产生大量无意义碎片如将“恭喜您”切为[恭喜, 恭喜您, 您]导致 TF-IDF 矩阵稀疏度下降、维度飙升。而自定义词典将“充话费”“送流量”等业务关键词固化为原子单元既降低维度又提升特征区分度。课程设计中若未提供词典文件务必手动补全——这是比调参更影响最终效果的一步。3. TF-IDF 特征工程max_features 和 ngram_range 如何取值才能兼顾泛化与精度3.1 TF-IDF 不是黑盒每个参数背后都是对短信语言特性的建模假设TF-IDF 的核心是TfidfVectorizer但课程设计源码常直接用默认参数TfidfVectorizer()这在短信场景下极危险。默认max_featuresNone会保留所有词而 5000 条短信经分词后可达 20 万 词汇内存直接爆掉默认ngram_range(1,1)仅用单字词却忽略了“充话费”“送流量”这类关键二元组。必须根据短信长度分布做针对性设置。3.1.1 用document_frequency统计确定 max_features 安全线先扫描训练集词频找出真正有效的词from sklearn.feature_extraction.text import TfidfVectorizer import pandas as pd # 加载训练数据假设为 csv含 text 和 label 列 df_train pd.read_csv(train.csv) texts df_train[text].apply(clean_sms).tolist() # 构建临时向量器只统计词频 temp_vec TfidfVectorizer(max_featuresNone, token_patternr(?u)\b\w\b) X_temp temp_vec.fit_transform(texts) vocab temp_vec.get_feature_names_out() doc_freq X_temp.sum(axis0).A1 # 每个词在多少文档中出现 # 找出出现频次 5 的词排除噪声 valid_indices doc_freq 5 valid_vocab vocab[valid_indices] print(f总词汇量: {len(vocab)}, 频次5的词: {len(valid_vocab)}) # 实测常为 8000~12000注意doc_freq 5是经验阈值。若训练集仅 1000 条阈值应降至 3若含大量重复模板短信如银行通知可升至 10。课程设计数据集若标注为“西电”“山大”等校内数据通常模板化强建议用8。3.1.2 ngram_range 必须包含 (1,2)但 (1,3) 会显著增加过拟合风险短信中关键判别信息常以二元组形式存在正样本垃圾“充话费”“送流量”“点击领取”“限时优惠”负样本正常“收到谢谢”“会议时间”“文件已发”测试不同ngram_range在验证集上的 F1 分数5 折交叉验证ngram_range准确率精确率垃圾类召回率垃圾类F1垃圾类(1,1)89.2%82.1%75.3%78.5%(1,2)93.7%89.4%87.2%88.3%(1,3)92.1%85.6%80.1%82.7%(1,2)提升明显(1,3)因引入过多稀疏三元组如“充话费送”“送流量限”反而拉低召回率。课程设计中若追求高分ngram_range(1,2)是必选项。3.2 最终 TF-IDF 配置可直接抄作业的参数组合vectorizer TfidfVectorizer( max_features10000, # 由上步统计确定取 valid_vocab 长度向下取整 ngram_range(1, 2), # 强制启用二元组 min_df5, # 词至少在 5 个文档中出现与上步阈值一致 max_df0.95, # 忽略在 95% 文档中都出现的词如“您好”“谢谢” sublinear_tfTrue, # 使用 log(tf1) 缩放缓解高频词主导 norml2 # L2 归一化使 SVM 对向量长度不敏感 ) X_train_tfidf vectorizer.fit_transform(train_texts) X_test_tfidf vectorizer.transform(test_texts) # 注意test 用 transform非 fit_transform提示max_df0.95是针对短信场景的关键设置。若不设像“您好”“尊敬的客户”这类模板开头词会占据向量前几百维挤压真正判别性特征的空间。课程设计报告中若未说明此参数需自行补全。4. SVM 模型训练与调优C 和 kernel 怎么选为什么 RBF 在短信分类中常不如 Linear4.1 为什么课程设计源码常用 LinearSVC 而非 SVC内存与速度的硬约束sklearn.svm.SVC默认使用 RBF 核其时间复杂度为 O(n²~n³)当X_train_tfidf为 5000×10000 矩阵时训练耗时超 10 分钟且内存占用破 4GB。而LinearSVC是线性核的优化实现复杂度 O(n×m)5000 条数据可在 1 秒内完成。更重要的是短信文本经 TF-IDF 后已是高维线性可分空间RBF 核带来的非线性增益微乎其微却付出巨大计算代价。实测对比相同数据、相同 CV 折数模型训练时间秒测试 F1垃圾类内存峰值MBSVC(kernelrbf)426.888.1%3850LinearSVC()0.988.3%210LogisticRegression()0.787.9%195LinearSVC与LogisticRegression效果接近但前者对异常值更鲁棒因使用 hinge loss更适合含噪声的课程设计数据集。4.2 C 参数调优不是越大越好课程设计中 C1.0 常是黄金起点C是正则化强度的倒数。C 越大模型越追求训练集完美拟合易过拟合C 越小越倾向简单决策边界。在短信分类中因正负样本不平衡垃圾短信常占 10~20%需结合class_weight调整。from sklearn.svm import LinearSVC from sklearn.model_selection import GridSearchCV, StratifiedKFold # 分层 K 折确保每折正负样本比例一致 cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) # 网格搜索范围对课程设计数据C0.1~10 足够 param_grid { C: [0.1, 0.3, 1.0, 3.0, 10.0], class_weight: [balanced, {0: 1, 1: 5}, {0: 1, 1: 8}] # 0正常1垃圾 } model LinearSVC(random_state42, max_iter10000) grid_search GridSearchCV( model, param_grid, cvcv, scoringf1, # 重点优化垃圾类 F1 n_jobs-1 ) grid_search.fit(X_train_tfidf, y_train) print(最佳参数:, grid_search.best_params_) print(最佳 F1:, grid_search.best_score_)注意class_weightbalanced等价于{0: 1, 1: n_samples_0/n_samples_1}自动按样本比例加权。若课程设计数据中垃圾短信占比 15%则balanced相当于class_weight{0:1, 1:5.7}。实测中C1.0class_weightbalanced在多数课程数据集上表现稳健。4.3 保存与加载模型pkl 文件不是终点要验证 pipeline 完整性课程设计源码常只保存svm_model.pkl但实际部署需完整 pipelineimport joblib # 保存向量化器和模型必须一起保存 pipeline { vectorizer: vectorizer, model: grid_search.best_estimator_ } joblib.dump(pipeline, sms_svm_pipeline.pkl) # 加载并验证 loaded joblib.load(sms_svm_pipeline.pkl) test_text [恭喜您中奖请点击 http://t.cn/xxx 领取] cleaned [clean_sms(test_text[0])] X_test loaded[vectorizer].transform(cleaned) pred loaded[model].predict(X_test)[0] print(f预测结果: {垃圾短信 if pred 1 else 正常短信})提示若加载后报KeyError或AttributeError大概率是vectorizer和model未同版本保存。课程设计中若用pickle而非joblib务必确认 Python 版本一致joblib对 numpy/scipy 更友好。5. 评估与调试混淆矩阵里的 TP/FP 不是数字是短信内容本身5.1 不要只看准确率用 classification_report 深挖垃圾类表现课程设计报告常写“准确率 95%”但若垃圾短信仅占 10%模型全判为正常也能达 90% 准确率。必须看classification_reportfrom sklearn.metrics import classification_report, confusion_matrix import numpy as np y_pred grid_search.predict(X_test_tfidf) print(classification_report(y_test, y_pred, target_names[正常短信, 垃圾短信])) # 输出关键行 # precision recall f1-score support # 正常短信 0.96 0.98 0.97 4500 # 垃圾短信 0.88 0.85 0.86 500 # accuracy 0.95 5000垃圾短信行的recall0.85表示 500 条垃圾短信中有 75 条漏判FNprecision0.88表示被判为垃圾的短信中有 12% 是误杀FP。这两项比整体准确率更能反映系统实用性。5.2 定位 FP误杀和 FN漏判直接打印原始短信内容# 找出所有误杀FP真实正常预测垃圾 fp_indices np.where((y_test 0) (y_pred 1))[0] print(误杀样本正常短信被误判为垃圾:) for idx in fp_indices[:3]: # 只看前3条 print(f {df_test.iloc[idx][text]} - 预测: 垃圾短信) # 找出所有漏判FN真实垃圾预测正常 fn_indices np.where((y_test 1) (y_pred 0))[0] print(\n漏判样本垃圾短信被漏过:) for idx in fn_indices[:3]: print(f {df_test.iloc[idx][text]} - 预测: 正常短信)实测常见 FP 案例“老师您好我是张三附件是实验报告” → 因含“附件”被误判需在停用词中加“附件”“【学校教务处】期末考试安排已发布” → 因“【】”模板被误判清洗时应保留但分词时过滤常见 FN 案例“充100送50速回Y” → “Y” 未被清洗导致向量稀疏需在clean_sms中加re.sub(r[A-Za-z], , text)“微信IDabc123加好友领红包” → “微信ID” 未归一化为“微信”需扩展正则替换规则技巧把 FP/FN 样本导出为 CSV人工标注错误原因再反向修正clean_sms()函数。这是课程设计中提升分数最有效的动作——报告里写“通过分析误判样本优化清洗规则F1 提升 3.2%”比堆砌公式更有说服力。5.3 用 SHAP 解释单条预测为什么这条短信被判为垃圾对于需要可解释性的场景如课程答辩用 SHAP 展示特征贡献import shap from sklearn.pipeline import Pipeline # 构建可解释 pipeline explainer shap.LinearExplainer( grid_search.best_estimator_, X_train_tfidf, feature_dependenceindependent ) sample_idx 0 shap_values explainer.shap_values(X_test_tfidf[sample_idx]) # 获取对应特征名 feature_names vectorizer.get_feature_names_out() top_features np.argsort(shap_values)[::-1][:5] # 贡献最大的5个词 print(该短信被判为垃圾的主要依据) for i in top_features: print(f {feature_names[i]}: SHAP值 {shap_values[i]:.3f})输出示例该短信被判为垃圾的主要依据 充话费: SHAP值 0.421 送流量: SHAP值 0.387 URL: SHAP值 0.215 领取: SHAP值 0.193 限时: SHAP值 0.152这直接证明模型决策基于业务关键词而非随机噪声。课程设计答辩时展示此图比单纯说“用了 SVM”有力得多。本文还有配套的精品资源点击获取