上个月我家里人收到一条短信,内容大概是"您的医保卡已被暂停使用,请点击链接认证"。要不是我平时天天跟这类文本打交道,真的很难一眼看出是骗局。那个页面做得像模像样,域名仿得也很接近,稍不注意就会输银行卡号。这事之后我下定决心,认真把"用人工智能识别诈骗短信"做成一个能落地的小项目,而不只是停留在概念上。
这个项目看起来高大上,其实拆开看就一件事:把一个短信文本分类成"诈骗"还是"正常"。技术原理上属于人工智能里的自然语言处理分支,更具体一点是文本分类任务。但真正去做的过程中,你会发现难点不在模型本身,而在于数据、特征、评估口径和后续迭代。这篇文章我就把自己从零开始做诈骗短信识别项目的完整过程整理出来,包括语料从哪来、模型怎么选、训练踩了什么坑、上线后怎么持续更新,希望对正在准备相关毕设、做安全风控产品,或者单纯对AI落地感兴趣的朋友有参考价值。
1. 先弄清"敌情":诈骗短信的内容规律与分类框架
识别诈骗短信的前提,是了解诈骗短信这东西长什么样。很多人觉得骗子的话术一定很精致,其实恰恰相反,绝大多数诈骗短信在文字层面相当粗糙,但正好能用粗糙完成一次"用户筛选"。
1.1 诈骗短信为什么总能骗到人
一条诈骗短信在文案上往往刻意制造紧张感,比如"账户异常""将被冻结""逾期未处理将销户",配合一个短时限,逼你在情绪上头的时候做决定。这种短信不需要骗过所有人,只要一万个人里有一个点链接,它就能回本。所以以低质量、低成本、广撒网的方式发送,是这类黑产的典型特征。
这一特征对AI来说反而是好消息。因为低质量内容在词汇分布、句法结构、符号使用上都和正规商家的短信有明显差异,模型比较容易抓出规律。真正难的是那些仿冒官方、文字打磨过的样本,这也是为什么我们不能只靠规则、需要上模型的原因。
1.2 常见诈骗短信的类型拆解
我在清洗语料时习惯先给诈骗短信做一次粗分类,这样后续分析特征和评估模型误报时心里有底。常见的类型有下面这些:
| 类型 | 典型话术逻辑 | 可提取特征 |
|---|---|---|
| 银行/支付风控 | 告知账户风险,诱导登录虚假页面 | 银行名称+风险词+短链接 |
| ETC/高速认证 | 提醒ETC过期,诱导填写信息 | ETC、认证、24小时 |
| 快递理赔 | 包裹丢失双倍赔偿,诱导下载App | 快递、理赔、添加客服 |
| 社保/医保停用 | 社保卡停用,点击链接恢复 | 社保、医保、停用 |
| 中奖/退费 | 声称获得奖品或教育退费 | 中奖、退费、点击领取 |
| 招嫖/刷单 | 声称轻松兼职、同城服务 | 高薪、日结、二维码 |
| 冒充熟人 | 换号借钱、帮忙转账 | 我是XXX、换号、转账 |
这套分类不需要做得特别细,重点是为了帮我做两件事:第一,评估训练集覆盖度,看看某些类型样本是不是太少;第二,上线后遇到误报,能快速定位模型到底把什么内容搞混了。这里有个很实用的心得:分类体系在项目第一天就要搭好,不要等到模型出了问题再补。
1.3 识别的边界:我们能做的是报警,不是拦截
这里要明确一点:用AI识别诈骗短信,本质上是提供一个"风险判断结果"。至于短信从运营商侧怎么拦截、设备侧怎么提示,那是另一个工程问题,比如接入运营商网关、做终端安全软件。作为个人项目或风控服务,通常只需要返回一个风险等级,并把这个结果吐给上层业务系统就够了。
所以我的整个架构都围绕"判风险"来设计,不掺和那些需要运营商底层能力的事。一个人也能在本地完整跑通,效果也能量化评估,这是项目能持续做下去的基础。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据从哪来:语料清洗、类型标注和样本均衡问题
所有做过文本分类项目的人都会告诉你一句话:垃圾进,垃圾出。诈骗短信识别最大的门槛不是算法,而是没有现成的、干净的、标注准确的训练数据。这也是我开始时最头疼的部分。
2.1 可用语料来源与脱敏原则
目前可合法获取的数据大致有这么几类:
- 公开的垃圾短信语料库:国内外都有高校或安全社区维护的短信集合,包含normal和spam两类,里面有一部分spam其实就是诈骗内容。这类数据的优点是量大,缺点是标签比较粗,只分垃圾/正常,没有细分诈骗;而且语料可能偏老。
- 安全厂商开放的风控样本:少数安全公司会在博客或GitHub开放脱敏样本,通常已经去掉手机号、卡号,直接可用。
- 用户投递/自己积累的投诉样本:如果你有实际业务场景,比如做个举报入口,用户主动标记的短信是最宝贵的语料。但这需要时间,还要做好隐私保护。
我自己做的时候以公开spam语料为起点,再人工从投诉社区收集了一批描述诈骗短信的帖子,自己整理成文本。整个过程非常费手,但必须强调: 千万别为凑样本去编造"诱导性诈骗话术"再放到数据集里训练,一是容易踩法律红线,二是会产生误导,让模型学到你自己想出来的"模式"。正规做法是把真实出现过的、脱敏后的样本拿来做标注。
2.2 清洗流程:几个容易被忽略的细节
短信文本和新闻、评论不一样,它很短,但包含大量噪音。以下清洗步骤是必须做的:
- 统一大小写与全角半角:苹果和安卓短信、不同运营商下发渠道,字符格式不一样。
- 繁体转简体:台湾、香港的诈骗短信可能用繁体,但很多模型训练语料以简体为主,不转会有大量低频词。
- 手机号、银行卡号等敏感信息脱敏:直接替换成"TEL""CARD"等占位符,而不是删除。因为有没有留电话本身是一个特征,直接删了会丢失信息。
- 保存URL的结构特征:不要简单把链接删掉。我会把URL拆成域名部分和路径部分,比如把"点击 http://example.com/abc 查询"变成"点击 URL_DOMAIN=example.com URL_PATH=/abc 查询",让模型学结构。
- 去除重复样本:同一个号码批量发送的短信高度相似,如果不做去重或相似度处理,训练集里同一类的样本会被放大很多,造成过拟合。
我第一版训练时偷懒没做繁体转换,结果验证集上跑分还可以,一上真实环境就发现识别率很差,后来定位到原因:线上来了一条繁体诈骗短信,分词出来全是生僻词,模型判断为正常,白白放走一条。这种坑只有经历过才会长记性。
2.3 标签颗粒度与样本不均衡处理
标签至少要分两级。第一级是二分类:正常短信 vs 可疑短信。第二级是可疑短信的大类,比如前面列举的银行风控类、ETC类、社保类等。
只做二分类也能上线,但做二级分类有一个明显好处:当模型把一条正常短信判成可疑短信时,你可以看到它是按哪个类型逻辑判的,方便快速定位误报原因。
诈骗短信和正常短信的比例在真实场景里常常是1:100甚至更悬殊,直接训练会让模型学到"大部分都是正常,少报诈骗也能有高准确率"。处理办法我有三个经验:
- 训练阶段把负样本数量控制在正样本的2到4倍,不建议太多。
- 分类器设置类别权重,比如让模型对少样本的诈骗类别做加权。
- 用一些简单的数据增强手段,比如同义词替换、句子中间插入空格、随机打乱短句顺序,但不要做太夸张的改写,否则短信那种"口语化+错别字"的真实特征会被抹掉。
数据这块我前后花了接近三周,训练调优只需要两三天。这个时间分配在NLP项目里非常正常,没有捷径。
3. 模型怎么选:为什么我用"规则召回+两阶段分类器"而不是直接上大模型
很多人一听"人工智能识别诈骗短信",第一反应是直接调ChatGPT或者通义千问写个提示词来判断。这个思路在Demo阶段确实能跑,特别是拿来快速验证一两条短信很好用,但真实落地会碰到成本、延迟、解释性三座大山。
3.1 为什么不建议"纯大模型方案"
一条短信若被运营商或安全软件逐个调大模型接口判断,按每日千万级的消息量算,光是token费用就是天文数字。这里涉及人工智能里常见的"算力—token—成本"三角关系:token是模型计费和处理的最小单位,输入一句话、输出一个判断都按token计费,请求量一大,算力和成本直接爆炸。
更麻烦的是响应延迟。短信是异步消息,晚几秒识别其实倒还好,但如果系统要同步返回结果给用户App弹窗提示,延迟一高体验就崩了。大模型还不方便离线部署,个人服务器跑不动大规模参数量模型。所以我把大模型定位成"辅助工具"而不是"主力路口"。
3.2 第一层:用规则引擎做高召回召回
规则引擎其实不是人工智能,但它在整个系统里扮演"粗筛"角色,能把可疑样本的规模降下来。
我在规则层维护四类信号:
- URL信号:短链接域名、免费域名、刚注册的域名、与官方域名相似但拼写不对的域名(比如"10086-verify.com"这种)。
- 关键词信号:账户冻结、链接认证、额度调整、医保停用等强风险词。
- 发送者特征:非官方号码段、境外号码、伪造的106号段。
- 行为信号:短信里出现电话+网址同时要你尽快处理。
任何一条短信命中任意两个以上信号,就进模型精判环节;一个都没命中,直接放行。这层规则会有误伤,但没关系,模型会把误伤拉回来。它的价值在于:模型不需要在全部短信流上逐条预测,只需要处理大约10%的候选集,极大降低了算力成本。
3.3 第二层:传统机器学习与预训练模型的分层配合
在候选集上,我先后试验了两种主力模型。
| 对比维度 | XGBoost + TF-IDF | 中文BERT系模型 |
|---|---|---|
| 输入特征 | 词频向量、N-gram、URL特征 | 整条文本语义 |
| 对复杂句的识别 | 一般,依赖词表 | 较好,能理解语境 |
| 训练成本 | CPU可跑,几十秒 | GPU/Colab可跑,几十分钟 |
| 推理速度 | 极快,适合高并发 | 相对慢,但几十毫秒级别可接受 |
| 可解释性 | 通过特征重要性追溯 | 需要用其他工具近似解释 |
| 样本需求量 | 几千条就能有不错效果 | 越少越容易过拟合 |
我在CPU机器上先用XGBoost做了一套基线模型,输入主要是TF-IDF向量加上URL相关的手工特征。这套基线效果好得超过预期,对文本比较"模板化"的诈骗短信识别率很高。但它有个弱点:一旦骗子换一种话术,断词特征变化很大,模型就识别不出来。
于是我又用中文ALBERT(BERT的一种轻量版本)训练了一个语义模型作为"精判员"。这个模型看的是整句话的上下文,即使出现没见过的组合,也能根据语义倾向做出判断。最后我让两个模型做投票,只有两个模型都判定为正常时才放行,任何一个判高风险就走限制流程。这样牺牲了一点点正常短信的通过率,但把所有"可疑"都留在了网内。
3.4 大模型在场外扮演"新话术挖掘器"
主力模型有一个天然缺陷:它训练时没见过的新骗术,推理时也大概率认不出。诈欺骗术更新极快,靠人工去搜集新类型再标数据,速度永远跟不上。
我解决这个问题的办法,是让大模型担任"场外教官"。每周我抽一批未被识别为诈骗、但用户投诉量上升的短信,丢给一个大模型做二次研判。大模型判断出疑似新骗术的样本,会进入人工复核池,复核通过后作为新训练语料,重新微调或增量训练小模型。这种做法用到的思路,本质上就是知识蒸馏——用大模型的能力去生产训练数据,再把能力"压缩"回轻量模型里,成本和延迟都降下来了。
这部分如果资金和算力充裕可以直接用开放的模型API,如果不想额外花钱,也可以定期人工导入。只要坚持这个"新样本采集—大模型初筛—人工复核—回流训练"的循环,系统就越用越聪明。
4. 把文本变成模型认识的数字:预处理、向量化和训练实现
做完模型选型,接下来说说代码落地层面的细节。这一章我尽可能写得直白,让不是算法出身的读者也能看懂每一步在干什么。
4.1 文本预处理的三个"反直觉"细节
刚才说了通用的清洗步骤,训练模型前还有三个细节容易被新手忽略。
第一个是分词前不要急着去掉标点。短信里"https://xxx.xxx/abc"这种整体结构、感叹号密度、全角符号等都可能携带风险信号。我的做法是先提取URL、手机号、QQ号等实体做占位,然后再进行分词和去停用词。
第二个是不要把所有数字都归一化。比如"额度提升至500000"里的数字大小是有含义的,但不同诈骗话术里的金额没有统一规律。直接保留原始数字会让特征空间爆炸,我把数字按位数归类,比如"5万""50万""500万"分别变成"数字5位数""数字6位数",既压维度,又保留了"大额利诱"这个信号。
第三个是同一句话要保留多个粒度。中文里"您的ETC已停用"和"您的etc已停用"其实是同一件事,但分词结果完全不同。我用jieba做细粒度分词的同时,把整句原文也保留一份用于计算N-gram特征。N-gram就是连续N个字的切片组合,比如2-gram会把"您的ETC"切出"您的""ETC"两个相邻词组合,能抓到不少分词器漏掉的边界。
4.2 基线模型:XGBoost搭配TF-IDF的完整流程
这里给出一份精简版核心代码,方便你搭出第一个能跑的流程:
python复制import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from xgboost import XGBClassifier
from sklearn.metrics import classification_report
# 假设df中有两列:text为清洗后的短信文本,label为0/1标签
df = pd.read_csv("sms_clean.csv")
X_text = df["text"]
y = df["label"]
# TF-IDF向量化:同时使用1-gram和2-gram
vectorizer = TfidfVectorizer(
ngram_range=(1, 2),
max_features=50000,
sublinear_tf=True
)
X_vec = vectorizer.fit_transform(X_text)
# 类别不均衡时提高诈骗类权重
scale_pos_weight = y.value_counts()[0] / max(y.value_counts()[1], 1)
X_train, X_val, y_train, y_val = train_test_split(
X_vec, y, test_size=0.2, random_state=42, stratify=y
)
model = XGBClassifier(
n_estimators=300,
max_depth=6,
learning_rate=0.1,
scale_pos_weight=scale_pos_weight,
eval_metric="auc",
tree_method="hist"
)
model.fit(X_train, y_train)
y_pred = model.predict(X_val)
print(classification_report(y_val, y_pred, target_names=["normal", "fraud"]))
在实际项目里,我还会把URL域名特征、短链特征、发送号码特征拼成一个额外特征矩阵,和TF-IDF矩阵横向合并后训练。XGBoost对这类稀疏混合特征处理得很好,不需要做太多归一化处理。
4.3 语义精判:从token到句子向量的直觉理解
BERT系模型的工作原理,可以用一句话概括:输入一句话时,模型先通过分词器把句子切成token,再把每个token映射成向量,然后利用注意力机制让句子里的每个字与整句其他字发生信息交互,最终把第一个token位置(CLS标记)的输出当作整句语义的浓缩。
这句话看起来长,但你不需要钻到数学细节里。在本项目里我只需要知道:把几百个token的权重矩阵训练好之后,模型就能理解"您的银行卡已被锁定"和"警告,你的银行卡被锁定了"是不是近似语义。
用Hugging Face Transformers库训练一个文本分类模型,核心代码如下:
python复制from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
# 轻量中文ALBERT
model_name = "albert-chinese-tiny"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(
model_name, num_labels=2
)
# 短信很短,max_len设成64足够覆盖绝大多数内容
def tokenize_fn(batch):
return tokenizer(
batch["text"],
padding="max_length",
truncation=True,
max_length=64,
return_tensors="pt"
)
# 训练参数要点:batch size不宜太大,epochs设3~5轮,加early stopping
training_args = TrainingArguments(
output_dir="./saved_model",
learning_rate=3e-5,
per_device_train_batch_size=32,
per_device_eval_batch_size=64,
num_train_epochs=4,
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
metric_for_best_model="eval_f1"
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=val_dataset,
tokenizer=tokenizer,
)
trainer.train()
之所以max_len设成64,是因为绝大多数短信正文不会超过这个长度。如果设成512,会让模型浪费大量算力在无意义的padding token上,而且训练和推理都会变慢。
4.4 训练时最容易翻车的三个地方
第一个是训练集和验证集的切分必须按时间,不能完全随机切。诈骗短信的话术在时间轴上会演化,如果随机切,训练集里可能有未来时间段的样本,评估分数虚高。我习惯按样本的产生时间排序,取前80%做训练、后20%做验证。这样线上表现才贴近真实。
第二个是验证集里必须混合真实的正常短信。很多公开垃圾短信语料里,正常短信都偏"商家营销类",比如促销、物流通知。但真实用户收到的正常短信还包括验证码、银行动账提醒、学校通知。这些文本的用词和诈骗短信差异很大,混进去之后模型会误杀一些正常内容。我后来特意从自己手机里收集了大量真实正常短信加进训练集,模型误报率立刻降了一个数量级。
第三个是保存模型时把tokenizer一起保存,这一点特别容易忘。加载模型时如果忘了加载配套tokenizer,文本会被按错误的规则切成token,模型预测效果直接崩。我在项目里是这么处理的:
python复制model.save_pretrained("./saved_model")
tokenizer.save_pretrained("./saved_model")
5. 不唯准确率论:反诈场景要盯住漏报率和误伤率
模型训练完,第一件事不是看loss曲线,而是看分类报告。很多刚刚上手的人会盯着准确率,但准确率在样本不均衡的诈骗识别场景里是个会骗人的指标。如果99%的短信都是正常的,模型只需要把所有短信都判成正常,准确率就是99%。可这样的模型毫无用处。
5.1 理解精确率、召回率和它们背后的代价
在二分类里,我们通常把"诈骗短信"当作正样本。于是有四个数字组成的混淆矩阵:真正例、假正例、真负例、假负例。对应到业务语言:
- 精确率:模型说"这是诈骗"的短信里,真的是诈骗的比例。精确率低,意味着系统频繁误伤正常短信。
- 召回率:真实诈骗短信里,模型成功找出来的比例。召回率低,意味着漏掉了很多诈骗。
在短信识别里,这两种错误的代价严重不对称。漏掉一条诈骗短信,用户可能因此损失几千上万块;但如果系统每10条正常里就误拦一条,用户很快就会想关掉这个功能。
所以正确的策略是:在保证不漏掉关键高危样本的前提下,尽量提高精确率。我在代码里不是直接用模型预测的0/1标签,而是拿模型输出的概率分数再设阈值。分数大于0.95直接拦截,0.7到0.95标记为高危但不直接拦截,低于0.7但规则层召回的做一般提醒。
5.2 我实际跑出来的指标参考
用大约两万条训练数据、三千条验证数据,XGBoost基线模型的验证结果大概是:
- 正常短信精确率:0.97
- 正常短信召回率:0.86(说明还有不少正常被误伤)
- 诈骗短信精确率:0.76
- 诈骗短信召回率:0.95
ALBERT语义模型单独跑出来,诈骗短信召回率能到0.97,精确率也能到0.82,明显比词袋模型好。两个模型融合之后,验证集上的诈骗召回率0.96、精确率0.88。这个数字在个人项目里已经够用,但离商用还有距离。商用系统通常还需要白名单放行、号码信誉库、团伙关联分析等多层防线一起上。
5.3 用"每周误报日志"代替一次性的指标汇报
模型上线不是终点,因为真实短信流和训练集的分布从来都不一样。我养成了一个习惯:每周导出被模型判为高风险但用户没有举报的短信,人眼扫一遍,分成三堆:真诈骗、误报、可疑但无实锤。 这一周一次的巡检,比任何评估指标都更能反映模型在真实环境的表现。
误报分析要追溯错误的来源。比如有一次我发现不少"【招商银行】您的验证码是123456,打死也不要告诉别人"这样的短信被判定为诈骗。原因是训练集里很多骗子也会模仿银行官方提醒,模型学到"银行+验证码"这个模式就紧张。解决办法是,把官方号码段和官方短信模板做一个白名单特征,同时调整样本权重,让这类正常短信在训练里更强势。这类问题只有通过持续的误报日志才能发现,只盯着总体指标根本看不出来。
6. 上线之后的长期对抗:模型更新、反馈闭环与扩展方向
最后这块,我想聊系统上线后怎么让它活下去。这是所有AI安全产品真正难的地方。诈骗短信识别是一场无休止的军备竞赛,骗子会发现你的拦截策略,然后不断调整话术。模型不会自己进化,它依赖一套完整的运营机制。
6.1 冷启动阶段:没有历史数据也能出手
如果你刚开始做,没有用户投诉通道,也没有历史告警日志,那第一步不是训练模型,而是先搭一个**"可疑提醒"级别的轻量规则引擎**。把域名风险、关键词风险、号码风险三类特征叠加起来,规则命中就弹窗提醒。这段时间持续一到两周,你把所有被规则命中但其实是正常短信的样本保存下来,作为后续训练的负样本。这个过程慢,但扎实。
不要一上来就想做一个"完美无缺"的深度学习模型,因为你根本没有数据喂它。规则粗筛阶段收集的误报和正报,才是最先能用的训练语料。
6.2 反馈闭环设计:让每一次判断都变成训练数据
模型在线上每做一次判断,最好都要留下可追溯的记录。如果最终产品形态是App里的安全助手,可以在用户点击"这不是诈骗"按钮时,把这条短信、模型概率、规则命中情况全部记下来,回传到一个样本池。
这里有条经验供参考:手工标记的样本,不要立刻进训练集,先放七天。原因是刚发生的事件容易扎堆,比如某天运营商群发一条活动短信,用户一周内标记了上千条"这不是诈骗",如果立刻进训练集,会让模型对这一类短信过拟合。放七天再抽样入库,样本的时间分布就更均衡。
6.3 模型更新节奏:小步快跑,而不是憋大招
我的建议是每两周做一次增量更新,每次只加入最近两周新增的高质量样本,并重新在固定测试集上做回归测试。所谓回归测试,就是把过去人工确认过的一批典型样本固定下来,每次新模型上线前跑一遍,保证新模型没有"忘记"老样本的特征。这是一个很便宜的测试,但能避免很多尴尬。
另外,深度学习模型每次重训时,训练超参数哪怕一丁点随机性都可能导致效果波动。我要求在候选模型通过回归测试前,不许直接上线替代线上版本。上线后还会做小流量AB对照,观察误报率变化,稳定两三天再全量放开。
6.4 从短信到更多场景的复制
这套方案跑通之后,你会发现同样的架构可以平移到很多类似的文本风控场景。把"诈骗短信"替换成"虚假招聘信息""钓鱼邮件""社交平台恶意评论""贷款中介推广",只要样本标签体系跟着改,规则层和模型层基本不用大动。因为这些场景的本质都是一样的:在一个噪声极高的内容流里,找出那一小撮利用信息差和技术手段做恶的文本。
我现在已经把同一套代码扩展到了邮件标题和评论区广告的识别上,只是在预处理阶段多加了几个特征提取器。如果你也在做类似的内容安全项目,不妨沿着这个思路走:先用规则圈定可疑候选,再用轻量词向量模型拦截大多数,然后用语义模型精判小部分,最后靠大模型做场外巡视和自动标注。这个"由粗到细、层层递进"的路线,比单纯追求一种模型包打天下要实用得多。
回到开头我家里人收到的那条医保诈骗短信,如果当时手机里有这样一套识别逻辑,短信大概率会被标记成"高危链接提醒"而不是让用户自己判断。做这个项目最大的收获,不只是学会了一堆模型调用和调参技巧,而是真正理解了AI落地时,每一条数据的标签、每一次样本回流、每一个阈值的设定,背后都在和一个真实世界的对手博弈。这场博弈不会停,但你的识别系统每更新一个版本,骗子的成本就会高一分。这个方向值得长期做下去。
