#生成数据集
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=5000, n_features=2,
n_informative=2, n_redundant=0,
n_repeated=0, n_classes=3,
n_clusters_per_class=1,
weights=[0.01, 0.05, 0.94],
class_sep=0.8, random_state=0)
#随机过采样
from imblearn.over_sampling import RandomOverSampler, BorderlineSMOTE
X_resampled, y_resampled = RandomOverSampler().fit_resample(X, y)
#SMOTE过采样及其变体
from imblearn.over_sampling import SMOTE
X_resampled_smote, y_resampled_smote = SMOTE().fit_resample(X, y)
X_resampled, y_resampled = BorderlineSMOTE(kind='borderline-1').fit_resample(X, y)
X_resampled, y_resampled = BorderlineSMOTE(kind='borderline-2').fit_resample(X, y)
#ADASYN过采样
from imblearn.over_sampling import ADASYN
X_resampled_adasyn, y_resampled_adasyn = ADASYN().fit_resample(X, y)
#随机欠采样
from imblearn.under_sampling import RandomUnderSampler
X_resampled, y_resampled = RandomUnderSampler().fit_resample(X, y)
#基于k-means聚类的欠采样
from imblearn.under_sampling import ClusterCentroids
X_resampled, y_resampled = ClusterCentroids().fit_resample(X, y)
#基于最近邻算法的欠采样
from imblearn.under_sampling import RepeatedEditedNearestNeighbours
X_resampled, y_resampled = RepeatedEditedNearestNeighbours().fit_resample(X, y)
#在数据上运用一种分类器
#然后将概率低于阈值的样本剔除掉
#从而实现欠采样
from sklearn.linear_model import LogisticRegression
from imblearn.under_sampling import InstanceHardnessThreshold
lr_underS = InstanceHardnessThreshold(
random_state=0, estimator=LogisticRegression())
X_resampled, y_resampled = lr_underS.fit_resample(X, y)
代码示例演示了如何使用 scikit-learn 和 imblearn (imbalanced-learn
)库来处理多类不平衡问题。代码中展示了常见的几种过采样和欠采样方法,以及它们在数据集上的应用。下面对每个方法的含义和用法做一个简要的说明,帮助理解这些代码的作用。
1. 数据集的生成
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=5000,
n_features=2,
n_informative=2,
n_redundant=0,
n_repeated=0,
n_classes=3,
n_clusters_per_class=1,
weights=[0.01, 0.05, 0.94],
class_sep=0.8,
random_state=0)
- n_samples=5000:生成 5000 条样本。
- n_features=2:有 2 个特征。
- n_informative=2, n_redundant=0, n_repeated=0:2 个有用特征,没有冗余特征和重复特征。
- n_classes=3:目标变量有 3 个类别。
- n_clusters_per_class=1:每个类别只有 1 个簇。
- weights=[0.01, 0.05, 0.94]:三个类别的分布分别为 1%、5% 和 94%,这就使得数据集高度不平衡。
- class_sep=0.8:类间分离度,用于控制数据分布的可分离程度。
- random_state=0:保证可重复性。
这段代码会返回特征矩阵 X
和标签 y
。由于 weights
的设定,得到的数据集中第三个类别占绝大多数,而前两个类别是少数类。
2. 随机过采样(RandomOverSampler)
from imblearn.over_sampling import RandomOverSampler
X_resampled, y_resampled = RandomOverSampler().fit_resample(X, y)
- RandomOverSampler 会对少数类进行随机复制(简单地复制少数类样本)直到满足指定的采样策略(通常是让少数类样本数量与多数类相当)。
- 优点:实现简单,速度快。
- 缺点:容易导致过拟合,因为它只是无选择地重复少数类样本。
3. SMOTE 过采样
from imblearn.over_sampling import SMOTE
X_resampled_smote, y_resampled_smote = SMOTE().fit_resample(X, y)
- SMOTE (Synthetic Minority Over-sampling Technique) 会在少数类样本之间进行插值,合成新的“虚拟”少数类样本,从而平衡各类样本数量。
- 优点:在样本空间中产生新的少数类样本,避免了简单过采样的“机械复制”问题,能够降低过拟合的风险。
- 缺点:SMOTE 会在特征空间中对少数类做线性插值,对于非线性、复杂的分类边界可能仍然难以处理。
4. Borderline-SMOTE 过采样
from imblearn.over_sampling import BorderlineSMOTE
X_resampled, y_resampled = BorderlineSMOTE(kind='borderline-1').fit_resample(X, y)
X_resampled, y_resampled = BorderlineSMOTE(kind='borderline-2').fit_resample(X, y)
- Borderline-SMOTE 是对 SMOTE 的改进版,通过只在“边界样本”周围进行插值来合成新的少数类样本。
- kind=‘borderline-1’ / ‘borderline-2’:这是两个不同的边界合成策略:
- borderline-1:更侧重在少数类和多数类接壤的“危险边界”区域来合成新样本。
- borderline-2:同样着重于边界区域,但在合成策略上更激进,合成样本可能离多数类更近。
- 相比原始 SMOTE,更能强化“边界区域”的少数类特征,从而改善分类器在边界上的识别能力。
注意:在新版本的
imbalanced-learn
中,kind
参数有时会更名为borderline_kind
,或者参数名称已被弃用。若出现报错,可参考对应版本的官方文档或更改参数名称。
5. ADASYN 过采样
from imblearn.over_sampling import ADASYN
X_resampled_adasyn, y_resampled_adasyn = ADASYN().fit_resample(X, y)
- ADASYN(Adaptive Synthetic Sampling) 类似于 SMOTE,都是通过插值来生成新的少数类样本,但它会根据样本分布自适应地为“难以学习”的区域生成更多合成样本,帮助分类器更好地学习难区分的边界。
- 优点:更关注困难样本所在的区域,理论上能进一步提升分类效果。
- 缺点:与 SMOTE 相似,插值合成的样本只在已有的少数类分布周围生成,对极端复杂分布仍会存在局限性。
6. 随机欠采样(RandomUnderSampler)
from imblearn.under_sampling import RandomUnderSampler
X_resampled, y_resampled = RandomUnderSampler().fit_resample(X, y)
- RandomUnderSampler 会随机删除多数类样本,直到达到指定比例,使得少数类与多数类平衡。
- 优点:实现简单,速度快,可以显著减少训练数据量,从而降低训练时间。
- 缺点:直接丢弃了大量的多数类样本,可能会丢失有用的信息,导致分类器性能下降。
7. 基于 K-means 聚类的欠采样(ClusterCentroids)
from imblearn.under_sampling import ClusterCentroids
X_resampled, y_resampled = ClusterCentroids().fit_resample(X, y)
- ClusterCentroids 会使用聚类算法(默认 KMeans)将多数类样本聚为若干簇,然后用各簇的“质心”(平均值)来代表这个簇,从而减少多数类样本数量。
- 优点:相比随机欠采样,能够保留更多的多数类“代表性”信息。
- 缺点:在聚类过程中,如果聚类效果不好,依旧可能丢失关键信息。
8. 基于最近邻算法的欠采样(RepeatedEditedNearestNeighbours)
from imblearn.under_sampling import RepeatedEditedNearestNeighbours
X_resampled, y_resampled = RepeatedEditedNearestNeighbours().fit_resample(X, y)
- Edited Nearest Neighbours (ENN) 的原理是:对于多数类中的某些“噪声”或“不典型”样本,如果它在一定数量的最近邻中属于少数类占多数,则认为它是可疑样本,将其剔除。
- RepeatedEditedNearestNeighbours 会将 ENN 的过程重复多次,使得数据集在多数类区域中“清洗”得更干净。
- 优点:逐步“编辑”出一个更纯净、更有代表性的多数类数据子集。
- 缺点:需要进行多轮迭代,执行速度会比随机欠采样慢一些。
9. 基于分类器预测概率的欠采样(InstanceHardnessThreshold)
from sklearn.linear_model import LogisticRegression
from imblearn.under_sampling import InstanceHardnessThreshold
lr_underS = InstanceHardnessThreshold(
random_state=0,
estimator=LogisticRegression()
)
X_resampled, y_resampled = lr_underS.fit_resample(X, y)
- InstanceHardnessThreshold:先训练一个指定的分类器(这里是 LogisticRegression),计算每个样本被预测为所属类别的概率,并对概率低于阈值(即分类器很难正确识别的样本)进行剔除。
- 这种方法相当于用已有模型预测“难样本”(hard instance),若一个多数类样本被模型认为“存在较高错分风险”,则将它剔除掉,减少其对模型训练的干扰。
- 优点:保留对分类器来说“容易”或“典型”的多数类样本,丢弃难样本,提高模型对少数类的学习能力。
- 缺点:需要先训练一个初步模型,对模型的依赖较大,如果模型本身偏差较大,也可能错删有价值的多数类样本。
总结
在 不平衡数据集 的场景下,通常需要结合以下几方面来进行全面的评估和选择:
- 过采样 vs 欠采样:
- 过采样会增加训练集大小,可能提高对少数类的学习能力,但也可能导致过拟合或数据冗余。
- 欠采样则会减少数据量、加快训练速度,但可能丢失信息。
- 不同的过采样方法(RandomOverSampler、SMOTE、Borderline-SMOTE、ADASYN 等)和 欠采样方法(RandomUnderSampler、ClusterCentroids、ENN 系列、IHT 等)对数据的“处理方式”不同,需要根据实际数据分布、模型需求和性能指标来做选择。
- 模型评估指标:对于不平衡场景,需要更多关注 AUC / ROC、Precision / Recall、F1-score 等指标,而不仅仅是准确率(Accuracy)。
- 实际业务场景:在金融风控或评分卡建模中,对不同类型错误(例如,错杀还是误放)会有不同的风险成本,需要在业务需求和模型表现之间做权衡。
可以多做实验,比较不同采样方法对模型指标(如 AUC、F1-score、KS 等)的影响,最终选出最适合业务目标和数据特点的方法。