ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

信用卡欺诈检测实战:从数据清洗到模型优化的完整解决方案

2026/8/23 4:10:59 拓冰建站 浏览量
信用卡欺诈检测实战:从数据清洗到模型优化的完整解决方案 1. 项目概述从混乱数据到精准预测的实战之旅最近在整理过往的项目资料翻到了一个非常经典的案例——信用卡欺诈检测。这几乎是每个数据科学入门者都会接触但真正做深了又能挖出不少门道的项目。表面上看它就是一个标准的分类问题给你一堆交易记录让你判断哪笔是正常消费哪笔是盗刷。但实际干起来你会发现从拿到原始数据到最终部署一个可靠的模型中间每一步都藏着“魔鬼”。数据脏得五花八门、正负样本比例悬殊到令人绝望业内行话叫“极度不平衡”、模型选型稍有不慎就会掉进“准确率陷阱”……这些坑我几乎一个不落地都踩过。今天我就以这个“信用卡欺诈检测”项目为线把数据清洗、探索性分析、数学建模到模型比较的完整链条结合我自己的实操心得掰开揉碎了讲一遍。这不是一个简单的教程更像是一个项目复盘。我会重点分享那些在标准教科书里不会写但实际工作中能帮你节省大量时间、避免方向性错误的“野路子”和“避坑指南”。无论你是刚用Python和Pandas处理数据的新手还是想深化对不平衡分类问题理解的老手相信都能从中找到可以直接“抄作业”的干货。2. 核心思路与挑战拆解为什么欺诈检测这么难在动手写第一行代码之前我们必须先想清楚这个项目的核心目标与独特挑战。盲目套用鸢尾花数据集上的那套流程在这里会死得很惨。2.1 核心目标在噪声中寻找极少数信号信用卡欺诈检测的本质是在海量的正常交易记录占99%以上中精准地识别出极少数的欺诈交易通常不到1%。这决定了我们的一切工作都必须围绕一个中心展开如何让模型学会关注并识别那些微弱的、异常的“欺诈信号”而不是被淹没在“正常交易”的海洋里。这带来了几个直接推论模型评估指标必须换。准确率Accuracy在这里基本是废指标。一个把所有交易都预测为“正常”的傻瓜模型在99.9%正常交易的数据集上准确率也能达到99.9%但它一个欺诈都抓不到。我们必须使用精确率Precision、召回率Recall、F1-Score尤其是PR曲线Precision-Recall Curve和AUC-PR值来评估模型。召回率代表我们抓住了多少欺诈精确率代表我们抓对的里面有多少是真的欺诈两者需要权衡。数据预处理是胜负手。由于欺诈样本极少任何随机的数据划分、不当的标准化或缺失值处理都可能意外地“抹掉”或“污染”这些珍贵的欺诈样本。数据清洗和特征工程的每一步都必须小心翼翼。2.2 主要挑战极度不平衡与特征匿名化我们面对的数据集通常有两个显著特点类别极度不平衡Imbalanced Class这是最大的挑战。正样本欺诈和负样本正常的比例可能达到1:100甚至1:1000。这种不平衡会贯穿整个项目流程从数据采样策略、模型损失函数设计到最终的评估都必须专门处理。特征匿名化Anonymized Features出于隐私保护公开的信用卡欺诈数据集如Kaggle上的经典数据集的特征名通常是V1, V2, …, V28以及Time和Amount。V1-V28是经过PCA主成分分析降维后的数值特征我们无法知道其原始业务含义。这要求我们的分析必须更加依赖统计方法和模型反馈而非业务直觉。基于以上理解我们的项目技术路线图就清晰了数据清洗与探索 - 针对不平衡数据的预处理 - 多种模型建模与调参 - 基于业务场景的模型评估与选择。下面我们就一步步拆解。3. 数据清洗与探索性分析第一次亲密接触拿到数据后的第一步不是急着跑模型而是像侦探一样仔细检查你的“案发现场”。3.1 数据加载与初窥我通常会用pandas进行第一步的探查配合matplotlib和seaborn进行可视化。import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from scipy import stats import warnings warnings.filterwarnings(ignore) # 假设数据文件为‘creditcard.csv’ df pd.read_csv(creditcard.csv) print(f数据集形状: {df.shape}) print(df.info()) print(df.head())关键检查点1结构与基本信息df.shape确认样本量和特征数。经典数据集约有28万条交易31个特征Time,V1-V28,Amount,Class。df.info()查看每列的数据类型和非空值数量快速排查是否有缺失值。信用卡数据集通常很干净但养成这个习惯很重要。df.describe()特别是对Amount交易金额和Time交易时间进行描述性统计查看分布范围、中位数、均值初步发现异常值。3.2 核心清洗处理时间、金额与缺失值虽然这个数据集很干净但真实数据往往需要更多处理。1. 时间特征 (Time) 处理这个Time特征表示的是该笔交易与数据集中第一笔交易之间经过的秒数。直接使用它效果不好。我通常会将其转换为更有意义的周期特征。# 将时间秒数转换为一天内的小时数捕捉消费的周期性如深夜高风险 df[Hour] df[Time] % (24 * 3600) // 3600 # 可以进一步创建‘时段’特征如‘深夜’、‘白天’、‘傍晚’ df[Period] pd.cut(df[Hour], bins[0, 6, 12, 18, 24], labels[Night, Morning, Afternoon, Evening], include_lowestTrue) # 处理完可以删除原始Time特征避免干扰 df.drop(Time, axis1, inplaceTrue)2. 交易金额 (Amount) 处理金额的分布通常是严重右偏的大部分是小额交易少数大额交易。很多模型对特征的尺度很敏感因此需要标准化。但这里有一个大坑由于欺诈样本少我们不应该在整个数据集上做标准化StandardScaler的fit而应该只在训练集上fit然后分别转换训练集和测试集防止数据泄露。在探索阶段我们可以先观察。fig, axes plt.subplots(1, 2, figsize(12, 4)) sns.histplot(df[df[Class]0][Amount], bins50, axaxes[0], kdeTrue, colorblue) axes[0].set_title(Normal Transaction Amount) sns.histplot(df[df[Class]1][Amount], bins50, axaxes[1], kdeTrue, colorred) axes[1].set_title(Fraud Transaction Amount) plt.show()通常你会发现欺诈交易的金额分布与正常交易不同可能更集中于某些特定区间。3. 缺失值检查用df.isnull().sum()进行排查。对于匿名特征V1-V28由于是PCA结果理论上不应有缺失。如果有需要根据情况处理删除或填充。一个重要的经验如果某列缺失值比例极高如50%可以考虑直接删除该特征。如果缺失很少对于数值型特征我倾向于使用中位数填充因为中位数对异常值不敏感。3.3 探索性数据分析用可视化发现线索EDA的目标是发现特征与欺诈之间的关系为后续特征工程提供思路。1. 类别不平衡可视化这是必须做的第一步让所有人对问题的难度有直观认识。class_counts df[Class].value_counts() plt.figure(figsize(6,4)) sns.barplot(xclass_counts.index, yclass_counts.values) plt.title(Class Distribution (0: Normal, 1: Fraud)) plt.ylabel(Count) plt.show() print(f欺诈比例: {class_counts[1] / class_counts.sum() * 100:.4f}%)2. 匿名特征分布对比选取几个主要的PCA特征如V1, V2, V3分别绘制正常和欺诈交易的分布图观察是否有显著差异。features_to_plot [V1, V2, V3, V4, V10, V14] fig, axes plt.subplots(2, 3, figsize(15, 8)) axes axes.ravel() for i, col in enumerate(features_to_plot): sns.kdeplot(df[df[Class]0][col], axaxes[i], labelNormal, fillTrue) sns.kdeplot(df[df[Class]1][col], axaxes[i], labelFraud, fillTrue) axes[i].set_title(fDistribution of {col}) axes[i].legend() plt.tight_layout() plt.show()你可能会发现像V4、V14等特征两类交易的分布中心或形状有明显区别这些特征很可能就是强预测因子。3. 相关性分析计算特征之间的相关性矩阵并重点关注与目标变量Class相关性最高的特征。# 计算相关性并筛选出与‘Class’相关性最高的特征 corr_with_class df.corr()[Class].sort_values(ascendingFalse) # 查看与欺诈最正相关和最负相关的特征 print(Top 10 features positively correlated with Fraud:) print(corr_with_class.head(10)) print(\nTop 10 features negatively correlated with Fraud:) print(corr_with_class.tail(10)) # 绘制热图可选特征多时可能看不清 plt.figure(figsize(10, 8)) # 可以选择与Class相关性高的前15个特征来画热图 top_features corr_with_class.abs().sort_values(ascendingFalse).head(15).index top_corr df[top_features].corr() sns.heatmap(top_corr, annotTrue, fmt.2f, cmapcoolwarm, center0) plt.title(Correlation Heatmap of Top Features Related to Class) plt.show()实操心得在EDA阶段我强烈建议将可视化结果保存下来。这不只是为了报告更是为了在后续模型效果不佳时可以回头审视这些图形检查是否忽略了某些明显的模式或异常。例如如果发现某个特征在欺诈和正常交易中的分布几乎完全重叠那么在后续特征选择中就可以考虑降低其权重或剔除。4. 数据预处理与特征工程为不平衡学习铺路清洗和探索完成后我们需要将数据准备好喂给模型。这个阶段的核心思想是为模型创造公平的“学习环境”。4.1 训练集与测试集划分严守数据泄露红线这是建模前最重要的一步必须先划分再处理。绝对不能先对整体数据做标准化或过采样再划分这会导致测试集信息“泄露”到训练过程中使评估结果虚高模型在实际中失效。from sklearn.model_selection import train_test_split # 首先分离特征X和目标y X df.drop(Class, axis1) y df[Class] # 使用stratify参数确保训练集和测试集中正负样本的比例保持一致 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42, stratifyy) print(f训练集形状: {X_train.shape}, 测试集形状: {X_test.shape}) print(f训练集欺诈比例: {y_train.mean():.4%}) print(f测试集欺诈比例: {y_test.mean():.4%})4.2 特征缩放谨慎处理数值特征我们需要对数值特征进行缩放特别是Amount和Hour这类量纲与其他PCA特征不同的。记住缩放器只从训练集学习参数。from sklearn.preprocessing import StandardScaler, RobustScaler # 对于金额Amount由于其可能包含异常值使用RobustScaler基于中位数和四分位数比StandardScaler基于均值方差更稳健。 scaler_robust RobustScaler() # 对于其他PCA特征使用StandardScaler即可 scaler_standard StandardScaler() # 分别对训练集进行拟合和转换 X_train[Amount_scaled] scaler_robust.fit_transform(X_train[[Amount]]) X_train[Hour_scaled] scaler_standard.fit_transform(X_train[[Hour]]) # 对PCA特征V1-V28进行标准化 pca_columns [col for col in X_train.columns if col.startswith(V)] X_train[pca_columns] scaler_standard.fit_transform(X_train[pca_columns]) # 对测试集进行转换使用训练集学到的参数 X_test[Amount_scaled] scaler_robust.transform(X_test[[Amount]]) X_test[Hour_scaled] scaler_standard.transform(X_test[[Hour]]) X_test[pca_columns] scaler_standard.transform(X_test[pca_columns]) # 删除原始的Amount和Hour列避免重复 X_train.drop([Amount, Hour], axis1, inplaceTrue) X_test.drop([Amount, Hour], axis1, inplaceTrue)4.3 解决类别不平衡采样策略的选择这是欺诈检测项目的核心环节。我们有以下几种主流策略1. 过采样Oversampling增加少数类样本。最常用的是SMOTE合成少数类过采样技术它通过在已有的少数类样本之间插值来创造新的“合成”样本。from imblearn.over_sampling import SMOTE smote SMOTE(random_state42) X_train_resampled, y_train_resampled smote.fit_resample(X_train, y_train) print(f过采样后训练集形状: {X_train_resampled.shape}) print(f过采样后类别分布:\n{y_train_resampled.value_counts()})2. 欠采样Undersampling减少多数类样本。如RandomUnderSampler随机删除多数类样本或NearMiss等更智能的方法。缺点是会丢失大量信息。3. 类别权重Class Weight在模型训练时给少数类样本更高的惩罚权重。很多模型如逻辑回归、SVM、决策树都支持class_weight参数设置为‘balanced’即可自动计算。我的经验与选择首选尝试类别权重因为它不改变原始数据分布计算效率高且通常效果不错。这是我最先推荐的方法。SMOTE 欠采样组合对于极端不平衡的数据可以先用SMOTE将少数类增加到一定比例如1:10再用随机欠采样将多数类减少到相同数量。imblearn中的SMOTEENN或SMOTETomek就是这类组合方法。不要对测试集做任何采样测试集必须保持原始的真实分布这是我们评估模型在现实世界中表现的唯一依据。进行交叉验证时采样要在每一折内部进行使用imblearn提供的Pipeline与sklearn的cross_val_score结合确保采样只在训练折叠中进行验证折叠保持原始分布。在本项目中为了演示多种技术我们会在不同模型上尝试不同策略并进行比较。5. 数学建模与模型训练多管齐下择优而用没有哪个模型是银弹。我们的策略是快速训练多个基线模型选出有潜力的再进行精细调优。5.1 基线模型建立与快速评估我们先不进行复杂的调参用默认参数快速跑几个经典模型看看它们在未解决不平衡问题时的原始表现。我们使用**PR曲线下的面积AUC-PR**作为核心评估指标因为它对类别不平衡不敏感比ROC-AUC更适合本例。from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier from sklearn.svm import SVC from sklearn.metrics import precision_recall_curve, auc, average_precision_score, classification_report # 初始化模型 models { Logistic Regression: LogisticRegression(max_iter1000, random_state42), Random Forest: RandomForestClassifier(n_estimators100, random_state42, n_jobs-1), XGBoost: XGBClassifier(use_label_encoderFalse, eval_metriclogloss, random_state42), SVM: SVC(probabilityTrue, random_state42) # 需要probabilityTrue才能画PR曲线 } # 存储结果 results {} for name, model in models.items(): # 训练 model.fit(X_train, y_train) # 预测概率 y_pred_proba model.predict_proba(X_test)[:, 1] # 计算Average Precision (AP)即AUC-PR的一个近似 ap_score average_precision_score(y_test, y_pred_proba) precision, recall, _ precision_recall_curve(y_test, y_pred_proba) auc_pr auc(recall, precision) # 计算精确的AUC-PR results[name] {AP: ap_score, AUC-PR: auc_pr} # 打印分类报告选择一个阈值如0.5 y_pred (y_pred_proba 0.5).astype(int) print(f\n{name} Classification Report:) print(classification_report(y_test, y_pred, target_names[Normal, Fraud])) # 比较结果 results_df pd.DataFrame(results).T print(\n 基线模型 AUC-PR 比较 ) print(results_df.sort_values(AUC-PR, ascendingFalse))通常像随机森林、XGBoost这类集成树模型在基线测试中会表现较好。5.2 针对不平衡数据的模型优化现在我们对有潜力的模型如随机森林和XGBoost应用不同的不平衡处理策略并进行简单的网格搜索调参。示例优化XGBoost模型XGBoost内置了处理不平衡的参数scale_pos_weight其值通常设置为负样本数 / 正样本数。from sklearn.model_selection import GridSearchCV # 计算scale_pos_weight scale_pos_weight len(y_train[y_train0]) / len(y_train[y_train1]) # 定义参数网格 param_grid { max_depth: [3, 5, 7], learning_rate: [0.01, 0.1], subsample: [0.8, 1.0], colsample_bytree: [0.8, 1.0], scale_pos_weight: [1, scale_pos_weight] # 对比使用和不使用权重的效果 } xgb XGBClassifier(use_label_encoderFalse, eval_metriclogloss, random_state42, n_jobs-1) # 使用AUC-PR作为评分标准 grid_search GridSearchCV(estimatorxgb, param_gridparam_grid, scoringaverage_precision, cv3, verbose1, n_jobs-1) grid_search.fit(X_train, y_train) print(f最佳参数: {grid_search.best_params_}) print(f最佳交叉验证 AP 分数: {grid_search.best_score_:.4f}) # 用最佳模型在测试集上评估 best_xgb grid_search.best_estimator_ y_pred_proba_xgb best_xgb.predict_proba(X_test)[:, 1] ap_xgb average_precision_score(y_test, y_pred_proba_xgb) print(f测试集 AP 分数: {ap_xgb:.4f})示例使用SMOTE随机森林from imblearn.pipeline import Pipeline from sklearn.model_selection import cross_val_score # 创建一个包含SMOTE和随机森林的Pipeline pipeline Pipeline([ (smote, SMOTE(random_state42)), (rf, RandomForestClassifier(n_estimators100, random_state42, n_jobs-1)) ]) # 使用交叉验证评估Pipeline注意评分指标 cv_scores cross_val_score(pipeline, X_train, y_train, scoringaverage_precision, cv3, n_jobs-1) print(fSMOTE RandomForest 交叉验证 AP 分数: {cv_scores.mean():.4f} (/- {cv_scores.std()*2:.4f})) # 在整个训练集上拟合并在测试集上评估 pipeline.fit(X_train, y_train) y_pred_proba_rf_smote pipeline.predict_proba(X_test)[:, 1] ap_rf_smote average_precision_score(y_test, y_pred_proba_rf_smote) print(f测试集 AP 分数: {ap_rf_smote:.4f})5.3 模型解释理解模型为何做出判断对于像欺诈检测这样的高风险应用模型的可解释性至关重要。我们不能只相信一个“黑箱”。特征重要性Feature Importance树模型如随机森林、XGBoost可以直接输出特征重要性。# 获取XGBoost模型的特征重要性 feature_importance best_xgb.feature_importances_ features X_train.columns importance_df pd.DataFrame({feature: features, importance: feature_importance}).sort_values(importance, ascendingFalse) plt.figure(figsize(10,6)) sns.barplot(dataimportance_df.head(15), ximportance, yfeature) plt.title(Top 15 Feature Importance (XGBoost)) plt.tight_layout() plt.show()SHAP值SHapley Additive exPlanations这是目前最强大的模型解释工具之一可以解释每一个样本的每一个预测是哪些特征、以何种程度贡献的。import shap # 计算SHAP值这可能需要一些时间 explainer shap.TreeExplainer(best_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,:])通过SHAP分析你可能会发现对于欺诈交易V14、V4的负向贡献很大而V10的正向贡献很大这与我们之前EDA中观察到的分布差异相互印证。6. 模型评估、比较与阈值选择模型训练好之后我们需要在独立的测试集上进行最终评估和比较。6.1 综合评估指标对比我们不能只看一个指标。一个好的做法是创建一个综合对比表格from sklearn.metrics import precision_score, recall_score, f1_score, roc_auc_score final_models { XGBoost (weighted): best_xgb, RandomForest (SMOTE): pipeline.named_steps[rf] # 注意这里取的是Pipeline中的模型 # ... 可以加入其他优化后的模型 } comparison [] for name, model in final_models.items(): if name RandomForest (SMOTE): # 对于Pipeline训练的模型需要用整个pipeline预测 y_pred_proba pipeline.predict_proba(X_test)[:, 1] else: y_pred_proba model.predict_proba(X_test)[:, 1] y_pred (y_pred_proba 0.5).astype(int) # 暂用0.5阈值 ap average_precision_score(y_test, y_pred_proba) roc_auc roc_auc_score(y_test, y_pred_proba) precision precision_score(y_test, y_pred) recall recall_score(y_test, y_pred) f1 f1_score(y_test, y_pred) comparison.append({ Model: name, AUC-PR: ap, ROC-AUC: roc_auc, Precision: precision, Recall: recall, F1-Score: f1 }) comparison_df pd.DataFrame(comparison).set_index(Model) print(comparison_df.sort_values(AUC-PR, ascendingFalse))6.2 关键根据业务需求调整决策阈值模型输出的通常是欺诈概率0到1之间。我们默认用0.5作为阈值来划分正负类。但在欺诈检测中0.5往往不是最优选择。如果业务追求高召回率宁可错杀不可放过例如在风险控制非常严格的场景希望尽可能抓住所有欺诈可以接受一些误报。这时应降低阈值如0.3让模型更“敏感”。如果业务追求高精确率减少误报提升用户体验例如不希望频繁打扰正常用户进行验证。这时应提高阈值如0.7让模型更“保守”。我们可以通过**精确率-召回率曲线PR Curve**来可视化这种权衡并选择最佳阈值。from sklearn.metrics import precision_recall_curve # 以表现最好的模型为例 y_pred_proba_best best_xgb.predict_proba(X_test)[:, 1] precisions, recalls, thresholds precision_recall_curve(y_test, y_pred_proba_best) # 绘制PR曲线 plt.figure(figsize(8,6)) plt.plot(recalls, precisions, marker.) plt.xlabel(Recall) plt.ylabel(Precision) plt.title(Precision-Recall Curve) plt.grid(True) # 找到一个平衡点例如让精确率和召回率都相对较高的点 # 也可以计算F1分数最大的点 f1_scores 2 * (precisions * recalls) / (precisions recalls 1e-7) # 避免除零 best_idx np.argmax(f1_scores) best_threshold thresholds[best_idx] if best_idx len(thresholds) else thresholds[-1] best_precision precisions[best_idx] best_recall recalls[best_idx] plt.plot(best_recall, best_precision, ro, markersize10, labelfBest F1 Threshold{best_threshold:.2f}) plt.legend() plt.show() print(f建议阈值: {best_threshold:.3f}) print(f在该阈值下 - 精确率: {best_precision:.3f}, 召回率: {best_recall:.3f}, F1: {f1_scores[best_idx]:.3f}) # 使用新阈值进行预测 y_pred_adjusted (y_pred_proba_best best_threshold).astype(int) print(\n调整阈值后的分类报告:) print(classification_report(y_test, y_pred_adjusted, target_names[Normal, Fraud]))6.3 常见问题与排查技巧实录在实际操作中你肯定会遇到各种问题。这里记录几个我踩过的坑和解决方法问题1模型召回率始终很低抓不到欺诈。排查首先检查训练集和测试集的欺诈样本比例是否因划分而差异巨大使用stratify可避免。然后检查是否在划分前错误地对整体数据进行了标准化或采样。解决确保使用StratifiedKFold进行交叉验证。尝试不同的采样策略组合如SMOTEENN。为树模型如XGBoost, LightGBM设置更大的scale_pos_weight比如2倍于理论值。使用专门为不平衡数据设计的算法如CatBoost它默认能很好地处理不平衡或使用Isolation Forest、One-Class SVM等异常检测算法将欺诈视为异常点。问题2模型过拟合训练集AUC-PR很高测试集很低。排查检查模型复杂度是否过高如树模型max_depth太大n_estimators太多。解决增加正则化参数如XGBoost的reg_alpha,reg_lambda随机森林的min_samples_leaf。使用更严格的早停法Early Stopping。减少特征数量或使用PCA进一步降维但需谨慎可能丢失信息。问题3SHAP分析发现某个特征重要性异常高但业务上说不通。排查这可能是数据泄露的迹象检查这个特征是否在某种程度上直接或间接包含了“是否欺诈”的信息。例如如果有一个特征是“交易是否被标记为可疑”那这就是典型的标签泄露。解决彻底审查数据字典和特征生成逻辑移除任何可能泄露未来信息或标签信息的特征。问题4模型上线后效果衰减。原因数据分布随时间发生变化即概念漂移。欺诈模式不是一成不变的。解决建立模型监控体系定期如每周计算模型在最新数据上的性能指标如精确率、召回率。设置性能下降警报。规划模型定期如每月或每季度的重新训练流程。最后我个人在这个项目中的体会是信用卡欺诈检测是一个需要持续迭代和业务结合的工程。没有一个一劳永逸的模型。最好的策略是建立一个从数据监控、特征更新、模型训练到效果评估的自动化管道并将模型的预测结果与风控专家的经验规则相结合形成“人机协同”的最终决策系统。例如模型可以给出一个高风险评分再由规则系统如“短时间内多国交易”进行二次过滤最后由人工审核极高风险的案例。这样既能发挥模型的广度覆盖能力又能利用规则和人的深度判断力在控制风险和保障用户体验之间找到最佳平衡点。