不平衡样本处理策略
文章目录
前期准备工作
不平衡数据的生成
采用sklearn.datasets的make_classification来生成试验的不平衡数据集。
from sklearn.datasets import make_classification
from collections import Counter
'''
make_classification的参数说明:
n_samples=5000:生成5000个样本
n_features=2:特征数为2
n_informative=2:有用特征数为2
n_redundant=0:冗余特征数为0,冗余特征是有用特征的随机线性组合
n_repeated=0:重复特征数为0,重复特征是有用特征和冗余特征的随机线性组合
n_classes=3:样本类别数为3
n_clusters_per_class=1:每个类别只有1个簇类
weights=[0.01, 0.05, 0.94]:第0,1,2类样本数量比例采用0.01:0.05:0.94,默认为平衡
class_sep=1.5:控制簇类之间距离的参数因子为1.5,越大簇类之间离得越远
random_state=34:随机种子设为34
'''
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=1.5,
random_state=34)
# Counter统计各类别样本出现的次数
Counter(y)
结果
Counter({
0: 65, 2: 4668, 1: 267})
make_classification生成的数据各类别的数量比例接近0.01 : 0.05 : 0.94。
也可以用计数图来可视化展示。
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
# 画出y的计数图
sns.countplot(y)
logistic回归结果可视化
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.linear_model import LogisticRegression
# 把坐标图网格划分
h = .02 # 设置网状间隔为0.2
# 规定数据X两个特征方向的最大最小值
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
# 用meshgrid产生网格数据
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
# ravel()能将多维数组转换为一维数组,它跟flatten()的区别在于:
# ravel()返回的是原数据,flatten()返回的是原数据的拷贝
# np.c_的作用是按列拼接xx.ravel()和yy.ravel()
data_mesh = np.c_[xx.ravel(), yy.ravel()]
# 用逻辑回归做多分类,并画出结果,写成函数方便后续调用
def lr_result_plot(X, y):
# 调用LogisticRegression函数,规定相应的参数
lr = LogisticRegression(solver='sag',
max_iter=100,
random_state=19,
multi_class='multinomial').fit(X, y)
# 输出训练的评价指标结果
print("training score : %.3f ('multinomial')" % lr.score(X, y))
# 将网格数据输入逻辑回归做预测
Z = lr.predict(data_mesh)
# 预测结果变换成与网格数据同样的维度
Z = Z.reshape(xx.shape)
plt.figure()
# contour和contourf都可以画三维等高线图
# 不同点在于contour()是绘制轮廓线,contourf()会填充轮廓
plt.contourf(xx, yy, Z, cmap=plt.cm.Set2)
plt.title("Decision surface of LogisticRegression ('multinomial')")
plt.axis('tight')
# 画样本的散点图,用不同颜色区分不同类的样本
colors = "rby"
for i,color in zip(lr.classes_, colors):
# 查找类别i的所有样本的所在行数
idx = np.where(y == i)
# 画出样本的坐标图
plt.scatter(X[idx, 0], X[idx, 1], c=color, cmap=plt.cm.hot,
edgecolor='black', s=20)
lr_result_plot(X, y)
分类结果如下图
红蓝黄3种颜色代表不同类别的样本,不同颜色的背景表示Logistic回归的分类结果。
后面只介绍不平衡数据处理方法中的数据处理方法。
过采样
简单过采样方法
简单过采样,即从少数类的样本中随机采样来增加新的样本,api使用RandomOverSampler。
from imblearn.over_sampling import RandomOverSampler
ros = RandomOverSampler(random_state=0)
X_over_resampled, y_over_resampled = ros.fit_sample(X, y)
Counter(y_over_resampled)
结果如下
Counter({
0: 4668, 2: 4668, 1: 4668})
简单过采样出来的结果,各类别的数量占比是1:1:1。
# 画出逻辑回归结果
lr_result_plot(X_over_resampled, y_over_resampled)
可以看到Logistic分类平面明显向类3样本(黄色)方向推进了。
因为简单过采样是重复采样,出来的样本在图中都是重叠的,所以少数类看起来数量没有变化,其实3类数据是一样多的。
简单过采样方法容易导致过拟合(因为少数类样本重复了很多次,训练时相当于重复学习了很多次那一小撮少数类样本的特征),为避免简单重复过采样,于是有了SMOTE算法。
SMOTE
SMOTE(Synthetic Minority Oversampling Tenchnique)是一种不重复的过采样算法,它的算法步骤如下:
-
算法只接收少数类样本;
-
每个少数类样本搜索K个最近的样本;
-
根据所需要的采样倍数,从K个最近邻中随机抽取M个样本;
-
合成的样本随机地生成在少数类样本与其M个近邻点之间的连线上。
api采用imblearn.over_sampling的SMOTE,具体使用如下:
from imblearn.over_sampling import SMOTE
smt = SMOTE()
X_smote, y_smote = smt.fit_sample(X,y)
Counter(y_smote)
结果
Counter({
0: 4668, 2: 4668, 1: 4668})
SMOTE算法生成的数据是平衡的。
# 画出逻辑回归结果
lr_result_plot(X_smote, y_smote)
因为SMOTE不是简单重复采样,所以从图可以很明显看出,类0样本(红色)和类1样本(蓝色)的数量有所增多。但是,图中比之前出现了更多的噪声(合成的样本跑到其他类别堆里),这是因为如果存在异常点,异常点与其近邻合成的样本很可能会变成噪声。
这是SMOTE算法非常致命的缺点,它加大了后续模型对样本的分类难度。
SMOTE算法的另一个问题是,远离两类数据边界的点合成的样本对模型分类几乎没有贡献,这是因为很多分类模型(如SVM、决策树等)都依赖于边界附近的样本做判断。
Borderline-SMOTE
为克服SMOTE算法的缺点,于是有论文提出了一种改进算法Borderline-SMOTE。
Borderline-SMOTE的思路是每个少数类样本搜索 m \text{m} m个最近邻,依据K个最近邻中多数类样本的个数 m ′ \text{m}'