类不平衡问题

数据挖掘 分类 阶级失衡 分类器
2022-03-08 02:51:10

我正在制作一个包含 5 个类的多分类器模型。(不过,在我的问题中,它是 2 类还是 5 类并不重要)。

班级分布很不平衡。
在此处输入图像描述

因此,我对过采样进行了重新采样以匹配每个类的数量。 在此处输入图像描述

我的问题是。如果我为过采样(引导重采样或 SMOTE)重新采样类,是否存在任何统计错误或问题?

谁能解释这种方法?

提前致谢。

1个回答

这取决于您的少数群体中的数据。

每个类中的数据可以被认为是来自某些人群的观察样本。该样本可能或可能不能很好地代表该类中的整个实例群。

如果样本很好地代表了总体,那么过采样只会引入一个小错误。但是,如果样本不能很好地代表总体,那么过采样将产生具有不同于总体统计特性的数据。

所有估计值(如置信区间预测区间)都是根据样本的统计属性(均值、方差等)计算得出的,不同分布和学习算法的精确计算不同。如果过采样数据的统计属性与其总体的统计属性不同,您将得到模型的置信区间和预测区间的错误估计。

我将用一个例子来说明这一点。

假设您有属于 2 个类的二维数据(每个观测值中有两个特征)。每个类中的数据呈正态分布,每个特征的标准差 = 1。类 1 的总体平均值为 (-2, 0)。第 2 类的总体平均值为 (1, 0)。

我说明了一个很大的人口,每个班级拿 500 分。这些类可以通过逻辑回归线分开,如下所示:

在此处输入图像描述

回归线几乎正好在总体均值之间并且几乎是垂直的,因为总体均值的两个纵坐标都为零。如果我取几千分,那么它将是垂直的。

这张图片的代码:

import numpy as np 
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression

n_points1 = 500
n_points2 = 500
mu1 = np.array([-2,0])
mu2 = np.array([1,0])

sig = 1

cl1 = sig * np.random.randn(n_points1,2) + mu1
cl2 = sig * np.random.randn(n_points2,2) + mu2

print('Red points:')
print('mu_x = %.3f, mu_y = %.3f, std_x = %.3f, std_y = %.3f' 
    % (np.mean(cl2[:,0]), np.mean(cl2[:,1]), np.std(cl2[:,0]), np.std(cl2[:,1])) )

y1 = np.zeros((cl1.shape[0],1))
y2 = np.ones((cl2.shape[0],1))

X = np.vstack((cl1,cl2))
y = np.vstack((y1,y2))

logreg = LogisticRegression()

logreg.fit(X, y.ravel())
w = logreg.coef_[0]
a = -w[0] / w[1]

xx = np.linspace(-4,4)
yy = a * xx - (logreg.intercept_[0]) / w[1]

plt.scatter(cl1[:,0], cl1[:,1], s = 50, marker='.', color='green')
plt.scatter(cl2[:,0], cl2[:,1], s = 50, marker='.', color='red')
plt.plot(xx, yy, 'b-')
plt.xlim((-5, 4))   
plt.ylim((-5, 5))
plt.grid()
plt.show() 

现在,让我们假设红色类的代表性不足(少数类)。我绿色课得500分,红色课得10分。然后我通过两种方法对红色数据进行过采样。一种是将它们复制 50 次,例如引导重采样(我将它们涂成红色),另一种是 SMOTE(它们是洋红色)。这不完全是 SMOTE。我只是添加了位于红色数据点之间的数据。在这个简单的示例中,我懒得计算每个观察值的最近邻,但它仍然说明了 SMOTE,因为在 SMOTE 中,合成示例是在现有少数示例的凸包内生成的,这减少了数据的方差。

这就是我得到的:

在此处输入图像描述

蓝线用于引导,黑线用于 SMOTE。少数类的样本均值和标准差如下:

Red points:
mu_x = 1.600, mu_y = -0.182, std_x = 0.719, std_y = 0.753

Magenta points:
mu_x = 1.625, mu_y = -0.174, std_x = 0.513, std_y = 0.550

样本均值的 x 分量被高估(应该 = 1),而标准差被低估(应该 = 1)。结果,划分类别的线与上一张图片上的真实线不同,因此很多新数据会被错误分类。

让我们从同一人群中随机抽取几个样本。同样,少数类的大小是 10,我使用相同的两种方法对它们进行重新采样。

在此处输入图像描述

Red points:
mu_x = 0.944, mu_y = 0.289, std_x = 0.943, std_y = 0.867

Magenta points:
mu_x = 0.974, mu_y = 0.298, std_x = 0.700, std_y = 0.617

这次样本均值的x分量还可以,它的y分量被高估了一点,标准差被低估了。分隔线再次不正确。

在此处输入图像描述

Red points:
mu_x = 0.922, mu_y = -0.236, std_x = 1.066, std_y = 1.097

Magenta points:
mu_x = 0.924, mu_y = -0.244, std_x = 0.757, std_y = 0.749

在最后一张图片中,我们很幸运地得到了一个与总体具有几乎相同的均值和标准差的样本。因此,分隔线非常接近第一张图片中数据平衡的线。请注意,SMOTE 的标准偏差始终较小,因为新数据是在现有数据之间添加的,而不是在现有数据之外。

您可能会考虑欠采样而不是过采样。检查此链接

最后3张图片的代码:

import numpy as np 
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression

n_points1 = 500
n_points2 = 10
mu1 = np.array([-2,0])
mu2 = np.array([1,0])

sig = 1

cl1 = sig * np.random.randn(n_points1,2) + mu1
cl2 = sig * np.random.randn(n_points2,2) + mu2

cl2 = np.tile(cl2,(n_points1//n_points2,1)) # oversampling 

# A kind of SMOTE but not quite
cl3 = np.zeros_like(cl1)
for k in range(len(cl3)):
    i = np.random.randint(low=0, high=n_points2, size=2)
    cl3[k,:] = (cl2[i[0]]+cl2[i[1]])/2

print('Red points:')
print('mu_x = %.3f, mu_y = %.3f, std_x = %.3f, std_y = %.3f' 
    % (np.mean(cl2[:,0]), np.mean(cl2[:,1]), np.std(cl2[:,0]), np.std(cl2[:,1])) )

print('Magenta points:')
print('mu_x = %.3f, mu_y = %.3f, std_x = %.3f, std_y = %.3f' 
    % (np.mean(cl3[:,0]), np.mean(cl3[:,1]), np.std(cl3[:,0]), np.std(cl3[:,1])) )

y1 = np.zeros((cl1.shape[0],1))
y2 = np.ones((cl2.shape[0],1))

X = np.vstack((cl1,cl2))
X3 = np.vstack((cl1,cl3))

y = np.vstack((y1,y2))

logreg = LogisticRegression()

logreg.fit(X, y.ravel())
w = logreg.coef_[0]
a = -w[0] / w[1]

xx = np.linspace(-4,4)
yy = a * xx - (logreg.intercept_[0]) / w[1]

plt.scatter(cl1[:,0], cl1[:,1], s = 50, marker='.', color='green')
plt.scatter(cl2[:,0], cl2[:,1], s = 50, marker='.', color='red')
plt.plot(xx, yy, 'b-')

logreg.fit(X3, y.ravel())
w = logreg.coef_[0]
a = -w[0] / w[1]
yy = a * xx - (logreg.intercept_[0]) / w[1]

plt.scatter(cl3[:,0], cl3[:,1], s = 10, marker='.', color='magenta')
plt.plot(xx, yy, 'k-')
plt.xlim((-5, 4))   
plt.ylim((-5, 5))
plt.grid()
plt.show()