ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

信用卡欺诈检测实战:不平衡数据处理与模型调优指南

信用卡欺诈检测实战:不平衡数据处理与模型调优指南 接手这个项目之前我以为信用卡欺诈检测就是拿一个二分类模型跑一遍输出auc分数就完事。真正把数据打开、把特征捋清楚、把模型调到能用的状态之后我才意识到这个案例为什么会被那么多人当作机器学习的必修项目——它几乎浓缩了真实业务建模会遇到的所有典型问题数据极度不平衡、评估指标陷阱、特征含义不透明、模型结果与业务决策之间的鸿沟。这篇文章就把我完整的实操过程、踩过的坑和最终落地的思路整理出来给同样在做这个方向的朋友一些参考。1. 一个看似简单的二分类难在数据极度不平衡1.1 数据集里到底有什么我用的数据集是公开的信用卡交易欺诈检测数据集Credit Card Fraud Detection在数据竞赛平台和学术论文里都非常常见。它包含2013年9月欧洲持卡人的真实交易记录总共284807笔交易其中欺诈样本只有492笔占比大约0.172%。这个比例意味着什么简单算一下就知道如果模型无脑把所有交易都预测为正常准确率也能达到99.83%。所以在这个数据集上准确率根本不是衡量模型好坏的指标它会在第一时间把你带偏。数据共有31列核心构成如下列名含义特点Time交易距第一笔交易经过的秒数数值型范围很大V1 - V28经过PCA变换后的特征数值型含义不可直接解释Amount交易金额数值型单位未知Class标签1为欺诈0为正常严重不平衡V1到V28这28个特征之所以长得这么抽象是因为原始数据涉及用户的敏感信息发布方做了脱敏处理。这是很多初学者容易纠结的地方拿着V1到V28却不知道它们是什么意思总觉得没法分析。实际上对你做特征工程来说PCA后的特征依然携带了原始数据的主要信息只是换了一种表示形式你完全可以把它当作普通数值特征来使用。1.2 准确率陷阱第一次跑完模型的真实感受我第一次跑这个项目时用的还是最初级的流程数据读进来、切分、标准化、丢给逻辑回归、计算准确率。结果出来是99.9%我第一反应是这项目也太简单了。后来随手加了一行混淆矩阵才发现真相——预测结果里几乎没有几个欺诈样本被正确识别出来。这里有一个很反直觉的点在极度不平衡的数据里模型的默认行为很可能就是猜全是负类。因为逻辑回归这类模型在优化损失函数时多数类的梯度贡献远大于少数类模型发现只要把所有样本预测为0损失就已经很低了。你看到的99.9%准确率本质上就是复读机式预测的产物。所以在处理这类问题时第一件事就是把评估指标从准确率切换成更严谨的指标我后面会详细展开。1.3 对抗性让模型天然容易过时欺诈检测还有一个普通分类任务中没有的特点对抗性。正常业务场景里数据分布的变化通常相对平缓比如用户行为随季节小幅波动但欺诈交易的实施主体是活生生的人他们会根据风控规则的漏洞不断调整作案方式。今天你的模型识别出一种模式明天对方就会改用手法绕开。这意味着你不能指望训练一次就一劳永逸模型必须持续用新数据重新训练并且监控指标要跟随时间滚动计算。我在做这个项目时就刻意把Time列保留着就是为了后面做时间维度的验证避免不经意间用到未来数据而不自知。2. 预处理细节决定模型上限时间、金额与PCA特征的处理逻辑2.1 Time列不能直接喂给模型但也不该直接扔掉很多教程一上来就把Time列删掉理由是它只是个序号没有业务含义。这话不完全对。Time列的单位是距第一笔交易过去的秒数虽然没有绝对时间戳但它隐含了交易在一天内的分布信息。我当时的做法是先把秒数转成小时然后画一下正常交易和欺诈交易在小时维度上的分布对比。结果是真实存在的——欺诈交易在某些时段明显更活跃。虽然这种模式在原始秒数里也能被树模型捕捉到但转成周期特征小时、星期几之后线性模型也能较好利用而且可视化分析时也更直观。import pandas as pd df[hour] (df[Time] / 3600) % 24 df df.drop(Time, axis1)不过要注意如果把Time理解成时间顺序那么数据集的划分就要非常小心。如果直接随机切分训练集里可能混入未来的样本测试集里也可能混入过去的样本这会让验证结果虚高。后面我会专门说这个数据泄露问题。2.2 标准化的顺序问题先划分再fitAmount列和Time列一样量纲和V1-V28完全不在一个级别。PCA后的V1-V28大致是零均值、单位方差的数据分布而Amount从0到几千不等。如果不做标准化逻辑回归这类对特征尺度敏感的模型会被Amount主导。标准化本身不难难在顺序。正确的流程一定是先划分训练集和测试集然后在训练集上fit工具再用同一个工具transform训练集和测试集。千万不能在切分之前对整个数据集做标准化否则测试集的信息已经通过均值和方差的估计泄露到了训练过程中。这个错误相当隐蔽模型分数可能因此虚高上线后马上露馅。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X df.drop(Class, axis1) y df[Class] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) scaler StandardScaler() X_train[Amount_scaled] scaler.fit_transform(X_train[[Amount]]) X_test[Amount_scaled] scaler.transform(X_test[[Amount]]) X_train X_train.drop(Amount, axis1) X_test X_test.drop(Amount, axis1)注意代码里的stratifyy这是切分不平衡数据时的标准做法它可以保证训练集和测试集里的正负样本比例大致一致避免某个子集里恰好没有欺诈样本。2.3 V1-V28这些看不见含义的特征怎么分析面对V1到V28常见的困惑是不知道它们代表什么怎么做特征分析。我的经验是特征的业务含义不明确不代表特征关系不明确。你可以做相关性分析看哪些特征之间强相关可以画特征分布直方图看欺诈和正常两类样本在不同特征上是否有明显分离还可以在树模型训练完成后查看特征重要性排序。实际跑下来V14、V10、V12、V17这些特征在欺诈检测中往往排在前列欺诈样本在这些维度上经常表现出明显的长尾异常。当然不同随机种子下排序会有波动但大致趋势是稳定的。另外要提醒一点不要试图对V1-V28做过多特征创造比如两两相乘之类。因为它们本身已经来自PCA信息是压缩过的再做多项式变换容易引入噪声收益通常不明显。真正值得多花时间的是金额、时间这些能结合业务理解的维度。3. 三种不平衡处理策略的实测对比3.1 随机下采样效果立竿见影但损失信息下采样的思路很直接从多数类样本里随机抽出一部分让正负样本数量达到平衡比如1:1然后用这个平衡后的子集训练模型。优点非常明显——训练速度快逻辑回归这类模型在下采样数据上能很快收敛而且效果通常立刻改善因为模型不再偏科了。缺点是浪费了大量正常交易数据这些数据里其实包含了很多正常模式的信息全部扔掉有些可惜。不过在下采样场景里有一个非常关键的操作细节验证模型时不能在下采样后的测试集上做。测试集必须保持原始的真实分布否则算出来的精确率、召回率都是失真的不能反映模型在真实环境中的表现。我见过不少人在这上面栽跟头训练集下采样后顺手把测试集也下采样了最后报告的指标毫无参考价值。3.2 SMOTE过采样用插值造数据的利与弊SMOTE的基本思路是对少数类样本做插值对每一个欺诈样本找到它在特征空间里的k个近邻然后在样本和近邻的连线上随机生成新的样本点。这样可以在不大幅重复的前提下增加少数类的数量。from imblearn.over_sampling import SMOTE smote SMOTE(random_state42) X_train_res, y_train_res smote.fit_resample(X_train, y_train)使用SMOTE时我最想强调的一点是必须只对训练集做不能碰测试集。测试集的意义是模拟真实世界的未知数据真实世界里不会给你插值造出来的样本。如果你不小心把SMOTE应用到了整个数据集上再去划分测试集里就会混入训练样本的合成近亲评估结果会虚高得离谱。SMOTE的另一个常见副作用是容易生成重叠样本特别是特征维度高且少数类样本本身分布比较分散的情况下。所以做了SMOTE之后建议配合调低模型复杂度或者加正则化否则模型很容易过拟合到合成样本的局部模式上。我实测下来SMOTE配合逻辑回归或随机森林召回率通常有明显提升但精确率会有一定下降。如果你更关心别冤枉好人那就要谨慎使用。3.3 class_weight与scale_pos_weight不改数据只改损失相比下采样和SMOTE调整损失函数权重是我个人更推荐先试的方案。核心思路是不动原始数据而是在计算损失时给少数类的误判赋予更高代价。Scikit-learn里逻辑回归和随机森林都有class_weightbalanced参数LightGBM里有scale_pos_weight参数。from sklearn.linear_model import LogisticRegression model LogisticRegression(class_weightbalanced, max_iter1000) model.fit(X_train, y_train)这个方案的好处是保留了全部数据信息训练流程也更干净——不需要额外引入不平衡处理工具库也少了很多数据重构的麻烦。代价是需要手动调试权重可以从正负样本比例的倒数开始比如这里大约为580然后在这个基础上增减。3.4 三种策略在测试集上的表现对比我自己在同一份测试集上对比了三种处理方式评估结果如下数值以实际实验为准这里的数字代表大致量级策略精确率召回率F1AUC基线逻辑回归0.880.620.730.96随机下采样0.750.850.800.96SMOTE过采样0.760.870.810.97class_weight平衡0.780.830.800.97从趋势上看不做任何处理时模型更偏向保守召回率低做了不平衡处理后召回率显著上升代价是精确率小幅下降。但注意这些对比都是基于默认阈值0.5的而实际项目中阈值本来就是可调的所以最优策略要结合阈值一起定不能只看单点结果。4. 模型选型与训练从逻辑回归到树模型的渐进路线4.1 逻辑回归不可逾越的baseline不要因为逻辑回归简单就轻视它。在欺诈检测这个场景里逻辑回归有两大优势一是训练极快可以快速验证数据流程的正确性二是参数可解释性强权重系数可以直接反映特征与风险方向的关系。我用逻辑回归作为baseline训练完成后除了看混淆矩阵还会审视每一项特征的权重。虽然特征经过了PCA不能直接对应业务含义但权重的正负和大小依然能帮你判断哪些特征方向上欺诈风险更高。逻辑回归在极度不平衡且高维的数据上配合标准化和正则化效果其实不差。很多人一上来就上LightGBM反而容易因为调参不当陷入过拟合或者花了大把时间调参后发现和逻辑回归差不多非常打击信心。4.2 随机森林特征重要性带来的额外价值随机森林是这个案例里非常值得尝试的模型。它擅长捕捉特征之间的非线性关系对异常值和缺失值也相对鲁棒并且能输出特征重要性。我在模型评估时发现随机森林和逻辑回归的泛化能力可能互有胜负但随机森林在召回率上的表现通常更好特别是当欺诈模式在多个特征联合维度上才明显时。训练完成后一定要输出feature_importances_看看哪些特征被重点使用。这不仅能帮你验证前面特征分析阶段的结论还能为后续规则引擎的构建提供依据——模型认为的高风险特征本质上就是业务规则里值得重点关注的方向。随机森林的一个劣势是模型体积大、推断速度相对慢。如果线上交易量大每笔交易都要实时打分随机森林的效率可能会成为瓶颈。这时候LightGBM往往就是更好的替代方案。4.3 LightGBM效率与效果的平衡点LightGBM是梯度提升树的一种训练速度和推断速度都很快内存占用也更低。在处理大规模交易数据时这是很实际的优势。import lightgbm as lgb model lgb.LGBMClassifier( scale_pos_weight200, n_estimators300, learning_rate0.05, random_state42 ) model.fit( X_train, y_train, eval_set[(X_test, y_test)], eval_metricauc )一个值得注意的实操细节是LightGBM在训练时可以直接通过eval_set传入验证集配合early_stopping可以提前停止训练以避免过拟合。这让调参过程高效很多不用手动试几百棵树的迭代次数。在我这个项目里LightGBM在AUC上一般比随机森林再高一些推理速度也更快。但如果你的目标是上线落地我建议仍然保留逻辑回归版本的模型作为对照因为它简单、可解释有时候在特定场景下反而是最不容易出错的选择。4.4 过拟合信号怎么识别不平衡数据集上过拟合的识别有明显特征训练集上的AUC接近1测试集上的AUC却明显偏低或者训练集上精确率和召回率都很高测试集上却大幅缩水。为什么容易出现这种情况因为欺诈样本数量只有几百个模型完全可以把它们全部背下来。特别是SMOTE之后如果树模型深度过大甚至会在合成样本之间画出生硬的决策边界。反制过拟合的手段包括限制树的深度max_depth设为3-5、增大叶节点最小样本数min_child_samples、调高正则化参数reg_alpha、reg_lambda、减少弱学习器数量。调参的原则是优先限制模型复杂度而不是无限增加数据量。数据量本身已经固定能控制的就是模型的拟合能力。5. 模型评估与业务落地用成本函数找最佳阈值5.1 别盯着AUC混淆矩阵才是业务语言AUC是一个非常稳健的排序指标它衡量的是模型对正负样本的排序能力不依赖具体阈值。在模型选型阶段AUC是个很好的参考。但真要给业务方交付AUC就太抽象了——业务想知道的是如果每天有100万笔交易模型会标记出多少笔需要风控复核这里面有多少是真的欺诈又有多少欺诈会被漏掉这些问题只有混淆矩阵能回答。把测试集预测结果与真实标签做交叉对比得到TP、FP、FN、TN四个值然后算精确率Precision和召回率Recall精确率 TP / (TP FP)它回答模型说这是欺诈有多大概率是真的欺诈召回率 TP / (TP FN)它回答所有真欺诈里模型抓到了多少在欺诈检测场景中这两个指标通常是此消彼长的。阈值提高模型更保守标记出的可疑样本变少精确率上升但召回率下降阈值降低模型更敏感召回率上去了但误报也增多。5.2 阈值搜索把概率变成决策模型输出的本质是概率分数而非直接给出0/1类别。默认情况下人们习惯用0.5作为分类阈值但在不平衡数据集上这个默认值几乎没有合理性——因为正常交易的预测概率天生就集中在高位欺诈样本的概率值虽然在0.5以下但也远高于大多数正常样本。正确的做法是将选阈值独立成一个决策环节。我通常按这个流程操作在验证集上得到每个样本的预测概率从0.1到0.9遍历所有可能的阈值对每个阈值计算精确率、召回率、F1结合业务成本选择最优阈值业务成本在欺诈检测里非常具体漏掉一笔欺诈FN意味着实际资金损失金额等于交易金额误报一笔正常交易FP意味着人工复核成本或用户被打扰的风险。如果两类成本可以量化就构造一个成本函数选择让总成本最低的阈值。import numpy as np from sklearn.metrics import precision_score, recall_score def search_threshold(y_true, y_prob, cost_fp1.0, cost_fn5.0): best_thr 0.5 best_cost float(inf) for thr in np.arange(0.1, 0.9, 0.05): y_pred (y_prob thr).astype(int) fp ((y_pred 1) (y_true 0)).sum() fn ((y_pred 0) (y_true 1)).sum() total_cost fp * cost_fp fn * cost_fn if total_cost best_cost: best_cost total_cost best_thr thr return best_thr, best_cost这个例子里的cost_fn是cost_fp的5倍意思是漏掉一笔欺诈的代价是被打扰一个正常用户代价的5倍。实际项目里这个比例要根据资金损失金额、人工成本等多因素估算不同场景差异很大。5.3 从模型到规则引擎部署时的现实问题模型跑出概率之后上线部署时通常会把它作为一个风控打分模块与其他规则一起组成决策链路。比如如果模型输出概率 0.8直接拒绝交易如果模型输出概率在 0.3 到 0.8 之间进入人工复核如果模型输出概率 0.3放行这个分层设计与直接一个硬阈值相比可以兼顾风控力度和用户体验。实际部署时还有一个容易被忽略的环节——做好模型输入的时序对齐。线上拿到一笔交易时需要实时把特征算好、标准化参数调好尤其是线性模型然后喂给模型。如果代码里训练的标准化参数和线上加载的不一致输出分就会完全失真。所以我现在做这类项目时都会习惯性地把预处理流程和模型一起序列化成一个Pipeline而不是分开保存预处理器和模型。这样能最大程度避免训练-推理不一致的问题。6. 踩过的一些坑与可复用的经验6.1 数据泄露的几个隐蔽来源数据泄露是机器学习项目里最棘手的问题之一因为它不会让代码报错却会让指标虚高。信用卡欺诈检测数据集里最经典的隐藏陷阱就是Time列顺序。我当时做了一件事来验证这个问题分别用随机切分和按时间切分两种方式划分数据前者随机打乱后者严格按时间阈值划分前80%的时间为训练集后20%为测试集。同样是逻辑回归按时间切分出来的AUC比随机切分要低一些。这说明随机切分确实会把未来的信息泄露给训练过程导致对模型真实泛化能力的误判。现实中做欺诈模型切分方式一定不能是随机切分而是按时间滚动切分。因为模型要预测的是明天会不会发生欺诈而不是这些样本和昨天的样本混在一起后模型能不能把他们分开。6.2 特征工程的边界别把未来信息带进训练有一种非常隐蔽的特征泄露发生在你构造聚合类特征时。比如你想构造这个用户过去一小时的平均交易金额作为风险特征如果实现方式不对这个特征里可能就包含了当前这笔交易之后的交易信息。这在离线训练时很难用肉眼发现但上线后会表现为模型效果断崖式下跌。防止这个问题的手段倒也不复杂构造任何时间序列类的特征时都必须只使用该笔交易发生之前的数据。实现时要先按时间排序再用shift/rolling等操作逐行计算不能在没有排序的数据集里用groupby直接聚合。另外标准化操作本身也可能成为泄露源头。前面反复强调的先划分再fit就是这个道理。包括SMOTE、下采样凡是会利用全局数据分布信息的过程都必须放在划分之后并且只针对训练集。6.3 后续可做的扩展这个项目做完之后可扩展的方向其实很多。如果你有兴趣继续深入我建议可以尝试把单模型替换成Stacking集成用逻辑回归和LightGBM的预测概率作为下一层模型的输入引入时序交叉验证替代单一的随机验证更接近真实部署环境尝试用异常检测的思路去做欺诈识别把欺诈当作偏离正常模式的离群点来研究给模型输出校准概率用Platt Scaling或Isotonic Regression把预测概率校准到更贴近真实概率水平这样业务侧直接看分数就能理解风险程度把规则引擎与模型结果做融合比如把已知的专家规则作为模型输入特征形成规则模型双通道决策天花板不在于数据集本身而在于你怎么看待模型只是业务决策里的一环这层关系。模型给的是一个概率但最终决定是否拦截交易的是业务成本和用户体验之间的平衡。把这一点想清楚了这个案例能带给你的就不只是几个模型分数而是一整套解决不平衡分类问题的思路和工程习惯。
返回列表