简介:这是一套基于大数据与机器学习技术的反电信诈骗管理系统项目,开发语言以Python为主,面向课程设计、毕业设计、安全竞赛及实际业务研究者,提供从通信数据采集、风险识别到可视化管理的完整工程方案。压缩包大小约46.24MB,文件清单与类型明细暂未提供,但项目结构清晰,涵盖核心算法、数据处理、前端交互和部署配置模块。系统功能围绕实时监控通话与短信、生成疑似诈骗报告、用户标记反馈、诈骗概率预测、防范教育以及Web管理后台展开,技术方面采用关系与非关系数据库存储日志,使用机器学习框架构建检测模型,借助自然语言处理工具分析短信语义,前端页面基于主流框架开发。目前已有492人学习浏览,适合具备一定编程基础、希望掌握大数据分析与机器学习综合实战的读者。通过项目可完整经历数据清洗、特征工程、诈骗规则库构建、模型训练与评估、接口联调等关键环节,并可迁移至电信运营商、安全机构和金融机构的诈骗预警场景,有效提升工程开发与部署能力。
1. 反诈系统到底是什么:python项目基于大数据反电信诈骗管理系统的落地全景
深夜值班,一条“客服退款”外呼被系统拦下,2 秒内返回号码画像和风险分 86,自动转人工复核。这就是 Python 基于大数据构建的反电信诈骗管理系统的典型现场:把每天上百万条话单、举报记录和黑灰产样本,变成能即时给出拦截决策的数据资产。它解决的不只是“封一个号”,而是把号码的行为、关系、时间规律压缩成可计算的特征,再用规则和模型双重判定。适合大数据、计算机方向的毕业设计选型,也适合中小团队搭内部风控。这套系统真正烦人的地方从来不是算法,而是数据清洗和特征对齐——后面讲到的每个坑,都是我实际跑出来的。
2. 系统拆解与数据流:先想清楚五层架构再写代码
拿到这类反诈系统,第一件事不是急着跑起来,而是先确认模块划分是否清晰。电信诈骗治理的业务链路很长:从号码进入视野,到产出拦截决策,中间隔着采集、清洗、特征、评分、处置五道工序。少了任何一层,系统都只能算“号码黑名单”,撑不起“基于大数据”这几个字。
2.1 先拆模块:反诈系统不离谱的五个层次
我习惯把系统拆成五个层次,每一层只做一件事,层与层之间用数据文件或接口解耦。这样后期换组件不用推倒重来。
- 数据接入层:接收举报工单、同步话单日志、爬取公开黑灰产样本。
- 存储层:MySQL 存举报工单和黑白名单,ClickHouse 或 Parquet 文件存话单明细,Redis 存实时计数。
- 计算层:Spark 做批量特征,Pandas 做小样本探索,Redis 做窗口计数。
- 算法层:规则引擎和评分模型并存,规则兜底保证基本盘,模型负责发现规则看不见的新号。
- 应用层:Flask 提供风险查询接口,ECharts 渲染大屏,工单推送触发人工处置。
对应的目录结构长这样:
anti_fraud/ ├── collector/ # 数据接入:举报API、话单同步、样本爬虫 │ ├── report_api.py │ ├── cdr_sync.py │ └── crawler.py ├── cleaning/ # 清洗脚本:号码归一化、去重、类型识别 │ └── clean_number.py ├── feature/ # 特征工程:按时间窗口生成号码特征宽表 │ └── build_features.py ├── model/ # 规则引擎与评分模型 │ ├── rules.py │ └── score_model.py ├── server/ # Flask 服务:风险查询、预警推送 │ └── app.py └── dashboard/ # 大屏前端:ECharts 可视化 └── index.html这个目录设计的核心是“隔离”。爬虫挂了不影响线上查询,模型重训不需要动服务代码。我见过把清洗逻辑直接写在 Flask 路由里的项目,数据量一上来,接口和清洗互相拖累,最后两头都崩。模块拆开,每一层都能独立测试,这是反诈系统能长期维护的前提。
如果是课程设计级别的项目,计算层可以砍掉 Spark,用 Pandas 代替;预警推送可以简化成写一张 alert 表。但模块边界不要砍,因为后面迭代十有八九是在模块内部换实现,而不是重构整体。
数据流的走向可以这样理解:号码从任意数据源进入接入层,经过清洗成为标准化的“号码实体”,特征层把号码过去 7 天、24 小时、1 小时三个窗口的行为压缩成几十个字段,算法层给出风险分,应用层把高分号码变成工单推给处置人员。每一层的输出最好落成一份带日期版本号的文件或表,出了问题能回溯定位。
2.2 技术选型:中小数据量和大数据量的分界线在哪
很多初学者一上来就上 Spark,结果本地跑得慢、集群起不来。反诈系统的数据量差异极大:一个社区级项目每天几千条举报,一个市级联动每天几千万条话单。选型的分界线应该画在“单机内存能否放下一天的数据”。
我一般这样选:
| 数据规模 | 推荐组合 | 选型理由 |
|---|---|---|
| 日增万级 | MySQL + Pandas + Redis | 单机内存足够,Pandas 清洗和特征效率最高,Redis 做频控 |
| 日增百万级 | ClickHouse + Dask + Redis | 明细进列存,Dask 并行特征,不引入 Hadoop 全家桶 |
| 日增千万级 | HDFS + Spark + ClickHouse | Spark 处理宽表关联,ClickHouse 喂实时查询 |
为什么强调 Python 而不是 Java?因为这个项目里爬虫、清洗、模型训练、后端接口要来回切换,Python 的 DataFrame 生态和 sklearn 让特征验证成本极低。用 Java 写清洗逻辑,改一个字段要重新编译,迭代速度差太远。Python 的短板在 CPU 密集型计算,但这刚好是 Spark 擅长接的活,两者分工合理。
数据规模到了千万级,就要认真对待集群部署策略。常见做法是 Spark Standalone 起步,三台 16C32G 的机器就能扛住市级话单量。spark-submit 的关键参数不是越大越好,我常用的模板是:
spark-submit \ --master spark://node01:7077 \ --executor-memory 8g \ --executor-cores 4 \ --num-executors 6 \ --conf spark.sql.shuffle.partitions=48 \ feature/build_features.py参数背后的逻辑是:executor-memory 定在 8G 而不是 16G,是为了留足堆外内存和操作系统余量;shuffle.partitions 设为 CPU 核数的 2 到 3 倍,6 个 executor 乘 4 核再乘 2,得到 48。这个值太小会拖慢 join,太大会产生大量小文件。刚开始跑集群,按这个模板起步,用 5000 万条样本压一次,观察 Spark UI 上每个 stage 的耗时,再逐步调整。
存储层的另一个隐蔽决策是“明细和宽表分开”。明细话单按天分区存 Parquet,特征宽表单独生成,不要在查询接口里现场 join 明细。原因很简单:大屏要的是毫秒级响应,而 join 一天几千万条明细的耗时会直接打穿 Web 层。这套选型也决定了后续预警管线的写法:离线特征用 Spark 批量生成,实时累计用 Redis 的 INCR 和 EXPIRE,两者在接口层合并。模型评分用的特征不是现场算的,而是预先算好放在特征表里,接口只做查表,这样单次评分延迟能压在 50 毫秒以内。
3. 数据从哪来:话单、举报工单与黑灰产样本的接入和清洗
模型再漂亮,喂进去的是没清洗过的数据,输出的就是垃圾预测。反诈系统的数据源通常有三个:运营商话单、用户举报工单、公开的黑灰产样本库。三种数据格式各异,接入方式也不同,但最后都要落到同一个清洗管道里。
3.1 三个数据源的接入方式
话单通常是 CSV 或 Parquet 按天同步。用 Pandas 读入时不要直接全量 load,先只读需要的列:
import pandas as pd df = pd.read_csv( "cdr/2025-06-01.csv", usecols=["caller", "callee", "start_time", "duration", "call_type"], dtype={"caller": "string", "callee": "string"}, # 号码必须按字符串读,不能用 int parse_dates=["start_time"], ) print(df.memory_usage(deep=True).sum() / 1024**2, "MB")usecols 只加载 5 列而不是整份宽表,能省下大量内存;号码按 string 读是因为手机号超过 int 范围,且用 int 会丢前导零。日均千万条话单时,建议改用 Spark 的 read.csv 加 schema 裁剪,Pandas 单机容易撑爆内存。另一个细节是话单文件经常带 BOM 头和乱码列名,读进来后第一件事是打印 df.columns 确认字段名和文档一致。
举报工单一般走 HTTP 接口,requests 拉取即可:
import requests resp = requests.get( "http://your-api/reports", params={"start": "2025-06-01", "end": "2025-06-02", "page_size": 1000}, headers={"Authorization": "Bearer <token>"}, timeout=10, ) data = resp.json()["items"]timeout 必须设,否则某个接口卡住会让整个采集任务挂死。分页拉取时还要注意服务端的 page_size 上限,有的接口超过 500 就报错,宁可多循环几次也不要一次拉满。
黑灰产样本用爬虫抓取时有一条红线:只抓公开免登录页面,遵守 robots 规则,不采集任何能直接定位到个人的字段。常见做法是抓那些专门收录疑似骚扰号码的公开页面,用 BeautifulSoup 解析表格。反诈系统采集的数据最终要用于治理,合规性和准确度同样重要,尤其不能把正常企业号码误伤成黑样本。爬虫代码里加个简单的节流:
import time for page in range(1, 11): resp = requests.get(url, params={"page": page}, timeout=10) parse_sample(resp.text) time.sleep(1.5) # 别把目标站打挂了1.5 秒的间隔是经验值,太短容易被封,太长采集速度跟不上。黑灰产站点的页面结构经常改,解析逻辑要单独放一个函数,方便页面改版后只改一处。
3.2 号码清洗:格式化、去重、识别虚拟号段
号码清洗是整个项目里最容易翻车的一步,也是数据质量提升收益最大的一步。不加清洗的话,同一个 +8613800138000 和 13800138000 会被当成两个实体,特征全被稀释。我一般会写一个独立函数,全项目统一调用:
import re def normalize_phone(raw: str) -> str: """统一手机和固话格式,返回干净的国内号码""" if not isinstance(raw, str): return "" s = re.sub(r"[^\d]", "", raw) # 去掉 +86、空格、横杠 if s.startswith("86") and len(s) == 13: s = s[2:] # 去掉国际区号前缀 if s.startswith("0") and len(s) == 11: # 固话前导0保留 return s if len(s) == 11 and s.startswith("1"): return s return s关键在两步:第一步把所有非数字字符清掉,否则 +86 和 0086 会生成不同结果;第二步按长度和首位数判断合法性,手机号是 1 开头 11 位,固话保留前导 0。清洗后要做去重,同一号码来自话单和举报工单时,以举报记录为主键合并黑历史。这里容易出问题的还有英文括号和全角空格,正则里 \D 能一并处理掉。
虚拟号段识别也要在这步完成。170、171、162、165、167 等开头的号码很多被用于过渡性诈骗,识别出来单独打标签。但注意不能直接判定虚拟号段等于诈骗,只作为特征输入模型,否则正常使用虚拟号段的快递员会被误伤。
3.3 特征工程:把号码变成一组风险数字
脏数据清洗干净后,进入特征工程。反诈特征按“号码本体、时间行为、关系网络”三个维度组织。我常用的第一批特征是:
- 近 7 天外呼次数、被叫去重数:判断是否在“撒网”。
- 近 24 小时呼叫频次、夜间呼叫占比:诈骗电话喜欢集中时段突袭。
- 近 7 天被举报次数:最直接的强信号。
- 与被确认黑号码在话单中共现的次数:团伙联动信号。
用 Spark 计算这批特征比 Pandas 稳得多,尤其是 groupBy 在亿级数据上的表现。下面是一个按号码和时间窗口聚合的核心逻辑:
from pyspark.sql import functions as F cdr = spark.read.parquet("cdr/2025-06-01") feat = ( cdr.groupBy("caller") .agg( F.count("*").alias("call_count_7d"), F.countDistinct("callee").alias("unique_callee_7d"), F.sum(F.when(F.hour("start_time").between(0, 5), 1).otherwise(0)).alias("night_calls_7d"), F.sum(F.when(F.col("callee").isin(black_list), 1).otherwise(0)).alias("black_contact_cnt"), ) ) feat.write.parquet("feature/7d/2025-06-01.parquet")每个字段的含义要和后面模型训练时的字段名严格对齐。最容易犯的错是用 call_count 这种含糊命名,训练时和预测时口径不一致,模型上线直接失灵。聚合窗口也要在代码里写清楚:算 7 天就用 7 天的分区,不要在一条逻辑里混进 30 天数据再去截断。isIn 一个大的黑名单 list 时要注意序列化开销,黑名单超过十万条建议先 broadcast 再参与计算。
提示:特征计算必须保证只使用预测时刻之前的数据。用当天整天的数据训练出来的模型看起来厉害,上线后会在每天凌晨之前集体失灵。
4. 反诈核心:规则引擎和评分模型怎么分工
反诈系统里最容易迷失的地方是“迷信模型”。真实的线上环境里,规则引擎和评分模型各管一段:规则引擎用确定性逻辑兜底,保证最典型的诈骗能立刻拦;模型负责在规则覆盖不到的新号上给出概率判断。两条腿缺一条都会翻车。
4.1 规则引擎:先保证能拦,再谈智能
规则引擎面向的是“一看就是诈骗”的场景,特征明确、判定快、可解释。常用的规则如下:
| 规则 | 触发条件 | 处置动作 |
|---|---|---|
| 黑名单直拦 | 号码命中已确认黑名单 | 直接拦截 |
| 高频外呼 | 1 小时外呼次数 > 50 | 转人工复核 |
| 批量举报 | 7 天被举报次数 > 10 | 冻结外呼权限 |
| 白名单放行 | 命中企业报备白名单 | 跳过后续判定 |
规则的实时实现依赖 Redis 的计数原子性。一个简单的 1 小时频控逻辑:
import redis, time r = redis.Redis(host="localhost", port=6379, db=0) key = f"call_cnt:{caller}:{int(time.time()) // 3600}" pipe = r.pipeline() pipe.incr(key) pipe.expire(key, 7200) # 两小时过期,覆盖上一小时窗口 cnt = pipe.execute()[0] if cnt > 50: alert(f"高频外呼: {caller} 1小时 {cnt} 次")用 pipeline 而不是单条 INCR,是为了保证计数和过期设置原子完成,否则 key 可能变成永不失效的死数据。过期时间设 7200 秒而不是 3600 秒,是因为窗口滚动时前一个小时的计数还会被查询,过期太早会导致频控漏判。键名里带上时间分片,自然形成滚动窗口。
规则引擎的好处是每条决策都能说清依据,处置人员拿到预警可以直接办案。但副作用是黑产换号太勤,纯规则会被绕过,所以需要模型补位。规则阈值也不要拍脑袋定“50”,先拉一周话单统计正常号码的外呼分布,取 P99 作为初始值再去线上调整。
4.2 评分模型:用贝叶斯和 LightGBM 给号码打分
评分模型的目标是把“号码画像”映射到一个 0 到 100 的风险分。在样本组织上,可以参考基于贝叶斯算法建模大样本犯罪数据的思路——贝叶斯先做基线,验证特征有效性,再用梯度提升树做主力模型。特征宽表 train.csv 一般长这样:一行一个号码,包含上一章生成的特征列,外加一个标签列 label,1 表示确认诈骗,0 表示正常。
import pandas as pd from sklearn.model_selection import train_test_split from sklearn.naive_bayes import GaussianNB from lightgbm import LGBMClassifier from sklearn.metrics import roc_auc_score, recall_score df = pd.read_csv("feature/train.csv") X = df.drop(columns=["caller", "label"]) y = df["label"] # 贝叶斯基线:验证特征有没有区分度 X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) nb = GaussianNB() nb.fit(X_train, y_train) print("NB AUC:", roc_auc_score(y_val, nb.predict_proba(X_val)[:, 1])) # 主力模型:LightGBM,处理类不平衡 lgbm = LGBMClassifier( n_estimators=300, max_depth=6, learning_rate=0.05, class_weight="balanced", random_state=42, ) lgbm.fit(X_train, y_train, eval_set=[(X_val, y_val)], eval_metric="auc") print("LGBM AUC:", roc_auc_score(y_val, lgbm.predict_proba(X_val)[:, 1])) print( "Top1% 召回:", recall_score(y_val, lgbm.predict_proba(X_val)[:, 1] > 0.95, pos_label=1), )贝叶斯在这里不是最终方案,而是黑匣子对照——如果贝叶斯都拉不出像样的 AUC,说明特征工程出了问题,别急着调模型。LightGBM 的三个参数值得说:class_weight 设 balanced,解决诈骗正样本只占 2% 到 5% 的问题;learning_rate 调低到 0.05,配合 300 棵树防止过拟合;eval_metric 用 auc 而不是 logloss,因为反诈场景更关心排序能力。max_depth 限制在 6,防止树太深学习到噪声。
评估时别只看 AUC。诈骗样本太少,AUC 可能虚高,真正要盯的是“风险分排前 1% 的号码里,到底抓中了多少诈骗号码”。这也是 recall_score 计算时把阈值取在 0.95 的原因——线上不会对全量号码都做处置,只处理分数最高的那一小撮。特征列的取值分布也要检查,如果某个特征全是 0,要么是清洗丢了数据,要么是窗口没对齐。
4.3 实时评分管线:从“算完”到“来得及拦”
离线特征算完后,模型单次打分其实很便宜,瓶颈在特征准备。我的做法是:离线把特征算好存入特征表,线上请求进来只做三次查表加一次推理,不现场聚合原始话单。
def score_call(caller: str) -> dict: # 1. 查离线特征宽表 feat = feat_table.get(caller) if feat is None: feat = default_feat # 新号码给默认向量,避免模型拿到全 0 # 2. 取 Redis 实时计数,合并成增量特征 feat["call_cnt_1h"] = int(redis.get(f"call_cnt:{caller}") or 0) # 3. 模型打分 prob = lgbm.predict_proba([feat_vector(feat)])[0, 1] risk = round(float(prob) * 100, 1) # 4. 过规则引擎和阈值 if risk > 85 or hit_blacklist(caller): alert_queue.put({"caller": caller, "risk": risk, "ts": now()}) return {"caller": caller, "risk": risk}这段流程的顺序很关键:先规则后模型,确保黑名单号码不依赖模型也能拦截;预警走 alert_queue 而不是直接写库,避免模型推理拖慢接口响应。风险分阈值先设 85,上线后按误拦率回调——正常号码误拦比例高于 1% 就要抬高阈值。对新出现的号码,默认向量不能全填 0,用历史所有号码的均值填充,否则模型会对新号一律给低分。
注意:模型评分的特征向量必须在训练和预测间保持一致。训练时用了 7 日窗口特征,预测时少传一个字段,结果就是黑匣子行为,排查起来非常痛苦。
5. 避坑排查:跑通这套系统最容易翻车的五个地方
前面把主链路讲完了,下面是真正决定系统能不能长期跑的血泪经验。每一条我都踩过,现象和解决办法写在明处。
5.1 数据不平衡:模型全预测成“正常号码”
现象:训练集里诈骗标签只有 2%,LightGBM 输出一排 0.01 的风险分,所有号码都走低风险通道,模型形同虚设。
原因:分类器在极度不平衡的数据上会放弃正类,因为把所有样本预测成负类损失最小。诈骗样本太少,模型学不到“什么是诈骗”的边界。
解决:先用 class_weight="balanced" 给正类更高权重;再对负类做欠采样,把诈骗样本和正常样本比例压到 1:10 以内,这一步通常在训练脚本里单独做,不污染原始数据。最后用规则引擎兜底,让黑名单和高频外呼号码不依赖模型也走人工复核。改完这三步,Top1% 召回率能从 20% 拉到 60% 以上。验证时用 stratified K 折,别用普通切分,否则验证集可能一折里连一个正样本都没有。
5.2 号码归一化的坑:同一个号被当成三个号
现象:特征宽表里 +8613800138000、13800138000、013800138000 同时存在,模型把同一个实体当成三个,特征稀疏到没法用,模型给出的风险分忽高忽低。
原因:清洗逻辑在开发阶段和生产阶段不一致。开发时手动洗了一遍,线上接口却直接从报文字段取值,两套逻辑各走各的。
解决:把 normalize_phone 抽成独立模块,所有数据源入口统一调用,并在入库前做断言校验。经验阈值是同一来源的号码去重后数量不能超过 23%,超过即报警,说明清洗链路漏了入口。注意也不能把隐私号一刀切归并,虚拟号段要单独保留标签,它们在模型里是有效特征。
5.3 Spark 本地跑通,集群上 OOM
现象:本地用 10 万条样例测完没问题,提交到集群处理全量数据,executor 在 shuffle 阶段反复被杀,Spark UI 上一串 Container killed。
原因:默认 shuffle.partitions 只有 200,join 大表时每个分区塞进几百万条记录;加上读明细时把全部列都加载了,内存被宽表撑爆。
解决:分区数按 CPU 核数乘 2 到 3 设置,6 个 executor 各 4 核就设 48;读 Parquet 时做列裁剪,只取特征需要的字段;还爆就把 executor-memory 提到 12G,并开启堆外内存作为临时缓冲。改完后再看 Spark UI 的 Shuffle Read 指标,确认单分区数据量降到 200MB 以内。这条排查顺序不能反:先加分区,再减列,最后才加内存,否则内存加了也是白加。
5.4 特征时间穿越:用未来数据训练,上线就翻车
现象:离线验证 AUC 0.96,上线后预警准确率不到三成,处置人员天天投诉系统在乱报。
原因:训练数据里的特征包含当天结束后的统计量,比如“当天是否被举报”这种字段,模型学到了未来信息。上线后这个特征根本等不到出现,输出自然离谱。
解决:按时间切片构建训练集和验证集,训练集取 T 日之前 30 天,验证集取 T 日当天,保证验证集特征只由 T 日之前的数据生成。特征脚本里每个聚合的窗口都要写死,和训练时保持一致,禁止在模型上线前临时改口径。上线前做个简单检查:打印特征表里各列的更新时间,凡是时间戳晚于样本日期的列一律删掉。
5.5 Flask 预警接口并发一高就假死
现象:压测到每秒 200 个评分请求,接口延迟从 50ms 飙到 5 秒,然后陆续超时,大屏数据开始出现空洞。
原因:开发服务器是单进程同步处理,模型推理和预警入队都在请求线程里执行,一个慢请求堵住后面所有请求。
解决:生产环境用 gunicorn 起多 worker,再引入 Redis 队列把预警异步化:接口只负责打分和入队,工单推送由后台 worker 消费。评分服务保持无状态,才能水平扩容。改完后压测到每秒 800 请求才出现拐点。注意 gunicorn 的 worker 类型要选 gevent 或 threads,默认 sync worker 在模型推理这种阻塞场景下照样卡。模型对象要在加载时放进全局变量,每个 worker 只加载一次,否则每来一个请求就 load 一次模型,延迟会多一个数量级。
6. 部署与验证:Flask+ECharts 把风险分实时端上大屏
6.1 最小可用的实时预警接口
落地成服务最省事的是 Flask 加 joblib 加载模型,接口只做查表和打分,保持无状态:
from flask import Flask, request, jsonify import joblib app = Flask(__name__) model = joblib.load("model/lgbm.pkl") @app.post("/v1/risk") def risk(): caller = request.json.get("caller") feat = feat_table.get(caller, default_feat) prob = model.predict_proba([feat])[0, 1] risk = round(float(prob) * 100, 1) if risk > 85: alert_queue.put({"caller": caller, "risk": risk}) return jsonify({"caller": caller, "risk": risk})joblib 加载模型比 pickle 更稳,能把 LightGBM 的底层结构完整恢复。接口不落库、不留状态,预警进队列后立即返回,这样压测时能平行扩展多个实例。大屏端用 ECharts 拉这个接口,每 5 秒刷新一次风险榜,配合 WebSocket 推最新预警,画一条实时曲线,这就是完整的数据可视化大屏闭环。
6.2 回测与压测:上线前必须做的两件事
历史回测是判断系统能不能上的底线:把昨天的话单按时间顺序重放给接口打分,比较“系统预拦截名单”和“实际确认名单”的重合率。重放时用一个简单的 for 循环按 start_time 排序逐条调用 score_call,最后统计命中数。压测用 locust 模拟 100 并发持续 5 分钟,盯 P95 延迟而不是平均延迟,反诈接口的尾部延迟决定了预警能不能及时推出去。回测指标和昨天对比,AUC 或 Top1% 召回差超过 5 个点就拒绝上线,这条规矩我坚持了很久。
6.3 我的教训与习惯
最早我把所有号码都套同一套阈值,结果大量外卖、快递企业号被误拦,投诉工单堆成山。后来加了号码类型识别和企业白名单,让模型对正常高频外呼降权。另一个习惯是每次上线前固定跑一次回测,指标和昨天对比,差超过 5% 就拒绝发布。这套流程走半年,预警准确率才爬到 75% 上下。反诈系统的价值不在模型多先进,而在误拦率能不能压到业务方接受的范围。做这块别追求 AUC 的漂亮数字,多盯误拦和漏拦的真实代价,希望帮到你。
本文还有配套的精品资源,点击获取