简介这份资源面向计算机、数据科学、人工智能等专业的在校学生、教师及企业员工提供一套基于机器学习实现金融反欺诈检测的完整Python项目源码可用于课程设计、毕业设计、大作业或初期项目立项演示也适合作为入门进阶的实战参考。压缩包共17个文件约133.46MB包含2个ipynb代码文件、1个pptx项目报告、1个html模型页面、1个md说明文档以及png、jpg等模型图例与可视化图片覆盖从数据预处理到模型评估的完整流程。项目围绕信用卡交易数据展开涉及缺失值处理、SMOTE样本平衡、GridSearchCV调参、阈值选择与PR—Re曲线分析等关键环节并配有目标变量可视化、直方图分布、场景解析等图示便于理解反欺诈建模思路。目前已有171人学习下载代码经运行验证稳定可靠读者可据此快速复现实验、撰写报告并在此基础上进行二次开发与功能拓展。1. 金融反欺诈检测为什么总在“样本不均衡”上翻车信用卡盗刷、信贷申请欺诈、营销薅羊毛这些场景背后都指向同一个技术命题金融反欺诈检测。你拿到的是一份基于机器学习实现的金融反欺诈检测 Python 源码附带说明文档和 PPT 报告。但真正决定这套代码能不能落地的不是模型选得多花哨而是你有没有意识到——欺诈样本通常只占总量的 0.1% 到 2%用常规的准确率去衡量模型把所有交易都判为“正常”就能拿到 99% 的准确率可这毫无意义。这份源码适合两类人一是正在做机器学习课程设计或期末项目的学生需要一套能跑通、能讲清楚流程的完整方案二是刚接触风控场景的工程师想用 Python 快速搭一个可解释、可迭代的检测基线。它解决的核心问题不是“做一个最强模型”而是“用一套规范的流程把数据清洗、特征工程、不平衡处理、模型训练和评估串起来”。如果你指望直接拿它上线生产那需要补的东西还很多但如果你想理解金融反欺诈检测的完整链路并在此基础上做二次开发这套源码是一个不错的起点。2. 拆解反欺诈检测的完整链路从原始交易到模型输出2.1 数据长什么样决定了你后面能走多远金融交易数据通常包含几类字段交易时间、交易金额、商户类别、持卡人信息、设备指纹、地理位置等。在这份源码里你大概率会看到一个结构化的 CSV 文件每行代表一笔交易最后一列是标签0 表示正常1 表示欺诈。常见做法是先用 pandas 做一轮探索性分析看看缺失值比例、类别型字段的取值分布、金额的偏态程度。import pandas as pd import numpy as np # 读取交易数据注意编码和分隔符可能因数据源而异 df pd.read_csv(transactions.csv, encodingutf-8) # 查看标签分布这是判断不平衡程度的第一个指标 print(df[is_fraud].value_counts(normalizeTrue)) # 检查缺失值超过 30% 缺失的字段考虑直接丢弃 missing_ratio df.isnull().mean() print(missing_ratio[missing_ratio 0].sort_values(ascendingFalse)) # 金额字段通常右偏严重先看分位数再决定怎么变换 print(df[amount].describe(percentiles[0.5, 0.9, 0.99, 0.999]))这段代码的逻辑很直接先确认标签比例再评估缺失情况最后看金额分布。参数上normalizeTrue让你直接看到比例而不是计数percentiles里加上 0.999 是为了观察极端大额交易——欺诈交易往往集中在尾部。如果发现某个字段缺失超过三成不要急着填充先想清楚这个字段是不是在交易发生时根本拿不到如果是直接删掉比强行插补更安全。2.2 特征工程把“时间”和“行为”变成模型能吃的数字原始字段直接喂给模型效果通常很差尤其是时间戳和类别型变量。时间字段要拆成年、月、日、小时、星期几甚至“是否深夜交易”这种二值特征。类别型字段如商户类别可以用目标编码或频率编码但要注意在训练集上计算编码值再映射到验证集否则会泄露标签。# 时间特征拆解 df[trans_time] pd.to_datetime(df[trans_time]) df[hour] df[trans_time].dt.hour df[day_of_week] df[trans_time].dt.dayofweek df[is_night] df[hour].apply(lambda x: 1 if x 22 or x 5 else 0) # 金额分箱把连续值变成有序类别增强鲁棒性 df[amount_bin] pd.qcut(df[amount], q10, labelsFalse, duplicatesdrop) # 类别型字段频率编码用出现次数替代原始类别 freq_map df[merchant_category].value_counts().to_dict() df[merchant_freq] df[merchant_category].map(freq_map) # 注意目标编码必须在交叉验证的每一折内部单独计算这里仅作示意 # 实际项目中建议用 category_encoders 库的 TargetEncoder 配合 Pipeline这里有几个参数需要留意。pd.qcut的q10表示按分位数切成十段duplicatesdrop是为了防止分位点重复导致报错。频率编码用value_counts()得到映射字典好处是不会引入标签信息坏处是高频类别和低频类别的区分度可能不够。如果你要用目标编码务必记住编码值只能在训练折上计算验证折和测试折只能用训练折的映射表这是血泪教训很多人在这一步翻车导致线下 AUC 虚高。2.3 不平衡处理过采样、欠采样还是代价敏感欺诈检测最核心的矛盾就是正负样本极度不平衡。源码里常见的做法是 SMOTE 过采样但它有几个坑一是在高维稀疏特征上效果不稳定二是可能生成不存在的“合成欺诈样本”导致过拟合。我一般会先试class_weightbalanced让模型自己在损失函数里给少数类更高权重这比直接过采样更稳妥。from imblearn.over_sampling import SMOTE from sklearn.model_selection import train_test_split # 先划分训练集和测试集SMOTE 只能用在训练集上 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) # 在训练集上做 SMOTEk_neighbors 默认 5样本极少时可降到 3 smote SMOTE(random_state42, k_neighbors3) X_train_res, y_train_res smote.fit_resample(X_train, y_train) print(f原始训练集正样本比例: {y_train.mean():.4f}) print(fSMOTE 后正样本比例: {y_train_res.mean():.4f})stratifyy保证划分后训练集和测试集的标签比例一致这个参数在不平衡场景下必须加。k_neighbors3是因为欺诈样本本身可能只有几十条用默认的 5 容易生成过于相似的样本。SMOTE 之后一定要用独立测试集评估不能用过采样后的数据做验证否则你看到的召回率是假的。3. 用 Python 把模型跑起来训练、评估与阈值选择3.1 选哪个模型逻辑回归、随机森林还是 XGBoost在这份源码里大概率会看到逻辑回归和随机森林的对比。逻辑回归可解释性强系数能直接看出特征影响方向但拟合非线性关系能力弱。随机森林和 XGBoost 效果好但调参成本高。我的建议是先用逻辑回归跑一个基线确认特征工程没有严重问题再上树模型。from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier from sklearn.metrics import roc_auc_score, average_precision_score # 逻辑回归基线max_iter 调大防止不收敛 lr LogisticRegression(class_weightbalanced, max_iter1000, random_state42) lr.fit(X_train_res, y_train_res) lr_prob lr.predict_proba(X_test)[:, 1] # 随机森林n_estimators 先设 200后面再调 rf RandomForestClassifier( n_estimators200, max_depth10, class_weightbalanced, random_state42, n_jobs-1 ) rf.fit(X_train_res, y_train_res) rf_prob rf.predict_proba(X_test)[:, 1] # XGBoostscale_pos_weight 设为负正样本比 scale (y_train 0).sum() / (y_train 1).sum() xgb XGBClassifier( n_estimators300, max_depth5, learning_rate0.05, scale_pos_weightscale, eval_metricauc, random_state42 ) xgb.fit(X_train_res, y_train_res) xgb_prob xgb.predict_proba(X_test)[:, 1] # 评估AUC 看排序能力AP 看少数类检索能力 for name, prob in [(LR, lr_prob), (RF, rf_prob), (XGB, xgb_prob)]: print(f{name} AUC: {roc_auc_score(y_test, prob):.4f}, AP: {average_precision_score(y_test, prob):.4f})class_weightbalanced会自动按标签频率反比设置权重scale_pos_weight是 XGBoost 里对应的参数。评估指标上AUC 衡量的是模型把正样本排在负样本前面的能力但在极度不平衡时 AUC 可能虚高平均精度 AP 更能反映少数类的检测质量。如果 AP 低于 0.3说明模型对欺诈样本的识别能力还不够需要回头检查特征。3.2 阈值不是 0.5用业务代价反推最佳切分点模型输出的是概率真正做决策需要选一个阈值。默认 0.5 在欺诈场景下几乎肯定不合适因为漏掉一个欺诈交易的代价远高于误拦一个正常交易。你需要和业务方确认漏判一个欺诈的损失是多少误判一个正常的损失是多少然后算期望代价最小的阈值。import numpy as np from sklearn.metrics import confusion_matrix def find_best_threshold(y_true, y_prob, cost_fn, cost_fp): cost_fn: 漏判一个欺诈的代价假阴性 cost_fp: 误判一个正常的代价假阳性 thresholds np.arange(0.01, 1.0, 0.01) best_thresh, best_cost 0.5, float(inf) for t in thresholds: y_pred (y_prob t).astype(int) tn, fp, fn, tp confusion_matrix(y_true, y_pred).ravel() total_cost fn * cost_fn fp * cost_fp if total_cost best_cost: best_cost total_cost best_thresh t return best_thresh, best_cost # 假设漏判代价是误判的 20 倍 best_t, min_cost find_best_threshold(y_test, xgb_prob, cost_fn20, cost_fp1) print(f最佳阈值: {best_t:.2f}, 最小总代价: {min_cost})这个函数的逻辑是遍历所有可能的阈值计算混淆矩阵再按业务代价加权求和。cost_fn20表示漏掉一个欺诈相当于误拦 20 个正常交易的损失这个比例需要根据实际业务场景调整。如果业务方无法给出具体数字可以先从 10 到 50 之间试几组看阈值变化对召回率和精确率的影响。3.3 模型可解释性SHAP 值告诉你哪些特征在起作用金融风控场景对可解释性要求很高你不能只给业务方一个黑箱模型。SHAP 是目前最常用的解释工具它能给出每个特征对单笔交易预测结果的贡献值。import shap # 用 TreeExplainer 解释树模型速度比 KernelExplainer 快很多 explainer shap.TreeExplainer(xgb) shap_values explainer.shap_values(X_test) # 全局特征重要性取绝对值平均 shap.summary_plot(shap_values, X_test, plot_typebar) # 单笔交易解释看哪些特征把预测推向了欺诈 shap.force_plot(explainer.expected_value, shap_values[0], X_test.iloc[0])TreeExplainer对 XGBoost 和随机森林都有很好的支持计算复杂度是多项式级别。summary_plot的 bar 类型展示全局重要性force_plot展示单笔预测的特征贡献方向。如果发现某个特征的重要性异常高但业务上说不通比如“交易序号”这种无关变量那很可能是数据泄露需要排查特征生成逻辑。4. 避坑指南反欺诈模型落地时最容易踩的五个坑4.1 用未来数据训练模型线下指标好看线上直接崩现象线下 AUC 0.95上线后一周内欺诈召回率不到 30%。原因特征工程里用了未来信息比如用全量数据的统计量做编码或者时间字段没有按时间切分。解决所有统计类特征必须用训练集计算验证集和测试集只能做映射时间序列数据要用时间切分而不是随机切分确保训练集的时间早于测试集。4.2 SMOTE 之后不做独立验证召回率虚高 20 个点现象过采样后模型在测试集上召回率 0.9实际业务中只有 0.5。原因SMOTE 生成的合成样本和原始样本混在一起做了交叉验证导致验证集里也有合成样本。解决SMOTE 只能在训练折内部做验证折和测试折必须保持原始分布。用imblearn.pipeline.Pipeline可以把采样步骤和模型训练串在一起避免手动操作出错。4.3 阈值定在 0.5漏掉大量欺诈交易现象模型 AUC 不错但实际拦截率很低。原因默认阈值 0.5 偏向多数类欺诈概率超过 0.5 的样本极少。解决用业务代价反推阈值或者直接看召回率-精确率曲线选一个召回率满足要求且精确率可接受的切分点。通常欺诈场景的阈值会设在 0.1 到 0.3 之间。4.4 特征重要性里出现“ID 类”字段模型在背答案现象SHAP 排名第一的特征是“用户 ID”或“交易流水号”。原因这类字段取值太多树模型可以记住每个 ID 对应的标签相当于过拟合。解决训练前直接删掉 ID 类、时间戳原始值、以及任何唯一值比例超过 90% 的字段。如果业务上确实需要用户维度信息改用聚合特征比如“该用户近 7 天交易次数”。4.5 模型更新频率太低欺诈手法一变就失效现象模型上线三个月后效果明显下降。原因欺诈团伙会针对模型调整策略特征分布发生漂移。解决建立监控机制每周计算一次线上 AUC 和特征 PSI群体稳定性指标PSI 超过 0.2 就触发重新训练。重新训练时要用最近三个月的数据并保留一部分旧数据防止灾难性遗忘。5. 从课程设计到生产原型三个让方案更值钱的进阶技巧5.1 用时间序列交叉验证替代随机切分随机切分在欺诈检测里是自欺欺人因为交易数据有时序依赖。TimeSeriesSplit能保证每次训练集都在测试集之前更接近真实上线场景。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_idx, test_idx in tscv.split(X): X_train, X_test X.iloc[train_idx], X.iloc[test_idx] y_train, y_test y.iloc[train_idx], y.iloc[test_idx] # 在训练集内部做 SMOTE再训练模型 # 记录每一折的 AUC 和 AP看波动范围n_splits5表示切五折每折的训练集逐渐增大。如果五折的 AP 波动超过 0.1说明模型不稳定需要检查特征或增加数据量。5.2 把模型封装成 API用 FastAPI 做实时推理课程设计里的模型通常跑在 notebook 里但生产环境需要 API。FastAPI 轻量且自带文档适合做原型。from fastapi import FastAPI from pydantic import BaseModel import joblib app FastAPI() model joblib.load(fraud_model.pkl) class Transaction(BaseModel): amount: float hour: int merchant_freq: int is_night: int app.post(/predict) def predict(trans: Transaction): features [[trans.amount, trans.hour, trans.merchant_freq, trans.is_night]] prob model.predict_proba(features)[0][1] return {fraud_probability: round(prob, 4), is_fraud: int(prob 0.3)}这个接口接收 JSON 格式的交易特征返回欺诈概率和判定结果。阈值 0.3 是示例实际要按业务代价调整。用joblib保存模型比 pickle 更高效尤其是 XGBoost 这类模型。5.3 监控特征漂移PSI 计算与告警上线后最怕的是数据分布变了但模型没跟着变。PSI 是衡量两个分布差异的常用指标计算简单适合做日常监控。def calculate_psi(expected, actual, buckets10): 计算两个分布的 PSIexpected 是训练集分布actual 是线上分布 breakpoints np.percentile(expected, np.arange(0, 100, 100/buckets)) expected_perc np.histogram(expected, breakpoints)[0] / len(expected) actual_perc np.histogram(actual, breakpoints)[0] / len(actual) # 避免除零 expected_perc np.where(expected_perc 0, 0.0001, expected_perc) actual_perc np.where(actual_perc 0, 0.0001, actual_perc) psi np.sum((actual_perc - expected_perc) * np.log(actual_perc / expected_perc)) return psi # 对每个重要特征计算 PSI超过 0.2 就告警 for col in [amount, hour, merchant_freq]: psi calculate_psi(X_train[col], X_test[col]) print(f{col} PSI: {psi:.4f})PSI 小于 0.1 表示分布稳定0.1 到 0.2 之间需要关注超过 0.2 建议重新训练模型。这个函数用分位数做分桶避免等宽分桶在长尾分布上失效。我自己做反欺诈项目这些年最大的习惯是每次模型上线前一定手动构造一批“看起来正常但实际是欺诈”的样本去测而不是只看测试集指标。测试集是历史数据欺诈手法早就变了。希望帮到你。本文还有配套的精品资源点击获取