机器学习中不平衡分类问题的采样策略与实践
1. 不平衡分类问题的挑战与解决思路
在机器学习实践中,我们经常会遇到类别分布严重不平衡的数据集。比如在信用卡欺诈检测中,正常交易可能占99.9%,而欺诈交易只有0.1%。这种极端不平衡会导致模型倾向于预测多数类,而忽略少数类——即使模型将所有样本都预测为多数类,也能获得很高的准确率,但这显然不是我们想要的结果。
传统解决方案主要分为三类:
- 调整类别权重(Class Weighting)
- 过采样(Oversampling)少数类
- 欠采样(Undersampling)多数类
每种方法都有其局限性:
- 单纯调整权重对极端不平衡数据效果有限
- 过采样容易导致过拟合
- 欠采样会丢失有价值信息
2. 采样方法的组合策略
2.1 过采样技术详解
SMOTE(Synthetic Minority Oversampling Technique)是最经典的过采样算法。它通过在少数类样本之间进行线性插值来生成新样本,而不是简单复制。具体实现步骤:
- 对于每个少数类样本x,找到其k个最近邻(通常k=5)
- 随机选择一个近邻x'
- 在x和x'连线上随机生成新样本: x_new = x + λ*(x' - x),其中λ∈[0,1]
改进版本包括:
- Borderline-SMOTE:只对边界样本过采样
- ADASYN:根据样本密度自适应调整采样权重
2.2 欠采样技术详解
常见的欠采样方法包括:
- Random Undersampling:随机删除多数类样本
- Tomek Links:移除边界附近的多数类样本
- Cluster Centroids:使用聚类中心代表多数类
更先进的ENN(Edited Nearest Neighbors)算法:
- 对每个样本,找到其k近邻
- 如果该样本的类别与多数近邻不同,则移除
- 特别适合清理噪声数据
3. 组合采样实践方案
3.1 SMOTE+ENN组合
这是最经典的组合策略之一,实现流程:
from imblearn.combine import SMOTEENN from sklearn.ensemble import RandomForestClassifier # 初始化组合采样器 sampler = SMOTEENN(sampling_strategy='auto', smote=SMOTE(k_neighbors=5), enn=EditedNearestNeighbours(n_neighbors=3)) # 应用采样 X_resampled, y_resampled = sampler.fit_resample(X, y) # 训练模型 model = RandomForestClassifier() model.fit(X_resampled, y_resampled)关键参数说明:
sampling_strategy:控制采样后的类别比例k_neighbors:SMOTE的近邻数n_neighbors:ENN的近邻数
3.2 SMOTE+Tomek组合
另一种轻量级组合方案:
from imblearn.combine import SMOTETomek sampler = SMOTETomek(tomek=TomekLinks(sampling_strategy='majority'), smote=SMOTE(sampling_strategy='auto'))这种组合的特点是:
- 先使用SMOTE增加少数类样本
- 再用Tomek Links清理边界区域
- 计算开销小于SMOTE+ENN
4. 评估指标选择
对于不平衡分类,准确率是无效指标。应该使用:
- 精确率-召回率曲线(PR Curve)
- ROC AUC
- F1 Score(特别是F2 Score当更关注召回率时)
- 混淆矩阵(重点关注少数类的召回率)
示例评估代码:
from sklearn.metrics import classification_report, roc_auc_score y_pred = model.predict(X_test) print(classification_report(y_test, y_pred)) print("ROC AUC:", roc_auc_score(y_test, y_pred_proba))5. 实战经验与调优技巧
5.1 采样比例选择
经验法则:
- 对于中度不平衡(如1:10),采样到1:2或1:1
- 对于极端不平衡(如1:1000),采样到1:10或1:20
- 可通过交叉验证寻找最优比例
5.2 避免数据泄露
常见错误:
- 在训练集和测试集划分前进行采样
- 在交叉验证时对整个数据集采样
正确做法:
from sklearn.model_selection import cross_val_score from imblearn.pipeline import make_pipeline pipeline = make_pipeline( SMOTEENN(), RandomForestClassifier() ) scores = cross_val_score(pipeline, X, y, cv=5, scoring='roc_auc')5.3 与模型参数的协同调优
采样参数应与模型参数一起调优:
from sklearn.model_selection import GridSearchCV param_grid = { 'smoteenn__smote__k_neighbors': [3,5,7], 'randomforestclassifier__n_estimators': [100,200] } grid = GridSearchCV(pipeline, param_grid, cv=3, scoring='roc_auc') grid.fit(X_train, y_train)6. 高级组合策略
6.1 集成采样方法
结合多个采样器的集成方案:
from imblearn.ensemble import BalancedBaggingClassifier bbc = BalancedBaggingClassifier( base_estimator=RandomForestClassifier(), sampling_strategy='auto', replacement=False, random_state=42)6.2 深度学习中的采样策略
对于神经网络:
- 在batch级别进行动态采样
- 使用加权损失函数+采样的组合
- 示例实现:
from tensorflow.keras import layers class BalancedBatchGenerator(tf.keras.utils.Sequence): def __init__(self, X, y, batch_size=32): self.sampler = SMOTEENN() self.X, self.y = self.sampler.fit_resample(X, y) self.batch_size = batch_size def __len__(self): return len(self.X) // self.batch_size def __getitem__(self, idx): batch_X = self.X[idx*self.batch_size:(idx+1)*self.batch_size] batch_y = self.y[idx*self.batch_size:(idx+1)*self.batch_size] return batch_X, batch_y7. 常见问题排查
7.1 过采样后模型性能下降
可能原因:
- 生成了太多噪声样本
- SMOTE的近邻参数k设置不当 解决方案:
- 尝试Borderline-SMOTE
- 减小k值
- 添加ENN清理步骤
7.2 欠采样丢失重要信息
现象:
- 模型在多数类上表现大幅下降 解决方案:
- 改用Cluster Centroids等智能欠采样
- 增加多数类的采样比例
- 尝试集成方法保留更多信息
7.3 计算时间过长
优化策略:
- 对大数据集先进行随机欠采样
- 使用更高效的实现如imbalanced-learn
- 考虑近似算法如Random Undersampling+SMOTE
在实际项目中,我通常会建立一个采样策略评估表:
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| SMOTE+ENN | 清理噪声效果好 | 计算成本高 | 中小规模数据 |
| SMOTE+Tomek | 计算效率高 | 清理不够彻底 | 快速原型开发 |
| 集成采样 | 自动平衡效果 | 实现复杂 | 生产环境 |
