机器学习基础-监督学习-标签平衡处理之 SMOTE(Synthetic Minority Over-sampling Technique)
SMOTE(Synthetic Minority Over-sampling Technique)是一种基于 K 近邻的过采样方法,它可以根据少数类样本之间的距离,产生一些新的少数类样本,从而解决数据集不平衡的问题。SMOTE 方法可以有效地避免过拟合问题,同时不会改变数据集中的总体样本数。
SMOTE 方法的主要思想是对于每个少数类样本 x,从它的 K 个最近邻中随机选择一个样本 x',并根据以下公式产生新的合成样本:
其中,λ\lambdaλ 是一个 [0,1] 之间的随机数,用于控制新生成的样本在原有样本和其最近邻之间的位置。
具体地,SMOTE 方法的实现过程如下:
-
对于每个少数类样本 xix_ixi,计算它的 kkk 个最近邻样本。
-
对于每个最近邻样本 xi,nnx_{i,nn}xi,nn,根据上述公式产生一个新的合成样本 xnewx_{new}xnew。
-
将所有合成样本添加到原始数据集中。
下面是一个使用 Python 实现 SMOTE 方法的示例代码:
from sklearn.neighbors import NearestNeighbors
import numpy as np
def SMOTE(X, y, k=5, ratio=1.0):
"""
:param X: 原始数据集
:param y: 标签
:param k: KNN 参数
:param ratio: 需要过采样的数量与少数类样本数量之间的比例
:return: 过采样后的数据集和标签
"""
# 找出所有少数类样本的索引
minority_class = np.where(y == 1)[0]
majority_class = np.where(y == 0)[0]
# 计算需要生成的新样本数量
n_minority_samples = len(minority_class)
n_synthetic_samples = int(ratio * n_minority_samples)
# 计算每个少数类样本的 k 个最近邻样本
knn = NearestNeighbors(n_neighbors=k, n_jobs=-1)
knn.fit(X[minority_class])
neighbors = knn.kneighbors(return_distance=False)
# 生成新样本
synthetic_samples = np.zeros((n_synthetic_samples, X.shape[1]))
for i, j in enumerate(np.random.choice(len(minority_class), n_synthetic_samples)):
nn = np.random.choice(neighbors[j])
dif = X[minority_class[nn]] - X[minority_class[j]]
gap = np.random.rand() * dif
synthetic_samples[i, :] = X[minority_class[j], :] + gap
# 将合成样本添加到原始数据集中
X_resampled = np.vstack((X, synthetic_samples))
y_resampled = np.hstack((y, np.ones(n_synthetic_samples)))
return X_resampled, y_resampled
需要注意的是,SMOTE 方法也有一些局限性。例如,当少数类样本分布极其不均匀时,SMOTE 方法可能会产生一些不太合理的合成样本。此外,当 k 值较小时,SMOTE 方法可能会产生一些与少数类样本过于相似的合成样本,从而导致过拟合问题。
为了解决这些问题,一些改进的 SMOTE 方法已经被提出,例如 Borderline-SMOTE 和 ADASYN 等。
下面是一个使用 Python 实现 Borderline-SMOTE 方法的示例代码:
from sklearn.neighbors import NearestNeighbors
import numpy as np
def BorderlineSMOTE(X, y, k=5, ratio=1.0, kind='borderline1'):
"""
:param X: 原始数据集
:param y: 标签
:param k: KNN 参数
:param ratio: 需要过采样的数量与少数类样本数量之间的比例
:param kind: Borderline-SMOTE 类型,可选值为 'borderline1' 或 'borderline2'
:return: 过采样后的数据集和标签
"""
# 找出所有少数类样本的索引
minority_class = np.where(y == 1)[0]
majority_class = np.where(y == 0)[0]
# 计算需要生成的新样本数量
n_minority_samples = len(minority_class)
n_synthetic_samples = int(ratio * n_minority_samples)
# 计算每个少数类样本的 k 个最近邻样本
knn = NearestNeighbors(n_neighbors=k, n_jobs=-1)
knn.fit(X[minority_class])
neighbors = knn.kneighbors(return_distance=False)
# 计算每个少数类样本的半径
distances, indices = knn.kneighbors(X[minority_class])
radii = distances.sum(axis=1) / k
# 生成新样本
synthetic_samples = np.zeros((n_synthetic_samples, X.shape[1]))
for i, j in enumerate(np.random.choice(len(minority_class), n_synthetic_samples)):
nn = np.random.choice(neighbors[j])
dif = X[minority_class[nn]] - X[minority_class[j]]
gap = np.random.rand() * dif
synthetic_samples[i, :] = X[minority_class[j], :] + gap
# 如果生成的样本距离其 k 个最近邻样本的平均距离大于样本半径,则将其标记为噪声样本
if kind == 'borderline1' and (distances[j, :] > radii[j]).sum() > 0:
synthetic_samples[i, :] = X[minority_class[j], :]
# 如果生成的样本距离其 k 个最近邻样本的平均距离小于样本半径,则将其标记为危险样本
elif kind == 'borderline2' and ((distances[j, :] > radii[j]).sum() > 0 and (distances[j, :] <= radii[j]).sum() > 0):
synthetic_samples[i, :] = X[minority_class[j], :]
# 将生成的新样本添加到数据集中
X = np.vstack((X, synthetic_samples))
y = np.hstack((y, np.ones(n_synthetic_samples)))
return X, y
在这个示例代码中,我们首先找出了所有少数类样本的索引,然后计算了需要生成的新样本数量。接着,我们使用 KNN 找出了每个少数类样本的 k 个最近邻样本,并计算了每个样本的半径。在生成新样本时,我们首先随机选择一个少数类样本作为基准样本,然后从它的 k 个最近邻样本中随机选择一个样本作为合成样本的参考样本。然后,我们计算了参考样本与基准样本之间的差异,并随机生成一个比例因子,将差异乘以这个比例因子,得到合成样本的特征值。最后,我们使用 Borderline-SMOTE 的规则判断合成样本是否为噪声样本或危险样本,并将其标记为相应的类型。
需要注意的是,Borderline-SMOTE 方法和 SMOTE 方法一样,也需要在训练集上使用。因此,在使用 Borderline-SMOTE 方法时,需要将原始数据集分成训练集和测试集,并只在训练集上使用 Borderline-SMOTE 方法进行过采样。
转载自:https://juejin.cn/post/7230728447206473786