恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

垃圾短信文本识别系统:BERT微调与数据清洗完整方案

  • 首页
  • 资讯中心
  • /
  • 垃圾短信文本识别系统:BERT微调与数据清洗完整方案

相关资讯

YOLOv8机场跑道FOD检测毕设实战:从数据集到部署全流程解析 2026/10/9 21:19:26
VOC+YOLO双格式282张公路落石数据集,小样本目标检测训练实战 2026/10/9 21:19:26
YOLOv5+LSTM多模态异常检测实战:从数据对齐到TensorRT边缘部署 2026/10/9 21:19:26

最新资讯

重新认识Selenium:从WebDriver机制到自动化测试的稳定落地
Claude Code Mods:从配置到钩子,打造自动化代码检查
Mycat2离线部署实战:install-template模板配置与启动避坑指南
三重积分从入门到精通:坐标系选择、积分次序与考研实战
Windows 上跑 RustFS,三个隐形坑一个比一个狠
AI大模型初探-接入API:用TaoToken统一Key打通Unity与DeepSeek

今日推荐

Codex 总用英文回答?从 AGENTS.md 到 config.toml 的中文输出调优指南
OpenClaw 自定义插件开发完整指南(2026最新版):从 TypeScript 到 npm 发布
基于Spark的电影推荐系统全链路实战:从爬虫到Web展示

本周热门

MR25H40CDF + PIC18F65K40:工业记录仪高可靠存储实战
基于STM32的数控恒压恒流电源设计:从硬件到PID调参全解析
LT9211 MIPI重定时器原理与双路扇出实战指南

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

垃圾短信文本识别系统:BERT微调与数据清洗完整方案

发布时间:2026/10/9 21:19:26
垃圾短信文本识别系统:BERT微调与数据清洗完整方案 简介本资源面向计算机相关专业学生、科研人员及行业开发者提供一套基于BERT模型的垃圾短信文本识别完整方案源自CCF大数据竞赛实践可用于毕业设计、课程设计、项目立项演示或技术研究。压缩包共47个文件约8.46MB以30个txt实验记录、7个Python源码、4个pyc编译文件、2个bat批处理脚本、2个csv结果数据及技术报告md为主覆盖数据清洗、模型训练、多分类器对比与精度评估等环节。源码上传前经过多环境测试可稳定复现并附有技术报告供参考学习。目前已有47人浏览学习。读者可获取完整赛题实现思路、数据预处理脚本、多种投票与集成模型对比结果以及可修改扩展的代码框架便于快速搭建原型、验证算法效果遇到环境配置问题还可获得远程指导与技术支持。1. 垃圾短信文本识别系统从 BERT 微调到数据清洗一套能跑通的完整方案垃圾短信文本识别系统这个题目看起来是典型的文本二分类任务但真正动手做过的人都知道难点从来不在模型结构本身而在数据。CCF 大数据竞赛这类赛题给出的原始短信数据往往夹杂着大量重复样本、标签噪声、特殊符号和极短文本直接丢进 BERT 微调F1 大概率卡在 0.9 上下上不去。我见过太多人把精力全花在换模型、调学习率上结果回头一看训练集里同一条短信出现了十几次正负样本比例接近 1:10模型学到的其实是「长度越短越像垃圾短信」这种伪特征。这套方案的核心思路很明确先用一套可复现的数据清洗流程把原始短信整理干净再用 BERT 做微调最后用阈值搜索和错误分析把边界样本捞出来。适合谁适合已经跑过 HuggingFace 基础微调、想在一个真实赛题数据上把完整链路走一遍的工程师也适合需要快速搭一个短信审核原型的小团队。下面按数据清洗、模型微调、避坑、进阶技巧的顺序展开每一步都给可抄的代码和参数说明。2. 数据清洗垃圾短信识别的地基怎么打2.1 先看清原始数据长什么样CCF 这类赛题的数据通常是一个 CSV 或 TSV字段大致是label和message两列label 用 0/1 或 ham/spam 表示。拿到数据第一件事不是写模型是写一个统计脚本把下面这些指标全部打出来总样本数、正负比例、文本长度分布min/median/max、重复文本数量、含 URL 的样本比例、含连续数字超过 8 位的样本比例、纯符号样本数量。import pandas as pd import re df pd.read_csv(sms_train.csv) df.columns [label, message] # 基础统计 print(总样本:, len(df)) print(标签分布:\n, df[label].value_counts()) print(长度分位:\n, df[message].str.len().describe()) # 重复文本检测 dup df[df.duplicated(subset[message], keepFalse)] print(重复文本条数:, len(dup)) # 含 URL 比例 url_pat re.compile(rhttps?://|www\.|\.com|\.cn) print(含URL比例:, df[message].str.contains(url_pat).mean()) # 纯符号或极短文本 print(长度3的样本:, (df[message].str.len() 3).sum())这段脚本的作用是给你一张「体检报告」。如果重复文本占比超过 5%就必须去重如果正负比超过 1:8就要考虑加权或重采样如果长度中位数只有 20 来个字符那 BERT 的 max_length 设 64 就够了设 128 纯属浪费显存。我一般会把这些统计结果存成一个 markdown 表格后面每次改清洗策略都回头对一遍。2.2 清洗规则的设计与实现清洗不是越狠越好。垃圾短信里恰恰有很多「噪声」是强信号比如「加V」「点击链接」「退订回T」这些词你如果把特殊符号全删了反而把信号删没了。我的做法是分三层处理第一层做无害归一化第二层做去重第三层做标签校验。import re import pandas as pd def normalize_text(s: str) - str: if not isinstance(s, str): return # 全角转半角 s s.replace(\u3000, ) # 统一空白 s re.sub(r\s, , s).strip() # 保留中文、英文、数字和常见标点去掉控制字符 s re.sub(r[\x00-\x1f\x7f], , s) return s def clean_pipeline(df: pd.DataFrame) - pd.DataFrame: df df.copy() df[message] df[message].map(normalize_text) # 去掉空文本 df df[df[message].str.len() 0] # 去重同文本同标签只留一条 df df.drop_duplicates(subset[message, label]) # 冲突标签处理同文本不同标签按多数投票票数相同则丢弃 conflict df.groupby(message)[label].nunique() conflict_msgs conflict[conflict 1].index if len(conflict_msgs) 0: vote df[df[message].isin(conflict_msgs)] \ .groupby(message)[label].agg(lambda x: x.mode().iloc[0] if len(x.mode()) 1 else None) vote vote.dropna() df df[~df[message].isin(conflict_msgs)] vote_df vote.reset_index() vote_df.columns [message, label] df pd.concat([df, vote_df], ignore_indexTrue) return df.reset_index(dropTrue) train clean_pipeline(pd.read_csv(sms_train.csv)) print(清洗后样本:, len(train))normalize_text里我特意没有去掉标点和数字因为「回复T退订」「拨打10086」这类模式对分类很有用。去重时用message label联合去重而不是只按 message因为同一句话在不同标签下出现说明标注本身有问题需要单独处理。冲突标签用众数投票票数相同直接丢弃——这种样本留着就是给模型喂毒。清洗完记得把正负比例再打一次如果变化超过 10%说明原始数据里重复样本严重偏向某一类。2.3 划分训练集与验证集时别踩的坑很多人用train_test_split随机划分结果验证集 F1 虚高。原因是短信数据里存在大量「模板相似」的样本比如「【某平台】您的验证码是1234」和「【某平台】您的验证码是5678」随机划分会把它们分到训练和验证两边模型其实在背模板。正确做法是按「模板」分组划分或者至少用GroupShuffleSplit按文本的归一化前缀分组。from sklearn.model_selection import GroupShuffleSplit import re def get_template(s: str) - str: # 用去掉数字后的前20个字符作为模板粗粒度标识 return re.sub(r\d, #, s)[:20] train[group] train[message].map(get_template) gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(train, groupstrain[group])) train_set train.iloc[train_idx].reset_index(dropTrue) val_set train.iloc[val_idx].reset_index(dropTrue) print(训练集:, len(train_set), 验证集:, len(val_set))这样划分后验证集 F1 通常会比随机划分低 1 到 3 个点但那个数字才是真实的。如果你拿随机划分的验证集去调阈值上线后必然翻车。3. BERT 微调参数怎么设、训练怎么盯3.1 模型选型与输入长度中文短信场景我一般直接上bert-base-chinese或者更小的hfl/rbt3。短信文本短rbt3 这种 3 层模型在 1 万条数据上微调效果和 base 差不了 0.5 个点但推理速度快 3 倍。如果数据量超过 5 万条再考虑 base。max_length 按清洗后长度分布的 95 分位来设通常 64 足够少数长短信截断即可。from transformers import BertTokenizer, BertForSequenceClassification model_name hfl/rbt3 tokenizer BertTokenizer.from_pretrained(model_name) model BertForSequenceClassification.from_pretrained(model_name, num_labels2) def encode(texts, labelsNone): enc tokenizer( texts, paddingmax_length, truncationTrue, max_length64, return_tensorspt ) if labels is not None: enc[labels] labels return encmax_length64是短信场景的甜点值。我试过 32长尾样本损失明显试过 128显存翻倍但 F1 只涨 0.1。paddingmax_length在训练时配合动态 batch 效率一般如果追求速度可以改成paddinglongest加DataCollatorWithPadding。3.2 训练参数与类别不平衡处理垃圾短信数据正负比经常是 1:5 到 1:10直接训练模型会偏向多数类。两种处理方式一是用WeightedTrainer给少数类加权二是在 loss 里用 focal loss。我一般先用加权简单可控。import torch from torch import nn from transformers import Trainer, TrainingArguments class WeightedTrainer(Trainer): def __init__(self, class_weights, *args, **kwargs): super().__init__(*args, **kwargs) self.class_weights class_weights def compute_loss(self, model, inputs, return_outputsFalse, **kwargs): labels inputs.pop(labels) outputs model(**inputs) logits outputs.logits loss_fct nn.CrossEntropyLoss(weightself.class_weights.to(logits.device)) loss loss_fct(logits, labels) return (loss, outputs) if return_outputs else loss # 按训练集分布算权重 import numpy as np counts train_set[label].value_counts().sort_index().values weights torch.tensor([len(train_set) / (2 * c) for c in counts], dtypetorch.float) args TrainingArguments( output_dir./sms_bert, learning_rate2e-5, per_device_train_batch_size32, per_device_eval_batch_size64, num_train_epochs4, weight_decay0.01, warmup_ratio0.1, evaluation_strategyepoch, save_strategyepoch, load_best_model_at_endTrue, metric_for_best_modelf1, logging_steps50, fp16torch.cuda.is_available(), )学习率 2e-5 是 BERT 微调的标准起点短信数据量小超过 3e-5 容易震荡。epoch 设 4 是因为小数据 2 到 3 轮就收敛第 4 轮是给 early stopping 留余量。warmup_ratio0.1对小数据集很重要能避免前几十步把预训练权重带偏。权重计算用总样本/(类别数*该类样本数)这是 sklearn 的 balanced 公式比手动拍一个 5.0 靠谱。3.3 评估指标与阈值搜索训练时盯 f1但 f1 默认是 0.5 阈值下的。垃圾短信场景漏判把垃圾判成正常和误判把正常判成垃圾代价不同通常漏判更严重所以阈值要往下压。训练完在验证集上做一轮阈值搜索。from sklearn.metrics import f1_score, precision_recall_curve import numpy as np def predict_probs(model, tokenizer, texts, batch_size64): model.eval() probs [] for i in range(0, len(texts), batch_size): batch texts[i:ibatch_size] enc tokenizer(batch, paddingTrue, truncationTrue, max_length64, return_tensorspt).to(model.device) with torch.no_grad(): logits model(**enc).logits probs.extend(torch.softmax(logits, dim-1)[:, 1].cpu().numpy()) return np.array(probs) val_probs predict_probs(model, tokenizer, val_set[message].tolist()) best_t, best_f1 0.5, 0 for t in np.arange(0.1, 0.9, 0.01): pred (val_probs t).astype(int) f1 f1_score(val_set[label], pred) if f1 best_f1: best_f1, best_t f1, t print(f最佳阈值 {best_t:.2f}, F1 {best_f1:.4f})阈值搜索范围我一般从 0.1 扫到 0.9步长 0.01。如果最佳阈值落在 0.3 以下说明模型对正类信心不足要回头检查是不是正样本太少或者清洗时把强信号删了。搜完阈值别急着高兴拿这个阈值去验证集上单独看 precision 和 recall如果 recall 涨了 5 个点但 precision 掉了 10 个点那这个阈值不能要。4. 避坑与排查那些让 F1 卡住不动的真实原因4.1 验证集 F1 高但测试集崩了现象本地验证集 F1 0.96提交到评测平台只有 0.88。原因几乎总是数据泄漏——验证集和训练集有重复或模板重叠。解决用第 2.3 节的 GroupShuffleSplit 重新划分并且检查训练集和验证集的模板重合率超过 5% 就说明分组没做好。4.2 模型把所有样本都预测成多数类现象训练 loss 正常下降但验证集上正类 recall 接近 0。原因通常是类别权重没生效或者 tokenizer 把短信截断得太狠正类信号全在截断部分。排查先打印训练集正负比再检查compute_loss里权重张量是否真的传到了 GPU 上最后看几条被截断的正样本确认 max_length 是否设小了。4.3 清洗后样本量骤降现象原始 10 万条清洗完只剩 6 万。原因可能是去重规则太激进或者冲突标签丢弃太多。解决把去重前后的样本按标签分别统计如果某一类掉得特别多说明该类重复率高这时候应该保留一条而不是全删。冲突标签如果超过总样本 2%要回头查标注规范而不是简单丢弃。4.4 推理时显存溢出现象训练时 batch_size 32 没事推理时 batch_size 64 就 OOM。原因是推理没有梯度累积但 attention 的中间激活在长文本上更占显存。解决推理 batch_size 降到 32或者用torch.cuda.amp.autocast()做半精度推理。另外确认model.eval()和torch.no_grad()都加了少一个显存就多占一倍。4.5 阈值在验证集上最优但线上效果差现象验证集搜出来的阈值 0.35上线后误判率飙升。原因是验证集和线上数据的分布不一致比如线上短信更长、含更多营销词。解决不要死磕一个阈值按短信长度分桶每个桶单独搜阈值或者用precision_recall_curve选一个 recall 不低于 0.95 的前提下 precision 最高的点而不是单纯最大化 F1。5. 进阶技巧用错误分析和模型融合再挤两个点5.1 错误分析怎么做才有用训练完别只看 F1把验证集里预测错误的样本全部导出来按「假阳性」和「假阴性」分两组每组随机抽 30 条人工看。我一般会重点看三类极短文本长度小于 8、含大量数字的文本、含「退订」「回复」等中性词的文本。看完你会发现很多错误是标注本身模棱两可这种样本应该从验证集里剔除而不是硬调模型。val_set[prob] val_probs val_set[pred] (val_probs best_t).astype(int) fp val_set[(val_set[pred] 1) (val_set[label] 0)] fn val_set[(val_set[pred] 0) (val_set[label] 1)] print(假阳性示例:\n, fp[message].head(10).tolist()) print(假阴性示例:\n, fn[message].head(10).tolist())5.2 模型融合的轻量做法单模型卡在 0.95 左右时可以试两个轻量融合一是 BERT TextCNN 概率平均二是不同随机种子的 BERT 做投票。TextCNN 用清洗后的文本训一个 3 层卷积训练成本极低和 BERT 的错误样本重合度通常只有 60% 左右融合能涨 0.5 到 1 个点。投票用 3 个种子每个种子跑 3 epoch取概率均值比单模型稳。# 概率融合示例 final_prob 0.7 * bert_probs 0.3 * cnn_probs final_pred (final_prob best_t).astype(int) print(融合后 F1:, f1_score(val_set[label], final_pred))权重 0.7/0.3 是经验值如果 CNN 单独 F1 能到 0.93 以上可以调到 0.6/0.4。融合前记得确认两个模型用的是同一份验证集划分否则数字不可比。5.3 一个我常犯的错早期做这类赛题我总想把清洗做到极致结果把「加V」「点击链接」这些词归一化没了模型反而学不到东西。后来养成一个习惯每改一版清洗规则先跑一版基线模型对比 F1 变化涨了才保留。清洗规则不是越多越好能提升验证集 F1 的才是好规则。另外阈值别在训练完立刻定等错误分析做完、确认验证集干净了再搜否则你搜到的只是噪声上的最优。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号