ARTICLE DETAIL

资讯详情

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

金融反欺诈检测Python实战:处理不平衡数据与模型调优

金融反欺诈检测Python实战:处理不平衡数据与模型调优 简介面向Python机器学习初学者的金融反欺诈检测完整项目适用于期末作业、课程设计或项目开发。项目以信用卡欺诈识别为实战场景覆盖数据预处理缺失值处理、特征筛选与标准化、SMOTE不平衡采样、stacking集成学习模型构造等关键环节能清晰演示从原始数据清洗、特征工程到模型训练与评估的全流程。资源包共17个文件压缩包大小约66.7MB核心包含2个Jupyter Notebook代码文件、可视化HTML模型展示、项目报告PPT、README开发文档以及若干图片素材与数据文件。所有代码均经过严格测试可放心运行并方便在此基础上进行二次开发与功能扩展。目前已有192人学习使用。除可直接运行的源码外配套开发文档与数据报告还能帮助理解各类特征的含义、模型调参思路与结果评价。尤其适合需要快速完成期末作业、课程设计或项目答辩的读者既能学到反欺诈建模的完整思路也能直接复现实验、替换数据集进行扩展实验。1. 金融反欺诈检测一个数据不平衡到极致的机器学习项目一份交易记录里正常交易占99.8%欺诈交易只有0.2%你训练出的模型准确率高达99.9%却一个欺诈都没抓住。这不是段子而是金融反欺诈检测这个python机器学习项目最常见的结果。这个题目被反复选作期末大作业和课程设计原因就在于它逼着你面对真实世界里最折磨人也最值钱的问题类别极度不平衡、评估指标选错就全盘皆输、业务阈值远比默认的0.5复杂。用python实现反欺诈检测既要能跑通源码也要能讲明白每一个参数为什么这样设。这篇文章给要做课设、要交开发文档、想把项目写进简历的人把原理、落地步骤和踩坑点一次说清。2. 先看懂任务再写代码金融反欺诈要解决的根本问题2.1 欺诈检测在数学上是什么一个极度不平衡的二分类问题金融反欺诈检测本质上是一个二分类问题每条交易记录进来模型输出它是正常交易还是欺诈交易。但难点不在分类本身而在数据分布。公开的信用卡欺诈数据集里欺诈样本通常只占0.1%到1%这意味着哪怕模型把所有样本都判成正常准确率依然高达99%以上。很多第一次做这个题目的人看到准确率报表非常兴奋直到检查召回率才发现模型一个欺诈都没识别出来。还有一层隐蔽的困难欺诈行为是动态对抗的。欺诈者会不断试探风控规则今天管用的特征下个月可能就失效。所以金融反欺诈检测不只是训练一个静态的机器学习模型而是要求你建立一套能持续评估、持续调整的流程。这也是为什么课程设计里除了源码通常还要你交数据报告和开发文档——老师想看的是你有没有理解模型的边界而不只是代码能不能跑。代价的不对称也决定了评估方式。把正常交易误判为欺诈损失的是一次客服介入和可能的用户投诉把欺诈交易放过损失的是真金白银。所以在反欺诈场景里召回率recall的优先级通常高于精确率precision而这两者的平衡点就是后面要调的阈值。理解了这一点你才会明白为什么不能拿准确率当核心指标。2.2 拿到源码包先做数据体检四个必查项不管你是自己写代码还是拿到了现成的源码包第一步永远不是训练而是体检数据。很多课设翻车都翻在没看数据就开跑。我一般拿到数据集后会先跑一遍这个脚本import pandas as pd df pd.read_csv(creditcard.csv) print(样本量:, df.shape[0]) print(特征数:, df.shape[1]) # 标签分布正常交易为0欺诈交易为1 print(df[Class].value_counts()) print(df[Class].value_counts(normalizeTrue)) # 缺失值和重复行检查 print(缺失值总数:, df.isnull().sum().sum()) print(重复行数:, df.duplicated().sum())这段代码做的事很简单看样本总量、看标签比例、看缺失和重复。value_counts(normalizeTrue)会直接输出正负样本的百分比如果欺诈比例低于1%后面所有处理都要围绕这个不平衡来设计。缺失值总数如果不为0需要记录是哪些特征有缺失常见做法是用中位数填充或直接删除该列但不能不做处理。还有两个容易被忽略的字段Time和Amount。Amount是交易金额不同商户的金额跨度极大后面必须做标准化。Time在公开数据集里通常表示距首条记录的时间差很多同学直接丢掉这列也没问题但如果想做得更细可以从时间里切出“是否是凌晨”“是否是周末”这类特征对欺诈识别往往有意外帮助。2.3 模型选型理由逻辑回归与随机森林的分工这个题目选什么模型决定了你开发文档里能写出多少实质内容。常见做法是“逻辑回归做基线随机森林做提升最后统一调阈值”。逻辑回归的优点是训练快、系数可解释配合标准化后的特征你能直接看到哪个特征对欺诈判断贡献最大答辩时很好讲。随机森林的优点是不用担心特征尺度能捕捉非线性关系配合class_weight可以处理不平衡问题。不建议一上来就上XGBoost或深度学习。不是不能用而是课设答辩时老师会追问“你为什么选这个模型”如果你只能说“因为它效果好”这题就不好收场。逻辑回归和随机森林都有清晰的数学机理文档里可以写出模型公式、损失函数、正则化参数的作用这些内容才撑得起“开发文档说明”这个交付物。如果你拿到源码包里已经有训练好的模型也别直接拿去跑预测。先问一句这个模型是在什么数据上训练的划分方式是什么评估指标是什么下文会一步步拆给你看这三件事每个都有坑。3. 把样本喂进模型前特征工程与类别不平衡处理的落地步骤3.1 数据清洗与标准化让逻辑回归不翻车的最小操作逻辑回归对特征尺度极其敏感。想象一下Amount的取值范围从0到几千而PCA降维后的V1到V28基本在正负几个标准差内如果不做标准化逻辑回归的梯度更新会被金额这个大尺度特征主导模型系数会失真训练也不稳定。金融反欺诈检测里最常见的预处理组合就是StandardScaler它把每个特征减去均值再除以标准差所有特征回到同一个量纲上。这里有一个关键细节fit和transform必须分开。fit只能在训练集上做算出训练集的均值和标准差然后用这两个值去transform测试集。如果你先对全量数据做标准化再划分训练测试集测试集的信息就已经污染了训练过程模型评估结果会偏乐观这在反欺诈这种数据分布敏感的题目里很致命。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X df.drop(columns[Class]) y df[Class] # stratify保证划分后训练集和测试集的正负比例一致 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 标准化fit在训练集transform在测试集 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)stratifyy这行建议保留。如果不加切分时可能把数量极少的欺诈样本全部切到训练集或测试集后面的评估就没有意义了。random_state42固定随机种子保证每次运行结果可复现写文档时实验结果也对得上。重复行要不要删公开数据集里通常没有重复行但如果你自采的数据有重复建议删除。需要说明的是删除的是“一模一样的整行记录”不是删除重复的Time或Amount值后者可能是不同商户在同一时刻的合法交易删了会引入偏差。3.2 类别不平衡欠采样、过采样和class_weight怎么选处理类别不平衡有三种主流方案很多人把它们混着用结果模型效果反而变差。先明确各自的定位。第一种是调整class_weight让模型在计算损失时给少数类更高的权重这是最轻量、最不容易出问题的方法任何模型都能用。第二种是欠采样undersampling从多数类里随机抽取一部分样本让正负比例接近1:1缺点是会丢掉大量正常交易的信息模型可能欠拟合。第三种是过采样oversampling用SMOTE等算法在少数类附近合成新样本这样既不丢数据又能平衡类别。在金融反欺诈检测这个题目里我一般推荐的顺序是先用class_weight跑一版基线如果效果不够再上SMOTE。SMOTE的效果在大部分不平衡数据集上确实好但它有一个必须避开的陷阱——只能在划分训练测试集之后、只对训练集做。很多同学顺序搞反在完整数据集上跑SMOTE再切分合成样本会把测试集“污染”最后的评估成绩虚高到离谱一上真实场景就原形毕露。这个坑下文第5章还会细说这里先记住顺序铁律先切分再采样。from imblearn.over_sampling import SMOTE # SMOTE只作用于训练集测试集保持原始分布 smote SMOTE(random_state42) X_train_res, y_train_res smote.fit_resample(X_train_scaled, y_train) # 查看采样后的类别分布 print(pd.Series(y_train_res).value_counts())SMOTE的基本原理是对每一个少数类样本找到它的k个近邻样本在两者连线上随机生成新样本。random_state42依然是为了可复现。采样后训练集的欺诈样本数量会增长到和正常样本相当此时逻辑回归看到的是一个平衡的分布不再“懒惰地”把所有样本都判成正常。3.3 特征范围与业务理解别只盯着算法跑分处理完不平衡后很多人就直接训练了忽略了一个问题特征和业务的关系。公开数据集的V1到V28是PCA降维后的匿名特征本身没有业务含义但Time和Amount是真实字段。以我做过类似风控项目的经验交易金额这个特征在欺诈检测里尤其关键。大额交易和小额交易的行为模式完全不同对欺诈概率的先验也不一样所以Amount标准化后建议保留不要因为数值范围大就删掉。特征工程能做到什么程度取决于你的数据。如果你用的是其他金融数据集不是匿名的可以考虑组合特征例如“过去一小时内该卡片的交易次数”“当前交易金额与历史均值之比”这类特征能明显提升模型上限。但如果只有一张卡的空间、没有时间窗口构建能力就别硬造反而引入噪声。课设阶段把标准化的流程写清楚把为什么保留哪些特征讲明白已经足够拿分。另外要注意训练集里欺诈样本数量如果极低比如不足200条SMOTE合成的样本可能会高度重复模型过拟合到合成样本上。这时候可以先把k_neighbors参数调小比如设为3减少每个样本参考的邻居数量合成出的样本更多样一些。具体效果用验证集的召回率来判断而不是凭感觉。4. 建模与调优从基线模型到可用欺诈检测器的完整路径4.1 先用逻辑回归建立基线搞清评估指标选哪个模型选型之前先把基线跑起来。逻辑回归在这个场景下是最适合当基线的模型原因有三个训练速度快参数少而且输出的是概率分数而不是硬分类正好为后面的阈值调优做准备。用sklearn.pipeline把标准化和逻辑回归串起来代码更整洁也方便交叉验证。from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline from sklearn.metrics import precision_recall_curve, recall_score, precision_score model LogisticRegression(max_iter1000, C1.0) model.fit(X_train_res, y_train_res) # 输出概率分数用于后续阈值调整 y_score model.decision_function(X_test_scaled) # 以0.5为阈值的基准表现 y_pred_default (y_score 0).astype(int) print(默认阈值 精确率:, precision_score(y_test, y_pred_default)) print(默认阈值 召回率:, recall_score(y_test, y_pred_default))这里有个细节decision_function返回的是决策函数值对逻辑回归来说就是线性变换后的分数正负号对应分类方向。如果觉得不好理解可以用predict_proba(X_test_scaled)[:, 1]取正类的概率效果是一样的。max_iter1000是为了保证标准化后的逻辑回归能收敛不调的话可能报ConvergenceWarning。评估指标的选择直接决定你看到的实验结果。很多同学第一眼会看ROC-AUC但这个指标在不平衡数据集上非常容易被“虚高”欺骗。ROC的横轴是假正率负样本基数巨大即使误判很多正常交易假正率的绝对数值也不大画出来的曲线依然漂亮。反欺诈场景更该看的是PR曲线精确率-召回率曲线它的横轴是召回率纵轴是精确率直接反映“抓到了多少欺诈”和“误伤了多杀正常交易”之间的拉扯。代码里用precision_recall_curve画出来的那条曲线才是这个项目的真实水平。4.2 随机森林与class_weight什么时候值得换复杂模型逻辑回归跑通后可以试着用随机森林提升效果。随机森林对不平衡数据的天然优势在于它可以通过class_weight在每棵树的节点分裂时给少数类更高的权重不依赖SMOTE也能有一定效果。但不要再对X_train_res做标准化树模型对尺度不敏感标准化反而白白增加计算量。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators200, max_depth8, class_weightbalanced, random_state42, n_jobs-1 ) rf.fit(X_train_res, y_train_res) # 查看特征重要性前10个特征是谁 importance sorted(zip(rf.feature_importances_, X.columns), reverseTrue)[:10] for score, name in importance: print(f{name}: {score:.4f})class_weightbalanced是自动根据类别比例计算权重不用手填数字。max_depth8限制了每棵树深度防止单棵树过拟合少数类样本。n_jobs-1让所有CPU核心并行训练200棵树在普通笔记本上也就几秒钟。特征重要性是随机森林附带的解释能力开发文档里放一张特征重要性Top10表比贴一堆训练日志有力得多。如果随机森林的召回率比逻辑回归有明显提升说明数据里确实存在非线性关系。如果两者差不多就别纠结逻辑回归更容易解释答辩时反而更稳。有很多人迷信“模型越复杂越好”在金融反欺诈这里业务方要的是“这个模型为什么拒绝了我的交易”逻辑回归能回答随机森林只能说“树投票投出来的”这是很多课设文档没写透的地方。4.3 阈值不是0.5用PR曲线把模型调到业务可用金融反欺诈检测里最容易被忽视、也是最终决定模型能不能用的是阈值。默认情况sklearn的.predict()以0.5为界probability大于0.5判为欺诈小于等于0.5判为正常。但在正样本占比只有0.2%的数据里模型输出的欺诈概率普遍很低用0.5做阈值会漏掉绝大多数欺诈交易。调整阈值是成本最低、见效最快的优化手段。import numpy as np precision, recall, thresholds precision_recall_curve(y_test, y_score) # F1最大化是PR曲线上最常用的选点方式 f1_scores 2 * precision * recall / (precision recall 1e-9) best_idx np.argmax(f1_scores) best_threshold thresholds[best_idx] print(F1最大时的阈值:, round(best_threshold, 4)) print(对应精确率:, round(precision[best_idx], 4)) print(对应召回率:, round(recall[best_idx], 4))跑完这段你会发现最佳阈值通常不是0.5而是某个负值或很低的概率。这是正常现象不用怀疑模型出了问题。precision_recall_curve返回的thresholds数量比precision少一个所以用np.argmax(f1_scores)索引时要小心边界代码里对分母加了1e-9是防止除零报错。调阈值本质上是回答一个问题银行愿意承受多少次误报来换回多少比例的欺诈资金。如果业务要求“宁可错杀一千不可放过一个”就把阈值往低调精确率会下降召回率会上升如果客服资源有限就把阈值往高调保证报警的精确率。把这个决策过程写进数据报告里比写“我们用了网格搜索调参”高级得多因为老师想看到你具备把模型输出转化为业务决策的能力。5. 金融反欺诈检测常见误区与排查五条血泪经验5.1 随机划分训练集导致时间穿越现象模型在验证集上召回率0.85交叉验证也很稳定部署到新数据上效果骤降。原因金融交易数据有强时间相关性。随机划分时同一时间段的数据可能同时出现在训练集和测试集模型间接“见过”测试集的行为模式。更严重的是欺诈模式随时间漂移昨天有效的规则今天可能失效。用随机切分评估的是“在相似时间段内识别相似欺诈”而不是“预测未来的欺诈”。解决按时间顺序切分。用训练集的前80%时间段的交易做训练后20%做验证。如果源码包里的数据没有明确时间戳也要在开发文档里诚实说明这一局限并建议获取带时间字段的数据后重新验证。这个坑如果你主动写进报告反而是加分项。5.2 SMOTE在切分前使用测试集被污染现象过采样后模型在验证集上精确率、召回率都奇高但拿到真实数据上预测非常稀疏大量欺诈漏报。原因对全量数据做SMOTE合成样本会在原始特征空间里“散布”切分后训练集和测试集共享了这些合成样本的分布信息。更直接的污染是合成样本可能和测试集样本距离很近模型等于提前见过类似样本。解决铁律是先划分、后采样。代码顺序应该是train_test_split在前SMOTE.fit_resample只作用于X_train。这条在评审和答辩时被问到的概率极高务必在自己的文档中明确说明。5.3 只看准确率模型把欺诈全放过现象准确率打印出来99.8%看着很漂亮但混淆矩阵显示预测的欺诈数量为0。原因类别极度不平衡下准确率不是合适的评估指标。全部预测为正常也能拿到99%以上的准确率而且这个数字不会给你任何警示。解决至少报告四个数字——精确率、召回率、F1、PR-AUC。其中PR-AUC作为模型整体性能的参考召回率作为业务风险的底线。如果你发现只有准确率高其他指标都很差说明模型根本没有学到欺诈模式回去检查类别平衡处理。5.4 逻辑回归系数异常大现象模型训练完某个特征的系数达到几十甚至上百其他特征系数都很小测试集表现也不稳定。原因特征未标准化或标准化被跳过。逻辑回归的系数大小与特征尺度直接相关金额特征动辄上千算法为了让损失变小会把对应系数压到极小或撑到极大来补偿尺度差异梯度更新也不稳定。解决确认StandardScaler在训练前执行并且fit只在训练集上。标准化后逻辑回归的系数才有可比性解释为“该特征每变化一个标准差欺诈log-odds的变化量”。5.5 随机森林不设class_weight总是预测多数类现象随机森林不输逻辑回归参数但预测结果里欺诈样本数量极少精确率高得离谱、召回率几乎为0。原因树模型分裂节点时默认按基尼不纯度最小化来选择特征和分裂点。多数类样本量大即使不平衡也不会给少数类特殊照顾叶子节点最终被多数类主导。解决设置class_weightbalanced让少数类的错误分类代价更高。如果用了之后召回率还是上不去再考虑配合SMOTE但注意保持先切分后采样的顺序。6. 把课设做成能答辩的项目验证、解释与收尾技巧验证一个反欺诈检测模型除了随机划分更稳妥的做法是模拟时间线上的上线测试。把数据按交易时间排序取前80%作为训练窗口后20%作为未来数据模型只允许看见过去、不允许看见未来。跑完对比一下随机划分和时序划分的召回率差距这个差距本身就是你在“数据报告”里可以大书特书的内容——说明模型对时间漂移有多敏感以及未来上线时需要多久重新训练一次。模型保存也要带上阈值这是很多人会漏掉的细节。你调优找到的best_threshold和模型本身是一体的部署时如果只加载模型、丢掉了阈值风控系统会用默认0.5去卡前文调优全部白费。保存代码很简单import joblib joblib.dump(rf, fraud_model.pkl) np.save(best_threshold.npy, best_threshold)开发文档里建议包含的内容大致有项目背景与问题定义、环境依赖清单、运行步骤、数据说明与预处理、模型选型对比实验、评估指标与阈值说明、已知局限与改进方向。这份文档不是事后补的而是在你跑实验的过程中同步记录的。每条实验记下当时的数据规模、类别比例、模型参数和评估结果最后汇总时你会发现这份文档天然就是完整的。数据报告的核心是“讲清楚为什么做这些选择”。把类别分布直方图、PR曲线、阈值-F1曲线、特征重要性Top10这几张图放进去每张图下写两段分析图上看到了什么、这个信息如何影响了你的下一步决策。老师最反感的是堆一堆图表却没有观点哪怕你的观点是“SMOTE在这个数据上并没有显著提升原因是原始特征分离度有限”也比写“效果很好”更经得起问。最后说一个我做课设和带人做项目的习惯永远先写文档目录再写代码。目录写下的那一刻你就知道要跑哪些实验、要画哪些图、要回答哪些问题。代码跑完文档内容顺手就填进去了而不是最后熬夜补一份漂亮但没细节的报告。这个习惯帮我躲过了很多答辩现场答不上来的尴尬。希望帮到你。本文还有配套的精品资源点击获取
返回列表