SMOTE算法进行过采样

算法思想

  • 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

class SMOTE(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)

    def get_neighbor_point(self):
        for index,single_signal in enumerate(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]

    def get_syn_data(self):
        self.get_neighbor_point()
        for i in range(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

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值