分类建模实战:交叉验证调参、下采样与 SMOTE 过采样解决样本失衡问题
前言在做分类任务时我们经常会遇到两个头疼的问题一是模型参数怎么调才靠谱二是数据集类别极度不平衡怎么办。今天就通过贷款资格检测这个经典数据集把交叉验证调参、下采样和过采样三种方法从头到尾走一遍。在银行信贷风控场景中最典型的问题就是样本不平衡绝大多数申请人会正常还款真正违约的用户只占极小比例。直接用原始数据训练模型往往整体准确率能到 99%但对违约样本的识别能力几乎为零完全达不到风控预警的要求。本文基于某银行脱敏后的贷款申请数据集从基础的交叉验证调参讲起再到下采样、SMOTE 过采样两种不平衡处理方案一步步落地实现。所有代码均附带详细注释可直接复现运行。一、交叉验证调参我们平时把数据切成训练集和测试集用训练集训模型、测试集看效果这种方法有个问题单次划分的结果偶然性很大换个随机种子可能准确率就变了。交叉验证的思路很简单把训练集分成 K 份轮流用 K-1 份训练、剩下 1 份验证重复 K 次后取平均得分。这样得到的模型性能评估更稳定调参也更可信。1.2 完整代码与逐段讲解① 导入工具包与混淆矩阵绘图函数import pandas as pd import numpy as np from sklearn.metrics import confusion_matrix import matplotlib.pyplot as plt # 混淆矩阵可视化函数 def cm_plot(y, yp): cm confusion_matrix(y, yp) plt.matshow(cm, cmapplt.cm.Blues) plt.colorbar() for x in range(len(cm)): for y in range(len(cm)): plt.annotate(cm[x, y], xy(y, x), horizontalalignmentcenter, verticalalignmentcenter) plt.ylabel(True label) plt.xlabel(Predicted label) return plt这里封装了一个混淆矩阵绘图函数后面三个 demo 都会用到作用是把预测结果直观地展示成热力图每个格子里标上具体样本数。② 数据读取与预处理# 读取数据 data pd.read_csv(creditcard.csv) # 对 Amount 字段做标准化 from sklearn.preprocessing import StandardScaler scaler StandardScaler() data[Amount] scaler.fit_transform(data[[Amount]]) # 删除 Time 列对分类无帮助 data data.drop([Time], axis1) # 划分特征与标签 x data.drop([Class], axis1) y data[Class] # 划分训练集和测试集 7:3 from sklearn.model_selection import train_test_split x_train, x_test, y_train, y_test train_test_split( x, y, test_size0.3, random_state0 )知识点StandardScaler把数据缩放到均值为 0、方差为 1逻辑回归对特征尺度敏感必须做标准化。Time列是交易发生的时间戳对欺诈检测没有直接意义直接删掉。random_state0固定随机种子保证每次运行划分结果一致方便复现。③ 交叉验证调参核心部分from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score scores [] c_param_range [0.01, 0.1, 1, 10, 100] for i in c_param_range: lr LogisticRegression(Ci, solverlbfgs, max_iter1000) # 8折交叉验证评估指标为准确率 score cross_val_score(lr, x_train, y_train, cv8, scoringaccuracy) score_mean sum(score) / len(score) scores.append(score_mean) print(score_mean) # 选出最优的 C 值 best_c c_param_range[np.argmax(scores)] print(f最优惩罚因子为{best_c})关键知识点C 参数逻辑回归中的正则化系数。C 越小正则化越强模型越简单越不容易过拟合C 越大正则化越弱模型越复杂。cv8表示 8 折交叉验证把训练集切成 8 份跑 8 次取平均。scoringaccuracy用准确率作为评估指标。最后用np.argmax(scores)找到得分最高对应的 C 值④ 模型训练与结果评估# 用最优参数训练最终模型 lr LogisticRegression(Cbest_c, max_iter1000) lr.fit(x_train, y_train) from sklearn import metrics # 训练集表现 train_predicted lr.predict(x_train) print(metrics.classification_report(y_train, train_predicted)) cm_plot(y_train, train_predicted).show() # 测试集表现 test_predicted lr.predict(x_test) print(metrics.classification_report(y_test, test_predicted, digits6)) cm_plot(y_test, test_predicted).show()classification_report会输出精确率、召回率、F1 值等指标。跑完你会发现整体准确率特别高99%但少数类老赖的召回率其实很低 —— 这就是类别不平衡带来的问题也是后面两个 demo 要解决的。二、下采样处理类别不平衡2.1 什么是下采样银行贷款数据集中正常贷款占 99% 以上老赖贷款只有不到 1%模型哪怕全预测为正常也能有 99% 的准确率但毫无实际意义。下采样Undersampling的做法是从数量多的类别里随机抽取一部分让正负样本数量相等。简单粗暴但缺点是会丢失大量多数类信息。2.2 核心代码① 下采样实现# 先把标签拼回训练集方便采样 x_train[Class] y_train data_train x_train # 分离正负样本 positive_eg data_train[data_train[Class] 0] # 正常交易 negative_eg data_train[data_train[Class] 1] # 欺诈交易 # 从多数类中随机抽取与少数类数量相同的样本 positive_eg positive_eg.sample(len(negative_eg)) # 拼接得到平衡数据集 data_c pd.concat([positive_eg, negative_eg]) x data_c.drop([Class], axis1) y data_c[Class]核心就是sample(len(negative_eg))这一行从正常交易里随机抽出和欺诈交易一样多的样本实现 1:1 平衡。② 交叉验证调参 训练评估from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score scores [] c_param_range [0.01, 0.1, 1, 10, 100] for i in c_param_range: lr LogisticRegression(Ci, solverlbfgs, max_iter2000) score cross_val_score(lr, x, y, cv8) score_mean sum(score) / len(score) scores.append(score_mean) print(score_mean) best_c c_param_range[np.argmax(scores)] print(f最优惩罚因子为{best_c}) # 训练最终模型 lr LogisticRegression(Cbest_c, solverlbfgs, max_iter2000) lr.fit(x, y)注意交叉验证是在下采样后的平衡数据集上做的但最终评估一定要在原始测试集上跑这样才能反映真实场景的效果。from sklearn import metrics # 在原始训练集上评估 train_predicted lr.predict(x_train.drop([Class], axis1)) print(metrics.classification_report(y_train, train_predicted)) cm_plot(y_train, train_predicted).show() # 在原始测试集上评估 test_predicted lr.predict(x_test) print(metrics.classification_report(y_test, test_predicted)) cm_plot(y_test, test_predicted).show()下采样后你会发现整体准确率下降了但老赖贷款的召回率明显提升了 —— 这才是我们真正关心的指标毕竟漏掉一笔老赖贷款的代价远大于误判一笔正常交易。三、SMOTE 过采样demo03 核心讲解3.1 什么是过采样下采样是减少多数类过采样Oversampling则是增加少数类。最简单的过采样是直接复制少数类样本但容易过拟合。SMOTESynthetic Minority Oversampling Technique是更智能的做法在少数类样本之间插值人工合成新的少数类样本既平衡了数据集又不容易过拟合。3.2 核心代码① SMOTE 过采样实现from imblearn.over_sampling import SMOTE oversample SMOTE(random_state0) os_x_train, os_y_train oversample.fit_resample(x_train, y_train)就这三行代码imblearn库直接帮你完成 SMOTE 算法输出的os_x_train和os_y_train就是平衡后的训练集正负样本 1:1。使用前需要先安装pip install imbalanced-learn② 交叉验证调参 训练评估from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score scores [] c_param_range [0.01, 0.1, 1, 10, 100] for i in c_param_range: lr LogisticRegression(Ci, solverlbfgs, max_iter1000) score cross_val_score(lr, os_x_train, os_y_train, cv5) score_mean sum(score) / len(score) scores.append(score_mean) print(score_mean) best_c c_param_range[np.argmax(scores)] print(f最优惩罚因子为{best_c}) lr LogisticRegression(Cbest_c, solverlbfgs, max_iter1000) lr.fit(os_x_train, os_y_train)同样评估要在原始测试集上进行from sklearn import metrics train_predicted lr.predict(x_train) print(metrics.classification_report(y_train, train_predicted)) cm_plot(y_train, train_predicted).show() test_predicted lr.predict(x_test) print(metrics.classification_report(y_test, test_predicted)) cm_plot(y_test, test_predicted).show()对比下采样和过采样的结果SMOTE 通常在保留数据信息上更有优势实际工业界用得也更多。四、小案例寝室分配数据把同样的流程套用到寝室分配数据集上整体流程和前面完全一致读取数据 → 标准化 → 划分训练测试集 → SMOTE 过采样 → 交叉验证调参 → 评估。唯一区别是数据格式不同用np.loadtxt读取 txt 文件核心逻辑和前面一模一样。五、方法对比与总结最后把三种方法的核心要点整理一下⚠️ 提一下采样操作只能在训练集上做测试集必须保持原始分布不动否则评估结果没有参考价值。

相关新闻