本文目录导读:

类别不平衡(Class Imbalance)是分类任务中的常见问题,即数据集中不同类别的样本数量差异巨大(例如欺诈检测中正样本仅占1%)。重采样是解决该问题的核心预处理手段。
以下是针对类别不平衡的重采样方法总结,按数据操作层面和实现难度分类:
核心重采样策略
| 方法 | 核心思想 | 优点 | 缺点 |
|---|---|---|---|
| 随机欠采样 | 随机删除多数类样本,使与少数类数量持平。 | 简单、运行快。 | 可能丢失重要信息,导致模型欠拟合。 |
| 随机过采样 | 随机复制少数类样本,使与多数类数量持平。 | 简单、不丢失多数类信息。 | 容易导致过拟合(模型记住了重复样本)。 |
| SMOTE | 在少数类样本之间插值生成新样本(而非简单复制)。 | 缓解过拟合,生成合理的新样本。 | 可能生成噪音(尤其在边界区域),对高维数据效果下降。 |
| ADASYN | 自适应合成采样,根据样本密度决定生成数量。 | 更关注难分类的少数类样本(密度低处)。 | 更容易受到噪声影响。 |
| Tomek Links | 清理“链接”样本(互为最近邻且类别不同),常用于欠采样。 | 去除边界噪音,使决策边界更清晰。 | 可能删掉有用信息。 |
| Edited Nearest Neighbors | 删除多数类中被邻居误判的样本。 | 比Tomek更彻底地清洗边界。 | 计算量大。 |
| SMOTE + Tomek | 先SMOTE过采样,再用Tomek清理模糊区域。 | 结合过采样与欠采样优点,减少噪声。 | 参数调优较复杂。 |
| SMOTE + ENN | 先SMOTE过采样,再用ENN清理。 | 比Tomek更激进,适用于噪音大时。 | 同上。 |
为什么不用简单随机过采样?
- 简单复制导致模型在少数类样本上过拟合:模型会记住特征的精确重复,而不是学习泛化特征。
- SMOTE的原理是在两个少数类样本的连线上随机插值: [ x_{new} = xi + \lambda \times (x{zi} - x_i), \quad \lambda \in [0,1] ] 这样生成的样本具有多样性,且保留了特征空间的连续性。
实现工具推荐
首选:imbalanced-learn(Python 库)
from imblearn.over_sampling import SMOTE, RandomOverSampler, ADASYN from imblearn.under_sampling import RandomUnderSampler, TomekLinks, EditedNearestNeighbours from imblearn.combine import SMOTETomek, SMOTEENN # 应用示例 smote = SMOTE(sampling_strategy='auto', random_state=42) # 'auto'自动平衡到多数类数量 X_resampled, y_resampled = smote.fit_resample(X_train, y_train)
注意: 务必在训练集上重采样,测试集保持原始分布不变,否则会严重高估模型性能(数据泄露)。
在Scikit-learn Pipeline中的使用
imbalanced-learn提供了子类Pipeline,确保重采样仅作用于训练阶段,不污染验证/测试集。
from imblearn.pipeline import Pipeline
pipe = Pipeline([
('sampling', SMOTE()),
('clf', RandomForestClassifier())
])
选择策略的实战建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 总数据量很大 (>=10万) | 随机欠采样 或 Tomek | 速度快,删除部分多数类对整体影响小。 |
| 数据量中等 (几千~几万) | SMOTE 或 ADASYN | 平衡效果与过拟合风险的最佳平衡点。 |
| 少数类样本非常稀少 (<50) | SMOTE + ENN | 先生成足够样本,再清除边界噪音。 |
| 特征维度非常高 (千维以上) | Borderline-SMOTE 或 Noise-robust SMOTE | 避免SMOTE在高维空间中产生扭曲的插值。 |
| 目标是检测异常/稀有事件 | 集成+重采样 | 例如使用 EasyEnsemble (训练多个欠采样子集集成) 或 BalanceCascade。 |
| 极度不平衡 (1:1000以上) | SMOTE + 集成学习 或 Data Boost | 单一重采样可能不够,结合算法层调整(如代价敏感学习)。 |
代码快速参考(sklearn风格)
from collections import Counter
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, recall_score
# 假设 X, y 存在严重不平衡
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"原始训练集分布: {Counter(y_train)}")
# 输出: Counter({0: 1000, 1: 50})
# 方法1:SMOTE
from imblearn.over_sampling import SMOTE
sm = SMOTE(random_state=42)
X_res, y_res = sm.fit_resample(X_train, y_train)
print(f"SMOTE后分布: {Counter(y_res)}")
# 方法2:SMOTE + Tomek
from imblearn.combine import SMOTETomek
smt = SMOTETomek(random_state=42)
X_res, y_res = smt.fit_resample(X_train, y_train)
# 训练模型
model = RandomForestClassifier(random_state=42)
model.fit(X_res, y_res)
# 评估(测试集不变!)
y_pred = model.predict(X_test)
print(f"召回率: {recall_score(y_test, y_pred):.2f}")
需要警惕的陷阱
- 绝对不要在重采样前划分训练/测试集。
- SMOTE不适用于图像、文本、序列数据(特征空间非欧几里得),对这些数据应使用数据增强(旋转、裁剪、同义词替换等)。
- 标签噪声敏感:如果少数类中有错误样本,SMOTE会放大噪音,建议先用ENN清理。
- 过采样可能引入偏倚:如果生成样本远离真实分布,模型泛化能力反而下降,此时应优先考虑代价敏感学习(调整
class_weight参数)。
当重采样效果不好时?试试算法层方法
如果重采样带来的提升有限,考虑算法层面的修改:
- 代价敏感学习:给少数类样本分配更高的误分类代价。
# sklearn中直接设置 model = RandomForestClassifier(class_weight='balanced')
- 集成方法:如 EasyEnsemble 或 BalanceCascade(
imbalanced-learn提供)。 - 单类分类:如果异常检测目标极稀有,使用 One-Class SVM / Isolation Forest。
重采样是解决不平衡的“快修”路径,而代价敏感学习和算法改进是更根本的解决方案,实际项目中通常先用SMOTE快速提升召回率,若效果不够再结合其他方法。