尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

诈骗电话识别全链路实战:从特征工程到LightGBM调参与踩坑复盘

诈骗电话识别全链路实战:从特征工程到LightGBM调参与踩坑复盘 简介在电信反欺诈场景中机器学习模型需要从海量通话记录中自动识别异常行为模式这本质上是典型的二分类问题核心挑战在于数据极度不平衡、行为特征动态演化以及原始数据预处理复杂。基于运营商脱敏通话记录构建号码行为画像通过统计特征、时间交互特征与网络拓扑特征刻画可疑模式并采用AUC作为评价指标以适配稀疏正样本场景。LightGBM凭借直方图算法在千万级表格数据上兼顾训练效率与内存占用配合scale_pos_weight权重调整和分阶段调参策略可有效提升模型对诈骗号码的排序能力。实际工程中zip压缩包校验、分块读取与增量聚合、按号码分组划分训练集避免数据泄漏都是决定线上成绩的关键细节。本文从数据解压到模型融合完整复盘诈骗电话识别竞赛项目并探讨从离线模型到实时风控系统的落地路径为处理同类不平衡分类任务提供可复用的工程参考。 2020数字四川创新大赛的诈骗电话识别赛题是我当年第一次完整走完“数据清洗—特征工程—模型训练—提交评测”全流程的比赛项目。最终成绩Rank 29779在总参赛队伍里属于中后段但这个项目对我的意义远超排名本身——它把我从“只会调sklearn接口”推到了“需要自己设计特征、处理不平衡数据、压缩内存、解析各种压缩包异常”的真实战场。尤其是在数据处理阶段你拿到的原始数据往往不是一个干净的CSV而是一堆需要自己解压、校验、分卷合并的zip包光是把数据完整读进内存就能劝退一批人。这篇博文就把这个项目的完整链路拆开讲赛题在考什么、数据长什么样、特征怎么设计、模型怎么选、调参踩了哪些坑、为什么排名不高以及如果重做一遍我会怎么改。也会把zip文件操作、数据校验、内存管理这些和数据竞赛强相关的实操细节一并说透。1. 赛题拆解诈骗电话识别到底在考什么1.1 赛题任务与业务背景“诈骗电话识别系统”这个赛题表面看是一个二分类问题给定电话呼入呼出的记录判断某个号码是否为诈骗号码。但放到2020年的业务背景下这个任务有非常具体的现实意义。电信诈骗号码往往具备“短时高频外呼”“生命周期极短”“号码归属地与呼叫目的地不匹配”“呼叫时段集中在特定时间窗”等行为特征传统基于黑名单的拦截方式滞后严重因为诈骗号码经常换号黑名单永远追不上新号。机器学习要做的事情就是从海量通话行为数据里自动提取出这些“可疑模式”在新号码还没有被举报之前就完成预警。数字四川创新大赛这个赛题使用的数据是经过严格脱敏处理的运营商通话记录字段包括主叫号码ID、被叫号码ID、通话开始时间、通话时长、通话类型语音/短信、呼叫结果接通/未接通/关机/空号等、IMEI设备指纹、基站区域编码等。数据总量在千万级到亿级文件压缩后仍有几GB规模。这也是为什么赛题官方会提供一个zip压缩包供下载——原始数据太大必须压缩传输。1.2 评价指标与类不平衡陷阱赛题采用的评价指标是AUCArea Under the ROC Curve。选AUC而不是准确率是因为诈骗电话识别天然面临严重的正负样本不平衡正常情况下诈骗电话占所有通话的比例极低可能千分之一都不到。如果你用准确率做指标模型只需要把所有号码都预测为“正常”准确率就能达到99.9%以上但这个模型没有任何用处。AUC对不平衡数据相对友好它衡量的是模型把正样本排在负样本前面的能力不依赖具体的分类阈值。这个特性直接影响了后续所有的建模决策我们优化的是排序能力不是分类正确率最终输出的是概率分不是硬分类标签。这也意味着模型调参的重点是让诈骗电话的预测分数尽可能高同时压低正常电话的分数而不是简单地调节分类阈值。1.3 赛题数据中暗藏的时间陷阱还有一个新手容易忽略的关键点训练集与测试集存在严格的时间切分。训练集覆盖的是前几个月的数据测试集覆盖的是之后一段时间的数据。这种设定模拟了真实业务场景——你只能使用历史数据训练模型去预测未来可能出现的诈骗行为。这个时间切分带来了两个问题。第一诈骗号码的行为模式会随时间演化训练期学到的模式在测试期可能已经失效。第二特征工程中如果使用了“未来信息”比如用整个训练集统计某个号码的全局呼出量再用于测试集号码会造成数据泄漏线下验证分数虚高线上分数一落千丈。我当时的做法是把训练集按时间排序后切出前80%做训练、后20%做验证确保验证集的时间分布与线上测试集一致这个细节后面会细说。2. 特征工程完整链路从原始记录到可用特征2.1 原始数据长得什么样赛题原始数据是逐条通话记录每行一次通话事件。为了说明方便我按脱敏后的字段形态整理如下字段名含义示例caller_id主叫号码脱敏IDu_1000234callee_id被叫号码脱敏IDu_833102start_time通话开始时间戳1591000000duration通话时长秒45call_type呼叫类型voice / smscall_result呼叫结果connected / not_connected / busyimei设备指纹IDd_772341cell_id基站区域编码c_90123一条记录描述的是“谁在什么时间给谁打了电话、打了多久、结果如何”单条记录本身信息量很小特征工程的关键在于把这些单点记录聚合到号码维度还原每一个号码的“行为画像”。2.2 号码行为特征的三层构建思路我把特征设计分成了三个层次。第一层是基础统计特征第二层是时间交互特征第三层是网络拓扑特征。基础统计特征以号码为group by单元统计号码在时间窗口内的呼叫次数、被叫次数、平均通话时长、通话时长方差、呼叫成功率connected占比、不同类型呼叫的占比、不同结果的占比等。这些特征刻画的是号码的“整体活跃度”和“行为模式”。诈骗号码往往表现为呼叫量极大、单次通话时长很短、接通率很低因为骗子的逻辑是广撒网不在乎单次通话是否成功。时间交互特征诈骗行为有非常明显的时段规律。我把一天按小时切分统计号码在各小时的呼出占比计算信息熵以及“夜间活跃度”凌晨0点到5点的呼叫占比。正常用户夜间通话极少而诈骗呼叫往往选择在用户可能接听的时间段批量外呼。时间熵越大说明号码全天均匀活跃这更像是机器自动外呼或者群呼设备的行为。网络拓扑特征这是后期提升空间最大的一个方向。如果号码A与已知的诈骗号码B存在频繁联系A本身是诈骗号码的概率也会上升。更进一步可以构建“号码—号码”二部图计算节点的度数、入度、出度、聚类系数、PageRank等图特征。但图特征在2020年这个赛题里受限于数据规模直接构建全量图对内存和算力要求很高我当时的做法是只对“呼叫次数超过N次的高频号码”构建子图计算度数特征低频号码的图特征直接填0。这种折中方案在当时比较可行代价是低频号码的图特征信息量很弱而诈骗号码偏偏以“低频新号”居多这是后话。2.3 特征筛选与验证方法特征构建完之后我总共得到约80个原始特征。数量不算多但特征间存在较强的相关性尤其是各种计数类特征之间。我用了两种方法做筛选一是IV值Information Value筛选。对每个特征做分箱计算IV值保留IV大于0.02的特征。IV值能够直观反映特征对正负样本的区分能力。实际操作中我注意到“主叫呼出总次数”这个特征的IV值特别高单独一个特征就能达到0.3以上这符合直觉——诈骗号码的呼出量远高于正常用户。但IV值也有局限它只衡量单变量区分能力无法识别“单变量区分弱、组合起来区分强”的特征所以在IV筛选之后还要配合模型特征重要性做二次确认。二是LightGBM自带的特征重要性排序。跑一轮基线模型输出feature_importance剔除重要性极低的特征再比较剔除前后的验证集AUC。如果剔除后AUC不降反升说明该特征实际上是噪声如果AUC下降明显说明特征有效。这个“置换验证”的方法需要谨慎使用因为树模型的特征重要性会受到特征共线性和使用频率的影响不能完全作为唯一依据。3. 模型选型与调参为什么用LightGBM而不是神经网络3.1 表格数据场景下树模型依然是首选诈骗电话识别这个赛题数据类型是典型的表格数据结构化特征行数千万级、列数几十到上百。在这种场景下梯度提升树GBDT家族尤其是LightGBM和XGBoost依然是最稳的选择。神经网络虽然在图像、文本、语音领域统治一切但在中小规模的表格数据上它的优势并不明显反而需要更多的特征工程和调参成本。我的选择是LightGBM理由有三个第一训练速度快。LightGBM使用基于直方图的决策树学习算法将连续特征离散化为固定数量的桶训练速度比XGBoost的快树算法快数倍对于千万级数据非常关键。第二内存占用低。直方图算法天然减少内存消耗相同数据量下LightGBM的内存占用通常只有XGBoost的一半左右这一点在后面“内存管理”部分会有实际体现。第三对类别特征友好。原始数据里的区域编码、设备指纹等都是类别特征LightGBM原生支持类别特征处理不需要手动做One-Hot编码省去了一大批特征预处理工作。3.2 类不平衡处理先用权重再调阈值类不平衡处理是诈骗电话识别里绕不开的一环。我在这个项目里尝试了三种方案并记录了对AUC的实际影响。方案一直接训练组织样本类别权重调整。这是不做任何处理让模型自己面对不平衡比。实测下来AUC也能到0.7左右因为LightGBM在训练时会自动调整叶子节点的样本权重对正样本的识别能力并不完全丧失。但这会带来一个问题模型输出的概率分普遍偏低诈骗号码的分数往往在0.01级别不方便后续设定业务阈值。方案二使用scale_pos_weight调整正样本权重。这是LightGBM官方推荐的做法公式为scale_pos_weight 负样本数 / 正样本数。在我的数据里这个比例大约是5000:1所以我设置了scale_pos_weight5000。这个操作的核心逻辑是在不改变训练数据分布的前提下通过损失函数中的样本权重放大模型对正样本分类错误的惩罚。实测AUC提升到0.78左右提升明显。方案三欠采样集成类似EasyEnsemble。把负样本随机抽样成与正样本数量相近的子集分别训练多个模型最后对预测概率取均值。这个方案在小数据集上效果好但在我的场景下有明显的短板——正样本本来就只有几千条欠采样后每个子模型的训练数据太少模型方差增大最后集成的AUC只稳定在0.75左右反而不如方案二。最终我采用了方案二的权重调整这也是绝大多数表格数据竞赛的实践经验。阈值调整AUC优化的是排序不关心具体阈值但实际上线你需要一个“分数超过多少就判为诈骗电话”的阈值。我采用的方法是在验证集上遍历0.01到0.9的分数区间找到“召回率达到80%且精确率达到30%”的最大阈值点。这个阈值的含义是宁可误报一批也要保证80%的诈骗电话能被拦截后续再由人工客服二次确认这就是业务决策和模型训练的衔接。3.3 LightGBM参数调优早停与顺序化搜索LightGBM调参有一个常见误区上来就开GridSearch穷举所有参数组合。数据量小的时候无所谓数据量千万级的时候一次GridSearch可能要跑几十个小时。我的做法是分三轮顺序调参第一轮固定树结构相关参数num_leaves31, max_depth-1只调学习率learning_rate和树的数量n_estimators。这里有一个重要的技巧先设置较大的learning_rate如0.1配合早停确定大致的最优树数量再根据树数量反推learning_rate。比如0.1时最优树数量是800那么换用0.05时最优树数量通常在1600左右二者成反比设置n_estimators2000、early_stopping_rounds100让它自己早停即可。第二轮调num_leaves和min_data_in_leaf。这两个参数组合直接影响模型复杂度。num_leaves越大模型越复杂越容易过拟合需要配合增大min_data_in_leaf来抑制。经验公式是min_data_in_leaf大约等于num_leaves * 10。我的最终选择是num_leaves63, min_data_in_leaf500相比默认的num_leaves31提升了一小截AUC。第三轮调feature_fraction和bagging_fraction随机特征采样和随机行采样。这两个参数本质上是给模型加随机性、降低方差。我最终设置为feature_fraction0.8, bagging_fraction0.8, bagging_freq1AUC稳定提升0.005左右。这不算大但在大规模数据上万分之几的AUC提升都值得争取。4. 实战中的坑从zip解压到内存爆炸这一章我单独拿出来写是因为实际参赛过程中真正消耗时间的不是模型训练而是数据处理环节。如果你打算复现这个项目下面这些坑大概率会再踩一遍。4.1 数据集zip解压的诡异问题官方给的数据集是一个zip压缩包里面有训练集、测试集、样例提交文件等多个文件。我下载下来之后第一次用系统的图形解压工具直接解压中途报错“文件已损坏”退出去重下了一遍还是同样的报错。后来才发现根本不是文件损坏而是我用的解压软件对超过4GB的zip包兼容性有问题。正确做法是用命令行工具验证zip包的完整性。在Linux或macOS上# 直接测试zip包是否完整 unzip -t train_data.zip如果输出类似“No errors detected in compressed data of train_data.zip”才说明文件完整。如果报错“CRC failure”“file is not a zip file”或者“invalid zip archive: could not find eocd”就要注意了。eocd是zip格式的End of Central Directory记录位于文件末尾如果zip包在下载过程中被截断或者某些下载工具不支持断点续传导致文件不完整就会出现“could not find eocd”错误。我当时的解决方法是换用wget或curl重新下载并且加上断点续传参数# 下载中断后可以继续下载而不重新开始 wget -c https://example.com/train_data.zip # 或者用curl支持断点续传 curl -C - -O https://example.com/train_data.zip下载完成后再次用unzip -t验证完整性。这个方法解决了我后续好几次数据集分发的问题。另外还有一个常见情况如果zip包是分卷压缩的后缀为.z01、.z02等Windows上的压缩软件一般能自动识别但Linux命令行下需要先合并再解压# 将分卷文件按顺序合并成一个zip cat train_data.z01 train_data.z02 train_data.zip full.zip # 再解压 unzip full.zip4.2 内存爆炸与增量特征处理当你成功解压出数据集真正的挑战才开始。训练集原始CSV文件大小约8GB如果用pandas直接pd.read_csv()读入内存在16GB内存的笔记本上会直接导致内存溢出。这是因为pandas读CSV时的内存占用通常是文件大小的3到5倍字符串类型字段更是内存杀手。我的做法是“分块读取 增量聚合”不一次性把全量数据读入内存而是用pd.read_csv(..., chunksize500000)分块读入每块都做一次“按号码聚合统计”把聚合结果累加到一个字典或小DataFrame里最后再对累加结果做归一化处理。因为特征工程的单位是“号码”而不是“通话记录”聚合后的数据量大幅缩小内存压力随之消失。具体代码如下import pandas as pd from collections import defaultdict # 使用字典累积每个号码的统计量 call_count defaultdict(int) connected_count defaultdict(int) total_duration defaultdict(float) chunk_iter pd.read_csv( train.csv, chunksize500000, usecols[caller_id, callee_id, duration, call_result] ) for chunk in chunk_iter: for row in chunk.itertuples(indexFalse): caller row.caller_id call_count[caller] 1 if row.call_result connected: connected_count[caller] 1 total_duration[caller] row.duration # 将聚合结果转为DataFrame feature_df pd.DataFrame({ caller_id: list(call_count.keys()), call_count: list(call_count.values()), connected_count: list(connected_count.values()), total_duration: list(total_duration.values()), })这段代码在商业数据上实测下来处理8GB的CSV文件内存峰值不到2GB耗时约15分钟。虽然用itertuples遍历每一行的方式比向量化操作慢但在读取阶段速度瓶颈在磁盘IO而非CPU完全能接受。如果你进一步优化可以直接用多进程并行处理分块再把结果合并我为了省事没有做这一步。4.3 模型结果漂移线下AUC高但线上分数低的根因很多参赛者都会遇到同一个问题线下验证AUC有0.80线上评测只有0.70甚至更低。这是整个竞赛里最打击人的事。我复盘后发现原因主要集中在三个方面。第一随机划分验证集导致的数据泄漏。如果直接用train_test_split(random_state42)随机划分同一个号码的通话记录会同时出现在训练集和验证集里模型在训练时已经“见过”这个号码的行为模式验证时自然得分虚高。正确做法是按号码分组划分同一个号码的所有记录只出现在训练集或验证集其中一侧中间用sklearn的GroupKFold或者GroupShuffleSplit实现。第二特征统计覆盖了验证期数据。我在构建号码行为特征时统计的是该号码在整个时间范围内的总量。训练期和验证期的号码行为被混在同一个统计量里验证期部分的“行为信息”被泄漏到特征中。正确做法是特征统计窗口严格限制在训练期之内验证期号码如果在训练期没有出现则其特征值全部置为默认值0或-1模拟线上测试时“新号码没有历史行为”的情况。第三没有考虑代码与环境的随机性。LightGBM虽然设置了random_state但如果你用了bagging_fraction且没有固定bagging_seed不同线程下跑出来的结果仍然会有微小差异。提交版模型为了保证可复现我会把deterministicTrue、force_col_wiseTrue、bagging_seed42和seed42全部固定并且固定n_jobs。这一步能把随机波动从0.002降到0.0005以内。5. 复盘Rank 29779失分点与改进方向5.1 排名背后的客观评估Rank 29779这个名次放在2020数字四川创新大赛里属于中后段。这个名次的直接原因有两个一是特征工程偏保守二是在模型融合上投入不足。先说前者我的特征以号码的统计特征为主虽然覆盖了基础行为模式但对“号码之间的关联关系”挖掘得太浅。当时我已意识到图特征的重要性但只做了高频号码子图的度数特征低频号码的图特征信息完全缺失。而诈骗电话恰恰以“新号”居多这些新号码没有足够的历史呼叫记录统计特征本身就稀疏图特征又因为低频而缺失等于模型手里只有一小半的牌。这是成绩受限的核心原因。5.2 如果再走一遍我会怎么做如果重做这个项目我会在以下三个方向上重点改进。方向一做全量图特征而不是子图特征。构建号码—号码的二部图用MinHash或局部敏感哈希做近似邻居计算把PageRank、聚类系数等图特征覆盖到全部号码而非只覆盖高频子图。这样低频号码也有对应的图信号。在当时的算力条件下全量图特征需要分布式计算现在直接在单机上用GraphSAGE或者更轻量的Node2Vec也能跑技术瓶颈已大幅降低。方向二引入多模型融合。我当时只训练了一个LightGBM没有尝试XGBoost、CatBoost、逻辑回归、以及基于深度学习的表格模型如TabNet、FT-Transformer。多模型融合的本质是“不同模型从不同视角看同一份数据然后互相纠正错误”。最简单有效的方法是做加权平均融合或者Stacking用LightGBM和XGBoost分别输出概率分在验证集上搜索权重w使final_score w * lgb_score (1 - w) * xgb_score的AUC最高。实测同类竞赛中两模型融合通常能带来0.01到0.02的AUC提升。方向三后处理规则与模型结合。纯机器学习模型很难覆盖所有场景可以在模型基础上叠加规则比如模型输出的诈骗概率超过0.9的号码直接判定为诈骗模型概率在0.5到0.9之间但号码在48小时内外呼次数超过200次也判定为诈骗。这种“模型为主、规则兜底”的做法在业务落地时比单纯依赖模型更可靠在竞赛中也能小幅提升线上分数。5.3 关于竞赛代码管理与模型文件的一些建议最后顺手分享一个竞赛代码管理的建议这也是Rank数字背后容易被忽略的部分。项目根目录下放的模型文件比如model.h5、lgb_model.txt通常会被打包成zip一并提交或存档这时候zip包里往往既有代码又有数据又有模型解压和再度训练都要注意路径一致性。我的习惯是使用稳定的目录结构避免在不同机器间搬运时出现“找不到文件”的问题telecom_fraud_detection/ ├── data/ │ ├── raw/ # 原始数据训练集、测试集 │ └── processed/ # 特征工程后的数据 ├── features/ │ ├── build_features.py # 特征构建脚本 │ └── feature_config.py # 特征配置特征列表、窗口大小 ├── models/ │ ├── train.py # 训练脚本 │ └── lgb_model.txt # 训练好的模型文件 ├── submission/ │ └── submit.csv # 提交文件 └── config.py # 全局配置在config.py里集中定义所有路径而不是在每个脚本里硬编码路径。这样打包zip发给别人时只需要保持目录结构一致对方就能直接运行。对于最终的模型文件我还会额外保存一份特征列表feature_columns.pkl确保训练和预测时特征顺序一致——这个顺序问题如果出问题LightGBM虽然还能跑但线上预测的结果就完全不对了而且没有任何报错提示特别坑人。6. 从竞赛到实战诈骗电话识别系统的落地视角如果你以为竞赛做完就结束了那这个项目的价值就只发挥了一半。竞赛里的数据和业务真实的诈骗电话识别场景还是有不少距离但核心方法是可以平移的。竞赛数据是脱敏后的离线全量记录真实业务里你面对的是流式数据电话打进来的时候你需要用毫秒级延迟判断这个号码是不是诈骗号码。这就意味着竞赛里的“训练好模型批量预测”的节奏要改成“实时特征计算 实时预测”。电话号码行为特征需要从离线批处理改成在线滑动窗口比如统计“过去1小时该号码呼出次数”“过去24小时该号码被标记次数”而这些统计本身就需要实时更新。从技术选型角度看竞赛里训练好的一棵LightGBM树模型在真实系统中可以被导出为轻量级的规则集合比如把树的路径展开成if-then规则部署在网关设备上毫秒级完成判决。这也是树模型相比深度神经网络的一个落地优势。如果你的系统用的是Java技术栈可以考虑用JPMML把LightGBM模型转成PMML格式或直接用Treelite把模型编译成C库在毫秒级完成推理。这些都是在项目排名之外我真正沉淀下来的工程经验。另一个实战差异是业务指标的变化竞赛用AUC真实业务更关心的是“精确率达到某个阈值时的召回率”。因为每一次人工外呼确认都有成本精确率太低客服团队会被海量误报淹没而召回率太低漏掉的诈骗电话又会造成真实损失。实际落地时通常要同时设置两个阈值一个高阈值直接拦截高精确率确保误报率极低一个低阈值进入人工复核队列保证召回率两个阈值之间的空间就是业务可以灵活调节的“灰区”。最后说一个我在竞赛期间悟到的“反直觉”经验不管你的模型排名多靠后只要你从项目里提炼出了一个可复现的流程——数据解压校验、特征聚合、模型训练、结果复盘——这个项目的价值就已经超过排名本身了。尤其当你遇到“压缩包打不开”“内存不够用”“线下线上分数不一致”这些问题并且靠自己的力量把它解决掉时这些经验会在你未来任何一个数据处理任务里反复用到。这也是我为什么写这篇博文的原因拿不拿名次是暂时的能不能把你踩过的坑变成别人可以绕开的路径才是这个项目长久的价值。本文还有配套的精品资源点击获取
返回列表