2. 过采样#

2.1. 实用指南#

你可以参考 比较过采样采样器

2.1.1. 朴素随机过采样#

解决这个问题的一种方法是在代表性不足的类别中生成新样本。最天真的策略是通过随机抽样替换当前可用的样本来生成新样本。RandomOverSampler 提供了这样的方案:

>>> 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
>>> ros = RandomOverSampler(random_state=0)
>>> X_resampled, y_resampled = ros.fit_resample(X, y)
>>> from collections import Counter
>>> print(sorted(Counter(y_resampled).items()))
[(0, 4674), (1, 4674), (2, 4674)]

增强数据集应代替原始数据集用于训练分类器:

>>> from sklearn.linear_model import LogisticRegression
>>> clf = LogisticRegression()
>>> clf.fit(X_resampled, y_resampled)
LogisticRegression(...)

在下图中,我们比较了使用过采样数据集和原始数据集训练的决策函数。

_images/sphx_glr_plot_comparison_over_sampling_002.png

因此,在训练过程中,多数类不会覆盖其他类。因此,所有类都由决策函数表示。

此外,RandomOverSampler 允许对异构数据进行采样 (例如包含一些字符串):

>>> import numpy as np
>>> X_hetero = np.array([['xxx', 1, 1.0], ['yyy', 2, 2.0], ['zzz', 3, 3.0]],
...                     dtype=object)
>>> y_hetero = np.array([0, 0, 1])
>>> X_resampled, y_resampled = ros.fit_resample(X_hetero, y_hetero)
>>> print(X_resampled)
[['xxx' 1 1.0]
 ['yyy' 2 2.0]
 ['zzz' 3 3.0]
 ['zzz' 3 3.0]]
>>> print(y_resampled)
[0 0 1 1]

它也可以与pandas dataframe一起使用:

>>> from sklearn.datasets import fetch_openml
>>> df_adult, y_adult = fetch_openml(
...     'adult', version=2, as_frame=True, return_X_y=True)
>>> df_adult.head()  
>>> df_resampled, y_resampled = ros.fit_resample(df_adult, y_adult)
>>> df_resampled.head()  

如果重复样本是一个问题,参数 shrinkage 允许创建一个平滑的引导样本。然而,原始数据需要是数值型的。shrinkage 参数控制新生成样本的分散程度。我们展示了一个示例,说明一旦使用平滑引导,新样本不再重叠。这种生成平滑引导样本的方式也被称为随机过采样示例 (ROSE) [MT14]

_images/sphx_glr_plot_comparison_over_sampling_003.png

2.1.2. 从随机过采样到SMOTE和ADASYN#

除了随机抽样替换外,还有两种流行的方法来对少数类进行过采样:(i) 合成少数类过采样技术(SMOTE)[CBHK02] 和 (ii) 自适应合成(ADASYN)[HBGL08] 采样方法。这些算法可以以相同的方式使用:

>>> from imblearn.over_sampling import SMOTE, ADASYN
>>> X_resampled, y_resampled = SMOTE().fit_resample(X, y)
>>> print(sorted(Counter(y_resampled).items()))
[(0, 4674), (1, 4674), (2, 4674)]
>>> clf_smote = LogisticRegression().fit(X_resampled, y_resampled)
>>> X_resampled, y_resampled = ADASYN().fit_resample(X, y)
>>> print(sorted(Counter(y_resampled).items()))
[(0, 4673), (1, 4662), (2, 4674)]
>>> clf_adasyn = LogisticRegression().fit(X_resampled, y_resampled)

下图展示了不同过采样方法的主要区别。

_images/sphx_glr_plot_comparison_over_sampling_004.png

2.1.3. 不适定示例#

虽然RandomOverSampler通过对少数类的一些原始样本进行复制来进行过采样,SMOTEADASYN则通过插值生成新样本。然而,用于插值/生成新合成样本的样本有所不同。事实上,ADASYN专注于在原始样本附近生成样本,这些样本在使用k-最近邻分类器时被错误分类,而SMOTE的基本实现不会在使用最近邻规则进行分类时区分简单和困难的样本。因此,训练期间找到的决策函数在算法之间会有所不同。

_images/sphx_glr_plot_comparison_over_sampling_005.png

这两种算法的采样特性可能会导致一些特殊的行为,如下所示。

_images/sphx_glr_plot_comparison_over_sampling_006.png

2.1.4. SMOTE变体#

SMOTE 可能会连接内点和离群点,而 ADASYN 可能只关注离群点,这两种情况都可能导致次优的决策函数。在这方面,SMOTE 提供了三种额外的选项来生成样本。这些方法专注于接近最优决策函数边界的样本,并将在最近邻类的相反方向上生成样本。这些变体在下图中展示。

_images/sphx_glr_plot_comparison_over_sampling_007.png

BorderlineSMOTE [HWM05], SVMSMOTE [NCK09], 和 KMeansSMOTE [LDB17] 提供了一些SMOTE算法的变体:

>>> from imblearn.over_sampling import BorderlineSMOTE
>>> X_resampled, y_resampled = BorderlineSMOTE().fit_resample(X, y)
>>> print(sorted(Counter(y_resampled).items()))
[(0, 4674), (1, 4674), (2, 4674)]

在处理混合数据类型(如连续和分类特征)时,除了RandomOverSampler类之外,所展示的方法都无法处理分类特征。SMOTENC [CBHK02]SMOTE算法的扩展,它对分类数据的处理方式不同:

>>> # create a synthetic data set with continuous and categorical features
>>> rng = np.random.RandomState(42)
>>> n_samples = 50
>>> X = np.empty((n_samples, 3), dtype=object)
>>> X[:, 0] = rng.choice(['A', 'B', 'C'], size=n_samples).astype(object)
>>> X[:, 1] = rng.randn(n_samples)
>>> X[:, 2] = rng.randint(3, size=n_samples)
>>> y = np.array([0] * 20 + [1] * 30)
>>> print(sorted(Counter(y).items()))
[(0, 20), (1, 30)]

在这个数据集中,第一个和最后一个特征被视为分类特征。需要通过参数 categorical_features 将这些信息提供给 SMOTENC,可以通过传递索引、当 X 是 pandas DataFrame 时的特征名称、标记这些特征的布尔掩码,或者如果列使用 pandas.CategoricalDtype 时依赖 dtype 推断:

>>> from imblearn.over_sampling import SMOTENC
>>> smote_nc = SMOTENC(categorical_features=[0, 2], random_state=0)
>>> X_resampled, y_resampled = smote_nc.fit_resample(X, y)
>>> print(sorted(Counter(y_resampled).items()))
[(0, 30), (1, 30)]
>>> print(X_resampled[-5:])
[['A' 0.19... 2]
 ['B' -0.36... 2]
 ['B' 0.87... 2]
 ['B' 0.37... 2]
 ['B' 0.33... 2]]

因此,可以看出,第一列和最后一列生成的样本最初属于相同的类别,没有任何额外的插值。

然而,SMOTENC 仅在数据是数值和分类特征的混合时有效。如果数据仅由分类数据组成,可以使用 SMOTEN 变体 [CBHK02]。该算法在两个方面有所变化:

  • 最近邻搜索不依赖于欧几里得距离。实际上,类ValueDifferenceMetric中实现的值差异度量(VDM)被使用。

  • 生成一个新样本,其中每个特征值对应于属于同一类的邻居样本中最常见的类别。

让我们来看下面的例子:

>>> import numpy as np
>>> X = np.array(["green"] * 5 + ["red"] * 10 + ["blue"] * 7,
...              dtype=object).reshape(-1, 1)
>>> y = np.array(["apple"] * 5 + ["not apple"] * 3 + ["apple"] * 7 +
...              ["not apple"] * 5 + ["apple"] * 2, dtype=object)

我们生成了一个数据集,将颜色与是否为苹果相关联。 我们强烈地将“绿色”和“红色”与苹果相关联。由于少数类别是“非苹果”,我们期望生成的新数据属于“蓝色”类别:

>>> from imblearn.over_sampling import SMOTEN
>>> sampler = SMOTEN(random_state=0)
>>> X_res, y_res = sampler.fit_resample(X, y)
>>> X_res[y.size:]
array([['blue'],
        ['blue'],
        ['blue'],
        ['blue'],
        ['blue'],
        ['blue']], dtype=object)
>>> y_res[y.size:]
array(['not apple', 'not apple', 'not apple', 'not apple', 'not apple',
       'not apple'], dtype=object)

2.2. 数学公式#

2.2.1. 样本生成#

无论是 SMOTE 还是 ADASYN 都使用相同的算法来生成新样本。考虑一个样本 \(x_i\),一个新样本 \(x_{new}\) 将根据其 k 个最近邻居(对应于 k_neighbors)生成。例如,3 个最近邻居包含在下图所示的蓝色圆圈中。然后,选择其中一个最近邻居 \(x_{zi}\),并生成一个样本,如下所示:

\[x_{new} = x_i + \lambda \times (x_{zi} - x_i)\]

其中 \(\lambda\) 是范围在 \([0, 1]\) 内的随机数。这种插值将在 \(x_{i}\)\(x_{zi}\) 之间的线上创建一个样本,如下图所示:

_images/sphx_glr_plot_illustration_generation_sample_001.png

SMOTE-NC 通过为分类特征执行特定操作,略微改变了生成新样本的方式。事实上,新生成样本的类别是通过在生成过程中选择最近邻中出现最频繁的类别来决定的。

警告

请注意,SMOTE-NC 并不是设计用来仅处理分类数据的。

其他SMOTE变体和ADASYN的不同之处在于它们在生成新样本之前选择样本\(x_i\)

常规的SMOTE算法——参见SMOTE对象——不会施加任何规则,并且会随机选择所有可能的\(x_i\)

边界线 SMOTE — 参考 BorderlineSMOTE 使用参数 kind='borderline-1'kind='borderline-2' — 将每个样本 \(x_i\) 分类为 (i) 噪声(即所有最近邻都来自与 \(x_i\) 不同的类别),(ii) 危险(即至少一半的最近邻来自与 \(x_i\) 相同的类别),或 (iii) 安全(即所有最近邻都来自与 \(x_i\) 相同的类别)。Borderline-1Borderline-2 SMOTE 将使用危险样本来生成新样本。在 Borderline-1 SMOTE 中,\(x_{zi}\) 将属于与样本 \(x_i\) 相同的类别。相反,Borderline-2 SMOTE 将考虑 \(x_{zi}\),它可以来自任何类别。

SVM SMOTE — 参见 SVMSMOTE — 使用SVM分类器来找到支持向量并生成样本。注意,SVM分类器的 C 参数可以选择更多或更少的支持向量。

对于边界线和SVM SMOTE,使用参数m_neighbors来定义一个邻域,以决定一个样本是处于危险、安全还是噪声状态。

KMeans SMOTE — 参见 KMeansSMOTE — 在应用SMOTE之前使用KMeans聚类方法。聚类将样本分组并根据簇密度生成新样本。

ADASYN 的工作方式与常规的 SMOTE 类似。然而,为每个 \(x_i\) 生成的样本数量与给定邻域中不属于同一类的样本数量成比例。因此,在最近邻规则未被遵守的区域将生成更多的样本。参数 m_neighbors 等同于 SMOTE 中的 k_neighbors

2.2.2. 多类管理#

所有算法都可以用于多类分类以及二分类。RandomOverSampler 在样本生成过程中不需要任何类间信息。因此,每个目标类都是独立重新采样的。相反,ADASYNSMOTE 都需要关于每个样本邻域的信息用于样本生成。它们通过选择每个目标类并针对数据集中的其余部分(这些部分被分组到一个类中)计算必要的统计量,使用一对多的方法。