机器学习中不平衡数据集处理实战指南
1. 不平衡数据集处理的核心挑战
在真实世界的数据分析任务中,我们经常会遇到类别分布严重不均衡的数据集。比如信用卡欺诈检测中正常交易占99.9%,医疗诊断中健康样本远多于患病样本,工业质检中缺陷产品占比可能不足1%。这类数据如果直接扔给机器学习算法,模型会倾向于预测多数类来获得"虚假的高准确率"——比如一个信用卡欺诈检测模型如果总是预测"正常交易",准确率就能达到99.9%,但这完全丧失了实际价值。
Pandas和Scikit-learn这对黄金组合提供了完整的解决方案链。Pandas作为数据操作利器,可以快速计算类别分布、筛选样本子集、进行基础统计分析。而Scikit-learn则提供了从数据重采样到评估指标的全套工具。两者配合使用,就像手术刀与显微镜的关系——Pandas负责精准的数据"解剖"和"预处理",Scikit-learn则完成算法的"显微手术"。
关键认知:处理不平衡数据不是简单的技术选择,而是需要建立从数据理解到模型评估的完整方法论。评估指标的选择往往比模型本身更重要。
2. 数据探索与可视化实战
2.1 使用Pandas进行类别分布分析
首先用Pandas的value_counts()快速获取类别分布:
import pandas as pd # 加载信用卡交易数据集 df = pd.read_csv('creditcard.csv') class_dist = df['Class'].value_counts(normalize=True) print(f"类别分布:\n{class_dist}") print(f"\n少数类样本数: {df['Class'].sum()}") # 假设1代表少数类输出示例:
类别分布: 0 0.998273 1 0.001727 Name: Class, dtype: float64 少数类样本数: 492进阶技巧是结合groupby()和describe()查看不同类别的特征统计差异:
# 对比两类交易的金额统计特征 amount_stats = df.groupby('Class')['Amount'].describe() print(amount_stats)2.2 可视化工具链
Matplotlib+Seaborn组合能直观展示不平衡性:
import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize=(10,6)) sns.countplot(x='Class', data=df) plt.title('类别分布可视化') plt.show()对于高维数据,可以尝试t-SNE降维后观察类别分布:
from sklearn.manifold import TSNE # 先标准化数据 from sklearn.preprocessing import StandardScaler X = StandardScaler().fit_transform(df.drop('Class', axis=1)) # 降维可视化 tsne = TSNE(n_components=2, random_state=42) X_tsne = tsne.fit_transform(X) plt.scatter(X_tsne[:,0], X_tsne[:,1], c=df['Class'], alpha=0.5) plt.colorbar() plt.title('t-SNE降维可视化') plt.show()3. 重采样技术深度解析
3.1 随机欠采样与过采样
最简单的随机欠采样实现:
from sklearn.utils import resample # 分离多数类和少数类 df_majority = df[df.Class==0] df_minority = df[df.Class==1] # 随机欠采样 df_majority_downsampled = resample(df_majority, replace=False, n_samples=len(df_minority), random_state=42) # 合并新数据集 df_downsampled = pd.concat([df_majority_downsampled, df_minority])随机过采样同理,只需设置replace=True并增加样本数。但这种方法容易导致过拟合。
3.2 SMOTE及其变种算法
Scikit-learn的imbalanced-learn扩展包提供了更高级的采样方法:
from imblearn.over_sampling import SMOTE X = df.drop('Class', axis=1) y = df['Class'] sm = SMOTE(sampling_strategy='auto', k_neighbors=5, random_state=42) X_res, y_res = sm.fit_resample(X, y) print(f"重采样后类别分布: {pd.Series(y_res).value_counts()}")SMOTE的几种改进版本:
- BorderlineSMOTE:只对边界样本进行过采样
- SVMSMOTE:使用SVM支持向量确定边界区域
- ADASYN:根据样本密度自适应生成新样本
3.3 混合采样与集成方法
结合欠采样和过采样的SMOTEENN:
from imblearn.combine import SMOTEENN smote_enn = SMOTEENN(sampling_strategy='auto', random_state=42) X_res, y_res = smote_enn.fit_resample(X, y)集成方法如EasyEnsemble:
from imblearn.ensemble import EasyEnsembleClassifier eec = EasyEnsembleClassifier(n_estimators=10, random_state=42) eec.fit(X_train, y_train)4. 模型训练与评估策略
4.1 专用评估指标体系
传统准确率在不平衡数据中完全失效,需要采用新指标:
from sklearn.metrics import classification_report, roc_auc_score y_pred = model.predict(X_test) print(classification_report(y_test, y_pred)) print(f"ROC AUC: {roc_auc_score(y_test, y_pred_proba)}")关键指标解读:
- Precision:预测为正的样本中实际为正的比例
- Recall:实际为正的样本中被正确预测的比例
- F1-score:Precision和Recall的调和平均
- ROC AUC:综合考量模型排序能力的指标
4.2 代价敏感学习
Scikit-learn中许多算法支持class_weight参数:
from sklearn.linear_model import LogisticRegression # 自动按类别比例调整权重 lr = LogisticRegression(class_weight='balanced', max_iter=1000) lr.fit(X_train, y_train)也可以自定义权重字典:
weights = {0:1, 1:10} # 少数类错误代价是多数类的10倍 lr_custom = LogisticRegression(class_weight=weights)4.3 阈值调整技术
通过调整决策阈值来优化业务指标:
from sklearn.metrics import precision_recall_curve precisions, recalls, thresholds = precision_recall_curve(y_test, y_pred_proba) # 找到满足业务需求的最佳阈值 target_recall = 0.95 best_idx = (recalls >= target_recall).argmax() best_threshold = thresholds[best_idx]5. 完整项目实战案例
5.1 信用卡欺诈检测全流程
# 完整代码框架 import pandas as pd from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report from imblearn.pipeline import make_pipeline from imblearn.over_sampling import SMOTE # 1. 数据加载与预处理 df = pd.read_csv('creditcard.csv') X = df.drop('Class', axis=1) y = df['Class'] # 2. 划分训练测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, stratify=y, random_state=42) # 3. 构建处理管道 pipeline = make_pipeline( SMOTE(sampling_strategy='minority', random_state=42), RandomForestClassifier(n_estimators=100, class_weight='balanced_subsample') ) # 4. 训练与评估 pipeline.fit(X_train, y_train) y_pred = pipeline.predict(X_test) print(classification_report(y_test, y_pred))5.2 不同方法的对比实验
我们对比几种主流方法的性能表现:
| 方法 | Precision | Recall | F1-score | ROC AUC |
|---|---|---|---|---|
| 原始数据(RF) | 0.92 | 0.76 | 0.83 | 0.88 |
| 随机欠采样(RF) | 0.05 | 0.92 | 0.09 | 0.95 |
| SMOTE(RF) | 0.91 | 0.85 | 0.88 | 0.97 |
| 代价敏感学习(LR) | 0.83 | 0.81 | 0.82 | 0.94 |
| SMOTE+集成(XGBoost) | 0.93 | 0.89 | 0.91 | 0.98 |
6. 工程实践中的经验总结
6.1 常见陷阱与解决方案
数据泄露问题:在交叉验证或管道构建时,SMOTE等操作必须在每个fold内部进行,否则会导致数据泄露
from imblearn.pipeline import Pipeline pipeline = Pipeline([ ('smote', SMOTE()), ('scaler', StandardScaler()), ('clf', LogisticRegression()) ])类别权重与采样冲突:同时使用
class_weight='balanced'和过采样会导致双重补偿高维数据问题:对于文本等超高维数据,直接应用SMOTE效果可能不佳,建议先降维
6.2 计算效率优化
- 对大型数据集,使用
RandomUnderSampler比过采样更高效 - 设置
n_jobs参数并行化处理:from imblearn.ensemble import BalancedRandomForestClassifier brf = BalancedRandomForestClassifier(n_estimators=100, n_jobs=-1)
6.3 业务场景适配技巧
- 欺诈检测:优先保证高Recall(捕捉尽可能多的欺诈交易)
- 医疗诊断:需要高Precision(避免健康人被误诊为患病)
- 推荐系统:可接受一定误报,侧重优化ROC AUC
实战建议:在最终部署时,建议保存预处理管道和模型为一个整体:
import joblib joblib.dump(pipeline, 'fraud_detection_pipeline.pkl')
处理不平衡数据不是简单的技术选择,而是需要根据业务目标、数据特性和计算资源进行综合决策的工程艺术。Pandas和Scikit-learn提供的丰富工具链,让我们能够构建从数据理解到模型部署的完整解决方案。记住:没有放之四海而皆准的最佳方法,只有最适合当前场景的解决方案。
