机器学习类别不平衡问题:欠采样方法详解与实践
1. 不平衡分类问题概述
在机器学习实践中,我们经常会遇到类别分布严重不均衡的数据集。比如在信用卡欺诈检测中,正常交易可能占99.9%,而欺诈交易仅占0.1%。这种极端不平衡的数据分布会给模型训练带来显著挑战。
传统分类算法在这种场景下往往会偏向多数类,导致对少数类的识别率极低。举个例子,在一个99:1的数据集上,即使模型简单地将所有样本预测为多数类,也能获得99%的准确率,但这种"模型"对少数类的识别完全失败。
实际经验表明,当类别不平衡比例超过3:1时,就需要考虑采用专门的应对策略。而在医疗诊断、工业缺陷检测等领域,10:1甚至100:1的不平衡比例都很常见。
2. 重采样技术基础
2.1 过采样与欠采样对比
解决类别不平衡的常用方法分为两大类:
- 过采样(Oversampling):增加少数类样本,如SMOTE算法
- 欠采样(Undersampling):减少多数类样本,如本文介绍的多种方法
关键区别:
- 过采样可能引发过拟合风险,特别是简单复制样本时
- 欠采样可能丢失重要信息,特别是随机删除样本时
- 实际应用中常组合使用,如SMOTEENN算法
2.2 欠采样的核心挑战
欠采样不是简单地随机删除多数类样本,而是需要智能选择:
- 哪些样本对分类边界影响最小?
- 哪些样本可能是噪声或异常值?
- 如何保留最具代表性的多数类样本?
3. 保留样本的欠采样方法
3.1 NearMiss系列算法
NearMiss通过距离度量选择保留的多数类样本,包含三个变种:
NearMiss-1:
from imblearn.under_sampling import NearMiss undersampler = NearMiss(version=1, n_neighbors=3) X_res, y_res = undersampler.fit_resample(X, y)- 保留距离最近的3个少数类样本平均距离最小的多数类样本
- 适合:希望强化分类边界的情况
NearMiss-2:
undersampler = NearMiss(version=2, n_neighbors=3)- 保留距离最远的3个少数类样本平均距离最小的多数类样本
- 适合:多数类内部存在多个子簇的情况
NearMiss-3:
undersampler = NearMiss(version=3, n_neighbors_ver3=3)- 为每个少数类样本保留最近的N个多数类样本
- 适合:少数类样本分布稀疏的情况
实测建议:NearMiss-3通常表现最好,但计算成本较高。在小数据集上(<10万样本)可以优先尝试。
3.2 压缩最近邻规则(CNN)
from imblearn.under_sampling import CondensedNearestNeighbour undersampler = CondensedNearestNeighbour(n_neighbors=1) X_res, y_res = undersampler.fit_resample(X, y)算法原理:
- 将所有少数类样本放入"store"
- 逐个检查多数类样本:
- 用当前store中的样本训练1-NN分类器
- 若该样本被错误分类,则加入store
- 最终store中的样本即为保留集合
注意事项:
- 计算复杂度O(n²),只适合小型数据集
- 可能保留部分噪声样本
- 实际应用中常设置n_neighbors=3或5而非1
4. 删除样本的欠采样方法
4.1 Tomek Links
from imblearn.under_sampling import TomekLinks undersampler = TomekLinks() X_res, y_res = undersampler.fit_resample(X, y)识别互为最近邻且类别相反的样本对(Tomek Link),然后:
- 删除多数类样本
- 或删除两个样本(更激进)
应用场景:
- 清理边界模糊的样本
- 常作为后处理步骤与其他方法联用
4.2 编辑最近邻规则(ENN)
from imblearn.under_sampling import EditedNearestNeighbours undersampler = EditedNearestNeighbours(n_neighbors=3) X_res, y_res = undersampler.fit_resample(X, y)算法步骤:
- 对每个样本,找到其3个最近邻
- 如果多数类样本被3个最近邻误分类,则删除
- 如果少数类样本被误分类,则删除其多数类最近邻
优势:
- 有效去除噪声点和边界模糊点
- 保留清晰的分类结构
5. 组合策略的实际应用
5.1 单边选择(OSS)
from imblearn.under_sampling import OneSidedSelection undersampler = OneSidedSelection(n_neighbors=1, n_seeds_S=200) X_res, y_res = undersampler.fit_resample(X, y)组合了:
- CNN算法选择初始种子点
- 移除Tomek Links进一步净化
参数建议:
- n_seeds_S控制初始样本量,通常设为≈少数类样本数
- 适合中等规模数据集(1万-10万样本)
5.2 邻域清洁规则(NCR)
from imblearn.under_sampling import NeighbourhoodCleaningRule undersampler = NeighbourhoodCleaningRule( n_neighbors=3, threshold_cleaning=0.5) X_res, y_res = undersampler.fit_resample(X, y)三阶段处理:
- 使用ENN找出可疑样本
- 计算每个可疑样本的类别纯度
- 删除纯度低于阈值的邻域样本
调优建议:
- threshold_cleaning通常设为0.5-0.7
- 对高维数据表现良好
6. 实战经验与避坑指南
6.1 方法选择决策树
根据数据特征选择合适方法:
if 样本量<1万: if 边界清晰: 使用Tomek Links+CNN else: 使用NearMiss-3 elif 1万≤样本量≤10万: if 计算资源充足: 使用NCR else: 使用OSS else: # >10万样本 先随机欠采样到10万再应用上述方法6.2 参数调优技巧
k值选择:
- 高维数据:增大k(5-10)
- 低维数据:小k(3-5)足够
- 可通过交叉验证确定最优k
采样比例:
- 初始设为1:1
- 逐步调整到验证集F1最高
- 极端不平衡时可分层设置(如1:2,1:3)
与过采样结合:
from imblearn.pipeline import Pipeline from imblearn.over_sampling import SMOTE pipeline = Pipeline([ ('oversample', SMOTE()), ('undersample', NeighbourhoodCleaningRule()) ])6.3 常见问题排查
问题1:欠采样后模型过拟合
- 检查是否删除了太多多数类样本
- 尝试增加k值或调整采样比例
- 考虑添加正则化项
问题2:计算时间过长
- 对大数据集先做随机欠采样
- 使用近似最近邻算法(如Annoy)
- 降低n_neighbors参数
问题3:少数类识别率下降
- 检查是否过度清理了边界样本
- 尝试组合过采样方法
- 调整分类决策阈值
7. 评估指标选择
不平衡分类需用专用评估指标:
| 指标 | 公式 | 适用场景 |
|---|---|---|
| F1-Score | 2*(P*R)/(P+R) | 平衡考量精确率与召回率 |
| G-Mean | √(TPR*TNR) | 要求两类表现均衡 |
| MCC | (TPTN-FPFN)/√[(TP+FP)(TP+FN)(TN+FP)(TN+FN)] | 综合评估指标 |
实施建议:
from sklearn.metrics import classification_report print(classification_report(y_test, y_pred, target_names=['多数类','少数类']))8. 进阶技巧与扩展
8.1 聚类辅助欠采样
- 对多数类进行聚类
- 从每个簇中选取代表样本
- 结合少数类样本形成新数据集
from sklearn.cluster import KMeans kmeans = KMeans(n_clusters=100) majority_samples = X[y==0] kmeans.fit(majority_samples) centroids = kmeans.cluster_centers_8.2 集成学习方法
from imblearn.ensemble import BalancedRandomForestClassifier model = BalancedRandomForestClassifier( sampling_strategy='auto', replacement=False) model.fit(X_train, y_train)优势:
- 自动处理类别不平衡
- 保持原始数据分布
- 通常比单次采样效果更好
8.3 代价敏感学习
通过class_weight参数调整误分类代价:
from sklearn.svm import SVC model = SVC(class_weight={0:1, 1:10}) model.fit(X_train, y_train)设置原则:
- 少数类误分类代价 = 不平衡比例倒数
- 可通过网格搜索优化
9. 行业应用案例
9.1 金融风控场景
挑战:
- 欺诈交易占比<0.1%
- 误判成本不对称
解决方案:
- 使用NearMiss-3初步欠采样
- 应用NCR清理噪声
- 训练XGBoost模型
- 设置动态决策阈值
9.2 医疗诊断系统
特殊要求:
- 假阴性代价极高
- 特征维度通常较高
实施方案:
pipeline = Pipeline([ ('undersample', TomekLinks()), # 温和清理 ('classifier', LogisticRegression( class_weight='balanced', penalty='l1')) ])9.3 工业质检应用
特点:
- 缺陷样本获取成本高
- 生产环境变化快
策略:
- 在线增量学习
- 动态调整采样比例
- 定期更新清洁规则
10. 工具与资源推荐
10.1 Python库比较
| 库名称 | 特点 | 适用场景 |
|---|---|---|
| imbalanced-learn | 算法全面 | 通用场景 |
| SMOTE-variants | 专注过采样 | 需要最新SMOTE变体 |
| unbalanced-dataset | 专注集成方法 | 大数据集 |
10.2 可视化工具
from imblearn.visualization import plot_2d_space plot_2d_space(X_res, y_res, 'After Undersampling')功能:
- 2D/3D数据分布展示
- 采样前后对比
- 决策边界可视化
10.3 基准数据集
常用测试数据集:
- Credit Card Fraud (Kaggle)
- NASA软件缺陷数据集
- KDD Cup 2008乳腺癌数据
获取方式:
from imblearn.datasets import fetch_datasets dataset = fetch_datasets()['credit_card'] X, y = dataset.data, dataset.target11. 性能优化策略
11.1 并行计算加速
from joblib import parallel_backend with parallel_backend('loky', n_jobs=4): undersampler.fit_resample(X, y)配置建议:
- 大数据集:n_jobs=-1(使用所有核心)
- 小数据集:n_jobs=2-4
11.2 内存优化
对于超大矩阵:
undersampler = NearMiss(version=3, n_jobs=4, ratio='majority') # 仅处理多数类11.3 近似算法
from sklearn.neighbors import NearestNeighbors # 使用LSH近似最近邻 nn = NearestNeighbors(algorithm='lsh', n_neighbors=3) undersampler.set_params(n_neighbors=nn)12. 持续学习与更新
12.1 学术前沿跟踪
近年创新方向:
- 深度学习与欠采样结合
- 自适应采样比例
- 在线流数据欠采样
12.2 实践社区推荐
- Kaggle不平衡分类比赛
- Imbalanced Learning Symposium
- 各大学机器学习课程专题
12.3 版本更新注意
imbalanced-learn主要版本变化:
- v0.6+: 新增ClusterCentroids
- v0.7+: 改进NCR效率
- v0.8+: 添加GPU支持
升级检查:
import imblearn print(imblearn.__version__)