算法思想
- SMOTE全称是(Synthetic Minority Oversampling Technique),即合成少数类过采样技术,它是基于随机过采样算法的一种改进方案,由于随机过采样采取简单复制样本的策略来增加少数类样本,这样容易产生模型过拟合的问题,即使得模型学习到的信息过于特别(Specific)而不够泛化(General)。
- SMOTE算法的思想是合成新的少数类样本,合成的策略是对每个少数类样本a,从它的最近邻中随机选一个样本b,然后在a、b之间的连线上随机选一点作为新合成的少数类样本。
算法流程
- 对于少数类中每一个样本a,以欧氏距离为标准计算它到少数类样本集中所有样本的距离,得到其k近邻。
- 根据样本不平衡比例设置一个采样比例以确定采样倍率N,对于每一个少数类样本Xᵢ ,从其k近邻中随机选择若干个样本,假设选择的近邻为X
- 对于每一个随机选出的近邻X,分别与原样本Xᵢ 按照如下的公式构建新的样本
- 新样本 = X + rand(0,1) × (Xᵢ - X) 其中,rand(0,1)的范围在 [0, 1] 之间,用于控制合成样本的位置。 对于多个少数类样本,重复以上公式的步骤,即可生成相应数量的合成样本来平衡数据集。
类别不平衡问题
- 类别不平衡问题,顾名思义,即数据集中存在某一类样本,其数量远多于或远少于其他类样本,从而导致一些机器学习模型失效的问题。例如逻辑回归即不适合处理类别不平衡问题,例如逻辑回归在欺诈检测问题中,因为绝大多数样本都为正常样本,欺诈样本很少,逻辑回归算法会倾向于把大多数样本判定为正常样本,这样能达到很高的准确率,但是达不到很高的召回率。
- 类别不平衡问题在很多场景中存在,例如欺诈检测,风控识别,在这些样本中,黑样本(一般为存在问题的样本)的数量一般远少于白样本(正常样本)。
- 上采样(过采样)和下采样(欠采样)策略是解决类别不平衡问题的基本方法之一。这是解决数据类别不平衡的最简单、最暴力的方法。
- 欠采样 如果负样本太多,那就对负样本进行欠采样,就是随机的从负样本中抽取一部分样本,然后与正样本合并成训练集丢给模型训练。这样有个很明显的弊端,就是会造成严重的信息损失,数据收集不易,你还要丢弃一部分,很显然不合理。
- 过采样 如果正样本太少,那就对正样本进行过采样,就是对正样本进行复制,或者如果是NLP、CV任务,可以做一些数据增强,以此来增加正样本的数量。但是对于一般的任务来说,简单的对正样本进行复制,以此来达到增加正样本数量的目的,这样会使模型在这正样本上过拟合,因为模型’看到太多次这样的样本。就像你如果复习同一道题太多次,答案都背住了,所以看到类似的题就直接写答案,不会变通显然是不对的。
- SMOTE也是基于采样的方法,但是SMOTE可以降低过拟合的风险。过采样是直接对样本进行复制,导致训练集重复样本太多,而SMOTE则不是直接复制,而是生成与正样本相似并且训练集中没有的样本。
SMOTE算法缺陷
- 在近邻选择时,存在一定的盲目性。从上面的算法流程可以看出,在算法执行过程中,需要确定K值,即选择多少个近邻样本,这需要用户自行解决。从K值的定义可以看出,K值的下限是M值(M值为从K个近邻中随机挑选出的近邻样本的个数,且有M< K),M的大小可以根据负类样本数量、正类样本数量和数据集最后需要达到的平衡率决定。但K值的上限没有办法确定,只能根据具体的数据集去反复测试。因此如何确定K值,才能使算法达到最优这是未知的。
- 该算法无法克服非平衡数据集的数据分布问题,容易产生分布边缘化问题。由于负类样本的分布决定了其可选择的近邻,如果一个负类样本处在负类样本集的分布边缘,则由此负类样本和相邻样本产生的“人造”样本也会处在这个边缘,且会越来越边缘化,从而模糊了正类样本和负类样本的边界,而且使边界变得越来越模糊。这种边界模糊性,虽然使数据集的平衡性得到了改善,但加大了分类算法进行分类的难度。
代码示例
import numpy as np
import random
import matplotlib.pyplot as plt
classSMOTE(object):def__init__(self,sample,k=2,gen_num=3):
self.sample = sample
self.sample_num,self.feature_len = self.sample.shape
self.k =min(k,self.sample_num-1)
self.gen_num = gen_num
self.syn_data = np.zeros((self.gen_num,self.feature_len))
self.k_neighbor = np.zeros((self.sample_num,self.k),dtype=int)defget_neighbor_point(self):for index,single_signal inenumerate(self.sample):
Euclidean_distance = np.array([np.sum(np.square(single_signal-i))for i in self.sample])
Euclidean_distance_index = Euclidean_distance.argsort()
self.k_neighbor[index]= Euclidean_distance_index[1:self.k+1]defget_syn_data(self):
self.get_neighbor_point()for i inrange(self.gen_num):
key = random.randint(0,self.sample_num-1)
K_neighbor_point = self.k_neighbor[key][random.randint(0,self.k-1)]
gap = self.sample[K_neighbor_point]- self.sample[key]
self.syn_data[i]= self.sample[key]+ random.uniform(0,1)*gap
return self.syn_data
if __name__ =='__main__':
data = np.random.uniform(0,1, size=[20,2])# 随机生成原始数据
Syntheic_sample = SMOTE(data,5,100)
new_data = Syntheic_sample.get_syn_data()print('原数据',data)print('生成数据',new_data)for i in data:
plt.scatter(i[0], i[1], c='b')# 绘制原始数据for i in new_data:
plt.scatter(i[0], i[1], c='r', marker='^')# 绘制生成数据
plt.show()
参考
https://blog.csdn.net/chrnhao/article/details/124045702
https://www.cnblogs.com/june0507/p/11726492.html
本文转载自: https://blog.csdn.net/glorious69/article/details/140668975
版权归原作者 Gloriouszh 所有, 如有侵权,请联系我们删除。
版权归原作者 Gloriouszh 所有, 如有侵权,请联系我们删除。