Python实战:SMOTE、Borderline SMOTE与ADASYN在不平衡金融风控数据中的应用对比
1. 金融风控中的“数据失衡”困局为什么你的模型总抓不住“坏人”在金融风控和反欺诈领域我踩过最大的坑不是模型不够复杂而是数据本身“不公平”。想象一下你手头有10万笔信用卡交易记录其中只有100笔是欺诈交易比例是1000:1。如果你直接用这些数据去训练一个模型会发生什么模型很快就会发现一个“捷径”只要把所有交易都预测为“正常”它的准确率就能轻松达到99.9%这个数字看起来很美对吧但这样的模型对我们来说价值是零因为它一个“坏人”都抓不到。这就是类别不平衡问题的典型场景。在金融数据里欺诈、违约、坏账这些我们真正关心的“少数类”事件往往只占极小的比例。模型在训练时会被海量的“多数类”正常交易样本所主导从而“偷懒”地学会了忽视少数类。最终模型在测试集上可能表现尚可但一旦上线对真正的风险事件却毫无识别能力造成巨大的业务损失。面对这种困境我们通常有几种思路一是从算法层面入手比如给少数类样本更高的误分类惩罚代价敏感学习二是从数据层面入手改变数据分布。今天我们不谈复杂的算法调参就聚焦在数据层面一个经典且强大的工具箱过采样技术。它的核心思想不是“削足适履”地扔掉大部分正常样本欠采样而是“无中生有”地创造出更多“坏人”的样本让模型能更清晰地看到“坏人”长什么样。在众多过采样方法中SMOTE、Borderline SMOTE和ADASYN是三位最常被提及的“明星选手”。它们都基于同一个理念——合成新的少数类样本但各自的“合成策略”却大相径庭直接影响了最终模型的表现。这篇文章我就结合自己多年在金融风控项目中的实战经验用Python带大家亲手试一试这三位选手看看在真实的欺诈检测任务中谁才是真正的“风控高手”。我会用最通俗的语言解释它们的原理用最直观的代码展示效果并告诉你在不同的业务场景下我通常会怎么选。2. 三位“造物主”SMOTE、Borderline SMOTE与ADASYN原理大拆解在开始写代码之前我们必须先搞清楚这三位“造物主”创造新样本的“底层逻辑”。理解了这个你才能明白为什么有时候A方法好用B方法却会“帮倒忙”。2.1 SMOTE雨露均沾的“平均主义者”SMOTE合成少数类过采样技术是最基础的版本它的思路非常直接。对于每一个少数类样本比如一个欺诈交易记录它这样做找朋友找到这个样本在特征空间里的K个最近的“邻居”同样是少数类样本。随机连线从这K个邻居中随机挑选一个。在线段上“生孩子”在这两个样本点原始样本和选中的邻居的连线上随机选取一个点这个点就是新合成的样本。用数学公式表示就是新样本 原始样本 随机数 * (邻居样本 - 原始样本)。它的优点很明显实现简单能有效增加少数类样本的多样性避免了简单复制样本带来的过拟合风险。但它的缺点在风控场景下可能是致命的SMOTE是个“老好人”它对所有少数类样本一视同仁。这意味着对于那些深陷在“敌营”被多数类样本包围的孤立欺诈点或者那些身处“安全区”被同类样本包围的欺诈点SMOTE都会不假思索地给它们“造孩子”。前者造出的新样本很可能和周围的正常样本混在一起模糊了分类边界后者造出的新样本则对定义分类边界没什么帮助。我经常把这个问题叫做“样本混叠”或“边界模糊”。2.2 Borderline SMOTE聚焦前沿的“边界守卫者”Borderline SMOTE意识到了SMOTE的缺陷它认为真正对模型分类边界有贡献的是那些处在“危险边缘”的少数类样本。因此它引入了一个聪明的筛选机制给样本“贴标签”对于每个少数类样本检查它的K个最近邻这次邻居可以是任何类别。安全样本如果超过一半的邻居是少数类说明它身处“安全区”暂时不管。危险样本如果超过一半的邻居是多数类说明它处在分类边界上是“前线战士”。噪声样本如果所有邻居都是多数类那它很可能是个异常点或噪声直接忽略。精准扶持只对那些被标记为“危险”的边界样本使用SMOTE的方法来合成新样本。这样做的好处是它把有限的“造样本”资源集中投放在了最能改善决策边界的地方。生成的样本更有可能落在两类样本的交界处从而让模型学习到的边界更加清晰、锐利。在风控中那些难以区分的、处在“灰色地带”的交易正是我们最需要模型去精准把握的。Borderline SMOTE还有两个变体Borderline-1 SMOTE在合成时只从少数类邻居中选点而Borderline-2 SMOTE则从所有类别的邻居中选点后者生成的样本可能更具挑战性有时能进一步推动边界向多数类区域扩张。2.3 ADASYN按需分配的“自适应调度员”ADASYN自适应合成采样的思路又进了一步。它认为不同位置的少数类样本“困难程度”是不一样的。一个被众多正常交易包围的欺诈样本比一个周围有很多同类欺诈的样本更难被正确分类因此更需要被“援助”。它的工作流程是计算总需求先算出总共需要合成多少少数类样本以达到我们想要的平衡比例比如1:1。评估个体难度对于每一个少数类样本计算其K近邻中多数类样本的比例。比例越高说明它越“困难”所处的区域密度越低。分配合成任务根据每个样本的“困难程度”权重来决定需要为它合成多少个新样本。越困难的样本分配到的合成名额就越多。执行合成对每个样本按分配到的名额使用类似SMOTE的方法生成新样本。ADASYN的核心优势在于其自适应性。它能够自动地将过采样的重点集中在那些难以学习的少数类样本区域从而在模型学习时对这些“硬骨头”区域给予更多关注。这在欺诈模式多样、且某些新型欺诈样本极少的情况下可能特别有用。为了让你更直观地理解三者的区别我画了一个简单的思维对比图特性SMOTEBorderline SMOTEADASYN核心思想对所有少数类样本均匀过采样只对处于分类边界附近的少数类样本过采样根据样本的学习难度自适应分配过采样数量关注点样本数量平衡决策边界清晰度少数类样本密度分布优点简单增加样本多样性生成的样本更有助于定义边界可能提升模型泛化能力能聚焦于难以学习的区域对复杂分布友好潜在缺点可能产生边界模糊的噪声样本可能过度关注边界忽视内部“安全”样本的信息对噪声敏感可能过度放大噪声区域风控场景适用性适用于少数类分布相对均匀、边界清晰度要求不极端的情况非常适用于风控因为风控核心就是精准界定“正常”与“欺诈”的边界适用于欺诈模式复杂、少数类内部分布也不均匀的情况3. 实战演练用Python在金融欺诈数据集上“比武”理论说得再多不如一行代码。接下来我们用一个模拟的金融交易数据集来真刀真枪地比一比。我们会使用imbalanced-learn这个专门处理不平衡数据的Python库它已经完美集成了我们今天要对比的三种方法。3.1 环境准备与数据生成首先确保你的环境里安装了必要的库。打开你的终端或Jupyter Notebook执行以下命令pip install imbalanced-learn scikit-learn matplotlib pandas numpy然后我们创建一个高度不平衡的二维数据集来模拟金融风控场景。二维数据的好处是我们可以直接可视化直观地看到过采样前后的数据分布变化。# 导入必要的库 import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_classification from collections import Counter # 生成一个不平衡数据集 # n_samples: 总样本数 # weights: [少数类权重 多数类权重]这里设置1:9的极度不平衡 # n_features: 特征数设为2以便可视化 # n_clusters_per_class: 每个类别有几个簇模拟不同欺诈模式 # class_sep: 类别分离程度值越小越难分 # random_state: 随机种子确保结果可复现 X, y make_classification(n_samples1000, n_features2, n_informative2, n_redundant0, n_repeated0, n_classes2, n_clusters_per_class1, weights[0.1, 0.9], class_sep1.5, flip_y0.05, random_state42) # 查看原始数据分布 print(f原始数据集类别分布: {Counter(y)}) # 输出类似原始数据集类别分布: Counter({1: 900, 0: 100}) # 这里我们将标签1视为多数类正常交易标签0视为少数类欺诈交易 # 可视化原始数据 plt.figure(figsize(6, 6)) plt.scatter(X[y 1, 0], X[y 1, 1], clightblue, edgecolork, s20, label多数类 (正常), alpha0.6) plt.scatter(X[y 0, 0], X[y 0, 1], cred, edgecolork, s40, label少数类 (欺诈)) plt.title(原始不平衡数据集分布) plt.xlabel(特征 1 (例如交易金额标准化值)) plt.ylabel(特征 2 (例如交易时间频率)) plt.legend() plt.grid(True, alpha0.3) plt.show()运行这段代码你会看到一张散点图。图中蓝色点多数类正常交易密密麻麻而红色点少数类欺诈交易稀稀拉拉形象地展示了我们面临的数据失衡问题。3.2 三大过采样算法代码实现与可视化对比现在让我们分别请出三位“造物主”看看它们是如何改造这个数据世界的。from imblearn.over_sampling import SMOTE, BorderlineSMOTE, ADASYN # 初始化三种过采样器 # 注意为了公平对比我们使用相同的随机种子(random_state)和近邻数(k_neighbors) smote SMOTE(random_state42, k_neighbors5) borderline_smote BorderlineSMOTE(random_state42, k_neighbors5, kindborderline-1) adasyn ADASYN(random_state42, n_neighbors5) # 应用过采样 X_resampled_smote, y_resampled_smote smote.fit_resample(X, y) X_resampled_border, y_resampled_border borderline_smote.fit_resample(X, y) X_resampled_ada, y_resampled_ada adasyn.fit_resample(X, y) # 打印过采样后的数据分布 print(fSMOTE 处理后类别分布: {Counter(y_resampled_smote)}) print(fBorderline SMOTE 处理后类别分布: {Counter(y_resampled_border)}) print(fADASYN 处理后类别分布: {Counter(y_resampled_ada)}) # 输出结果应该都是 Counter({0: 900, 1: 900})达到了1:1的平衡 # 将结果可视化进行对比 fig, axes plt.subplots(2, 2, figsize(14, 12)) # 原始数据 axes[0, 0].scatter(X[y 1, 0], X[y 1, 1], clightblue, edgecolork, s20, alpha0.6, label正常) axes[0, 0].scatter(X[y 0, 0], X[y 0, 1], cred, edgecolork, s40, label欺诈) axes[0, 0].set_title(原始数据 (欺诈:正常 1:9)) axes[0, 0].legend() axes[0, 0].grid(True, alpha0.3) # SMOTE 结果 axes[0, 1].scatter(X_resampled_smote[y_resampled_smote 1, 0], X_resampled_smote[y_resampled_smote 1, 1], clightblue, edgecolork, s20, alpha0.6, label正常) axes[0, 1].scatter(X_resampled_smote[y_resampled_smote 0, 0], X_resampled_smote[y_resampled_smote 0, 1], cred, edgecolork, s40, label欺诈 (合成)) axes[0, 1].set_title(SMOTE 过采样后) axes[0, 1].legend() axes[0, 1].grid(True, alpha0.3) # Borderline SMOTE 结果 axes[1, 0].scatter(X_resampled_border[y_resampled_border 1, 0], X_resampled_border[y_resampled_border 1, 1], clightblue, edgecolork, s20, alpha0.6, label正常) axes[1, 0].scatter(X_resampled_border[y_resampled_border 0, 0], X_resampled_border[y_resampled_border 0, 1], cred, edgecolork, s40, label欺诈 (合成)) axes[1, 0].set_title(Borderline SMOTE 过采样后) axes[1, 0].legend() axes[1, 0].grid(True, alpha0.3) # ADASYN 结果 axes[1, 1].scatter(X_resampled_ada[y_resampled_ada 1, 0], X_resampled_ada[y_resampled_ada 1, 1], clightblue, edgecolork, s20, alpha0.6, label正常) axes[1, 1].scatter(X_resampled_ada[y_resampled_ada 0, 0], X_resampled_ada[y_resampled_ada 0, 1], cred, edgecolork, s40, label欺诈 (合成)) axes[1, 1].set_title(ADASYN 过采样后) axes[1, 1].legend() axes[1, 1].grid(True, alpha0.3) plt.tight_layout() plt.show()仔细对比这四张图你会发现非常有趣的现象SMOTE生成的红色欺诈点合成样本均匀地散布在原始红色点的周围形成了一些小的“簇”。但注意看有些新生成的点已经“侵入”到了蓝色正常点的密集区域这就是我之前提到的“边界模糊”风险。Borderline SMOTE生成的欺诈点明显更加“克制”和“聚焦”。它们几乎只出现在原始红点和蓝点的交界地带有效地“拓宽”和“锐化”了分类边界。这正是我们希望模型重点学习的区域。ADASYN生成的欺诈点分布看起来与SMOTE有些相似但如果你仔细观察在原始少数类点更稀疏、更靠近多数类的区域ADASYN生成的合成点似乎更密集一些。这体现了它“按难度分配”的特性在“敌情”更复杂的区域投入了更多兵力。可视化给了我们一个直观的印象但模型到底喜欢哪一种“造出来”的数据呢我们需要用模型的表现来说话。4. 模型效果PK混淆矩阵与AUC-ROC曲线见真章接下来我们要用一个分类模型比如逻辑回归来测试这三种过采样技术到底谁更胜一筹。我们会将数据集分成训练集和测试集关键一步是过采样只应用在训练集上测试集必须保持原始的不平衡分布这样才能真实评估模型在现实中的表现。我们将使用两个非常重要的评估工具混淆矩阵直接告诉我们模型抓住了多少欺诈召回率以及抓得准不准精确率。在风控中我们通常更看重召回率Recall因为漏掉一个欺诈漏报的成本远高于误判一个正常交易误报的成本。AUC-ROC曲线这是一个综合性的指标它衡量的是模型将“欺诈”和“正常”区分开来的整体能力。AUC值越接近1说明模型性能越好。它不受分类阈值的影响非常适合评估不平衡数据上的分类器。from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import confusion_matrix, classification_report, roc_auc_score, roc_curve import seaborn as sns # 1. 分割数据集保持原始分布 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.3, random_state42, stratifyy) print(f训练集分布: {Counter(y_train)}) print(f测试集分布: {Counter(y_test)}) # 2. 定义评估函数 def evaluate_model(X_train_res, y_train_res, model_name): # 训练模型 model LogisticRegression(max_iter1000, random_state42) model.fit(X_train_res, y_train_res) # 在测试集上预测 y_pred model.predict(X_test) y_pred_proba model.predict_proba(X_test)[:, 1] # 预测为欺诈的概率 # 计算评估指标 auc roc_auc_score(y_test, y_pred_proba) cm confusion_matrix(y_test, y_pred) # 打印报告 print(f\n{*50}) print(f模型: {model_name}) print(fAUC-ROC 分数: {auc:.4f}) print(混淆矩阵:) print(cm) print(\n分类报告:) # 注意这里将标签0欺诈视为正类 print(classification_report(y_test, y_pred, target_names[欺诈 (0), 正常 (1)], zero_division0)) # 返回AUC和预测概率用于后续画图 return auc, y_pred_proba, model # 3. 基准模型不使用任何过采样 print(\n【基准模型 - 原始不平衡训练集】) auc_base, y_proba_base, model_base evaluate_model(X_train, y_train, 原始数据 (基准)) # 4. 分别用三种过采样方法处理训练集并评估 # SMOTE X_train_smote, y_train_smote smote.fit_resample(X_train, y_train) print(\n【使用 SMOTE 过采样】) auc_smote, y_proba_smote, model_smote evaluate_model(X_train_smote, y_train_smote, SMOTE) # Borderline SMOTE X_train_border, y_train_border borderline_smote.fit_resample(X_train, y_train) print(\n【使用 Borderline SMOTE 过采样】) auc_border, y_proba_border, model_border evaluate_model(X_train_border, y_train_border, Borderline SMOTE) # ADASYN X_train_ada, y_train_ada adasyn.fit_resample(X_train, y_train) print(\n【使用 ADASYN 过采样】) auc_ada, y_proba_ada, model_ada evaluate_model(X_train_ada, y_train_ada, ADASYN) # 5. 绘制ROC曲线对比 plt.figure(figsize(10, 8)) models [(基准, y_proba_base, auc_base), (SMOTE, y_proba_smote, auc_smote), (Borderline SMOTE, y_proba_border, auc_border), (ADASYN, y_proba_ada, auc_ada)] for name, y_score, auc_val in models: fpr, tpr, _ roc_curve(y_test, y_score) plt.plot(fpr, tpr, lw2, labelf{name} (AUC {auc_val:.3f})) plt.plot([0, 1], [0, 1], colornavy, lw1, linestyle--, label随机猜测 (AUC0.5)) plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel(假正率 (False Positive Rate) - 误报率, fontsize12) plt.ylabel(真正率 (True Positive Rate) - 召回率, fontsize12) plt.title(不同过采样方法下的模型ROC曲线对比, fontsize14) plt.legend(loclower right) plt.grid(True, alpha0.3) plt.show()运行这段代码后你会看到四份详细的分类报告和一张ROC曲线对比图。从我的多次实验经验来看通常会出现以下情况基准模型对欺诈类0的召回率Recall通常会非常低可能只有10%-30%因为模型根本“懒得”去预测欺诈。但精确率Precision可能看起来很高这是因为模型预测的欺诈样本极少一旦预测蒙对的概率不低但这毫无意义。SMOTE模型欺诈类的召回率会有显著提升可能达到60%-80%但精确率往往会下降。这是因为SMOTE生成的样本可能包含一些模糊边界的点导致模型将更多正常样本也误判为欺诈假正例增加。Borderline SMOTE模型在风控场景下它常常是表现最均衡或最优的。它的召回率提升与SMOTE相当甚至更好同时能更好地保持较高的精确率。这意味着它能抓住更多欺诈同时不会产生过多的误报AUC值也往往是最高或接近最高的。ADASYN模型表现可能与SMOTE类似有时在特定数据集上由于其自适应性对少数类中更“难”的样本学习更好召回率可能略有优势但同样面临精确率下降的风险。重点看混淆矩阵和召回率在输出中混淆矩阵的格式通常是[[TN, FP], [FN, TP]]。对于欺诈检测我们最关心的是第二行第一列FN假负例即漏掉的欺诈要尽可能小这对应着高召回率。同时第一行第二列FP假正例即误伤的正常交易也要控制住这对应着高精确率。Borderline SMOTE通常能在这两者间取得更好的平衡。5. 深入分析与实战建议如何为你的风控项目选择最佳方案看到这里你可能会问“所以我无脑选Borderline SMOTE就行了吗” 事情没那么简单。在实际项目中选择哪种方法甚至要不要用过采样都需要结合具体业务和数据特性来决策。5.1 理解模型评估指标的“业务权重”在金融风控中不同的指标有着不同的业务含义和成本召回率抓住了多少比例的欺诈交易。召回率低意味着漏报多直接造成资金损失。精确率我们认定的欺诈交易中有多少是真的。精确率低意味着误报多会导致客户体验下降正常交易被拦截、客服成本增加。AUC-ROC模型整体的排序能力即模型将欺诈交易排在正常交易前面的概率。没有绝对的最优解只有最适合当前业务目标的权衡。如果你的业务对欺诈“零容忍”愿意承受较高的误报成本比如某些高净值客户的风控那么可以优先选择召回率最高的方法可能是ADASYN或SMOTE。如果你的业务误报成本极高比如担心影响大量正常用户那么就应该选择精确率更高的方法Borderline SMOTE往往更有优势。ROC曲线可以帮助你选择合适的工作点阈值。5.2 高级技巧与组合策略单一的方法可能还不够在实践中我经常会将过采样与其他技术结合使用过采样 欠采样组合例如SMOTEENN或SMOTETomek。这些方法先使用SMOTE过采样然后使用ENN或Tomek Links等欠采样技术来清理可能产生的噪声样本或重叠样本。这能在增加少数类的同时净化多数类边界有时能取得比单一方法更好的效果。在imblearn中你可以直接使用SMOTEENN或SMOTETomek组合采样器。调整分类阈值过采样后模型预测的概率分布可能会发生变化。默认的0.5阈值可能不再是最优的。你可以根据业务成本在验证集上通过PR曲线或成本曲线来寻找最佳阈值。例如将阈值从0.5降低到0.3可能会大幅提升召回率抓住更多欺诈但会牺牲一些精确率。使用集成方法如EasyEnsemble或BalanceCascade。这些方法通过集成多个欠采样子集上的模型来解决问题尤其适用于极端不平衡的场景。它们本质上是一种智能的欠采样但效果常常能与优秀的过采样方法媲美。5.3 实战选择指南与避坑提醒根据我的经验为你总结一个快速选择指南初步尝试优先从 Borderline SMOTE 开始。它在大多数风控场景下能提供一个稳健的基线在提升召回率和控制精确率之间做得比较好。数据质量高边界清晰如果少数类样本本身质量很高分布相对紧凑可以试试标准的SMOTE它简单快速。欺诈模式复杂内部差异大如果欺诈行为本身就有很多子类型簇且不同子类型的“可区分难度”不同可以尝试ADASYN让它自适应地关注困难区域。担心生成噪声如果数据中可能存在噪声或者过采样后模型过拟合迹象明显一定要尝试组合方法如SMOTEENN。样本极度稀缺当少数类样本数量少到连K近邻都找不齐时比如少于5个所有基于近邻的过采样方法都会失效。这时需要考虑基于模型的方法如数据增强、代价敏感学习或者寻求更多数据。最后几个重要的提醒一定要在训练集上做采样这是铁律千万不能在完整数据集上采样后再分割否则会导致数据泄露评估结果会严重失真。交叉验证的姿势要对在使用交叉验证时过采样步骤必须放在每一次训练折叠的内部进行而不是在交叉验证之前。可以使用imblearn的Pipeline结合sklearn的cross_val_score来实现。过采样不是银弹它只是解决不平衡问题的工具之一。特征工程、选择合适的模型如树模型通常对不平衡更鲁棒、调整类别权重如class_weightbalanced同样重要甚至更重要。业务理解至上任何技术手段都要服务于业务目标。与业务方沟通明确漏报和误报的具体成本才能将技术指标转化为真正的业务价值。在我经历的一个信用卡反欺诈项目中最初使用SMOTE导致误报率激增客服压力巨大。后来切换到Borderline SMOTE并辅以阈值调整在召回率仅轻微下降的情况下将精确率提升了近一倍最终实现了业务成本和风险控制的最佳平衡。记住在风控的世界里最好的模型不是AUC最高的那个而是最懂业务、最能平衡风险与体验的那个。希望这篇文章和这些代码能成为你构建这个“懂业务”的模型的一块坚实垫脚石。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2411825.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!