1. 不平衡分类问题概述
在机器学习实践中,我们经常会遇到类别分布严重不均衡的数据集。比如在信用卡欺诈检测中,正常交易可能占99.9%,而欺诈交易仅占0.1%。这种极端不平衡的数据分布会给模型训练带来显著挑战。
传统分类算法在这种场景下往往表现不佳,因为它们默认假设类别分布大致平衡,并且以整体准确率为优化目标。在不平衡数据上,一个简单的"总是预测多数类"的策略就能获得很高的准确率,但这显然不是我们想要的结果。
关键问题:当少数类样本不足时,模型难以学习到有效的分类边界,导致对少数类的识别率(召回率)极低。
2. 重采样技术基础原理
2.1 过采样技术
过采样通过增加少数类样本来平衡数据集,主要有两种方式:
随机过采样:简单复制少数类样本
- 优点:实现简单,不引入新信息
- 缺点:容易导致过拟合,因为完全相同的样本被多次使用
SMOTE(合成少数类过采样技术):
- 工作原理:对每个少数类样本x,从其k近邻中随机选择一个样本x',然后在x和x'的连线上随机生成新样本
- 数学表达:x_new = x + λ(x' - x),其中λ∈[0,1]是随机数
- 优势:增加了样本多样性,缓解了过拟合问题
2.2 欠采样技术
欠采样通过减少多数类样本来平衡数据集,常见方法包括:
随机欠采样:随机删除多数类样本
- 优点:简单高效
- 缺点:可能丢失重要信息
Tomek Links:
- 定义:如果两个不同类样本互为最近邻,则构成一个Tomek Link
- 作用:移除这些边界样本可以使分类边界更清晰
ENN(编辑最近邻):
- 方法:移除那些被其k近邻多数误分类的样本
- 效果:能有效清理噪声数据和边界模糊样本
3. 组合采样技术详解
3.1 为什么需要组合采样?
单独使用过采样或欠采样各有局限:
- 单纯过采样可能导致模型对少数类过拟合
- 单纯欠采样可能丢失多数类的重要信息
组合两者可以发挥各自优势:
- 先过采样少数类,确保有足够样本学习决策边界
- 再欠采样多数类,去除噪声和冗余样本
3.2 手动组合实现
使用imbalanced-learn库的Pipeline可以灵活组合各种采样方法:
from imblearn.pipeline import Pipeline from imblearn.over_sampling import SMOTE from imblearn.under_sampling import RandomUnderSampler # 定义模型和采样策略 model = DecisionTreeClassifier() over = SMOTE(sampling_strategy=0.1) # 少数类增加到多数类的10% under = RandomUnderSampler(sampling_strategy=0.5) # 多数类减少到少数类的2倍 # 构建管道:先过采样→再欠采样→最后建模 pipeline = Pipeline(steps=[ ('over', over), ('under', under), ('model', model) ])3.3 参数调优经验
采样比例选择:
- 过采样比例通常设为0.1-0.5(相对多数类的比例)
- 欠采样后多数类样本数建议是少数类的1.5-2倍
- 可通过网格搜索寻找最优比例
SMOTE的k近邻参数:
- 默认k=5,对于高维数据可适当增大
- 使用k折交叉验证避免过拟合
与模型参数的协同:
- 决策树的max_depth需要与采样比例配合调整
- 逻辑回归需要适当增加正则化强度
4. 预定义组合方法实践
4.1 SMOTETomek 实现
from imblearn.combine import SMOTETomek # 默认配置:SMOTE平衡后,移除所有类的Tomek links resampler = SMOTETomek(tomek=TomekLinks(sampling_strategy='all')) # 替代配置:只移除多数类的Tomek links(更保守) resampler = SMOTETomek(tomek=TomekLinks(sampling_strategy='majority'))4.2 SMOTEENN 实现
from imblearn.combine import SMOTEENN from imblearn.under_sampling import EditedNearestNeighbours # 默认配置:SMOTE平衡后,ENN清理所有类 resampler = SMOTEENN() # 替代配置:ENN只清理多数类 resampler = SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority'))4.3 性能对比实验
我们在一个1:100的极端不平衡数据集上测试不同方法(10折交叉验证重复3次):
| 方法 | 平均ROC AUC | 训练时间(s) |
|---|---|---|
| 基准模型 | 0.762 | 1.2 |
| 随机过+欠采样 | 0.814 | 3.5 |
| SMOTE+随机欠采样 | 0.833 | 5.1 |
| SMOTETomek | 0.815 | 6.8 |
| SMOTEENN | 0.829 | 7.2 |
从结果可以看出:
- 所有重采样方法都提升了模型性能
- SMOTE+随机欠采样取得了最好效果
- SMOTEENN虽然计算成本较高,但性能稳定
5. 实际应用中的注意事项
5.1 数据泄露问题
在交叉验证中必须确保采样只在训练折叠中进行:
from sklearn.model_selection import cross_val_score # 错误做法:先全局采样再交叉验证 X_resampled, y_resampled = resampler.fit_resample(X, y) # 会导致数据泄露 scores = cross_val_score(model, X_resampled, y_resampled, cv=cv) # 正确做法:将采样器放入Pipeline pipeline = Pipeline([('resampler', resampler), ('model', model)]) scores = cross_val_score(pipeline, X, y, cv=cv)5.2 类别权重替代方案
对于计算资源有限的情况,可以考虑使用类别权重代替采样:
# 决策树中的类别加权 model = DecisionTreeClassifier(class_weight='balanced') # 逻辑回归中的类别加权 model = LogisticRegression(class_weight={0:1, 1:10})5.3 高维数据特殊处理
当特征维度很高时(如文本数据),需要调整SMOTE策略:
- 先使用PCA/T-SNE降维后再应用SMOTE
- 增大SMOTE的k近邻参数(如k=10)
- 使用SMOTE变体如Borderline-SMOTE
6. 进阶技巧与优化方向
6.1 动态采样策略
根据模型训练过程中的表现动态调整采样比例:
from imblearn.pipeline import Pipeline from sklearn.model_selection import GridSearchCV param_grid = { 'resampler__sampling_strategy': [0.1, 0.2, 0.3], 'model__max_depth': [3, 5, 7] } pipeline = Pipeline([ ('resampler', SMOTE()), ('model', DecisionTreeClassifier()) ]) grid = GridSearchCV(pipeline, param_grid, scoring='roc_auc') grid.fit(X, y)6.2 集成学习方法
结合采样技术与集成学习:
from imblearn.ensemble import BalancedRandomForestClassifier model = BalancedRandomForestClassifier( n_estimators=100, sampling_strategy='auto', replacement=True )6.3 自定义采样策略
实现自定义的采样逻辑:
from imblearn.base import BaseSampler class MySampler(BaseSampler): def _fit_resample(self, X, y): # 自定义采样逻辑 return X_resampled, y_resampled7. 不同场景下的方案选择
根据数据特点和业务需求选择合适策略:
计算资源充足:
- 使用SMOTEENN组合
- 配合网格搜索优化参数
- 考虑集成方法
实时性要求高:
- 简单随机过采样+欠采样
- 使用类别权重替代采样
- 选择线性模型
极端不平衡(<1%):
- 分层采样确保少数类代表
- 使用ADASYN替代SMOTE
- 考虑异常检测算法
在实际项目中,我通常会先尝试SMOTE+随机欠采样的组合,因为它通常在效果和效率之间取得了不错的平衡。对于特别关键的应用,则会进行更全面的方法比较和参数调优。