当我们在做机器学习任务时,会经常遇到这样的数据集,两种样本的数量相差特别悬殊。这就会导致一个问题,在建模的时候,模型会特别注重数量多的样本,而忽视数量少的样本,觉得数量少的样本不够重要。生活中也有许多这样的例子,如在一份患者数据集当中,绝大多数患者都是正常的,只有极少数患者会得癌症。在一份银行贷款数据集中,绝大数用户都能按时还款,只有极少数用户会欠款不还。如果我们我们的模型只关注正常样本,而忽视了这些极少数的异常样本,那么这样的模型是没有什么实际价值的。

银行贷款是一件犯错成本很高的事情,我们需要根据用户所提交的资料来判断是否给该用户贷款以及贷款的金额。如果一个用户能够正常还款,而我们没有给他贷款,我们损失的最多是贷款产生的利息;如果一个用户无法偿还贷款,而我们贷给他一大笔钱,那我们损失的将是整个本金。所以对于该情况下的模型而言,必须能够准确识别出这些极少数的异常样本。
对于机器学习模型而言,重视多数样本,忽视少数样本,这是符合机器甚至是人的认知逻辑的。但是面对这样的问题,我们该如何处理呢?我们希望异常样本和正常样本的数量一样多,这样机器就会认为它们同等重要。要实现正常样本和异常样本数量一样多,有两种方案:
1.下采样:
从多数的正常样本中随机选出与少数样本一样多的数据,组成新的数据集,然后进行建模任务。这里,我们以银行贷款数据集作为例子,该数据集已经做过了脱敏(去敏感信息)处理。
creditcard:链接:https://pan.baidu.com/s/1t5opuhFew5xVP2zpIwfmVA
提取码:khj6
具体实现过程:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
df = pd.read_csv('D:\\Py_dataset\\creditcard.csv')
df.head()#打印出前五行数据进行观察
print('该数据集规模:',df.shape)
该数据集规模: (284807, 31)
df['Class'].value_counts()
0 284315
1 492
Name: Class, dtype: int64
#样本分布可视乎
df['Class'].value_counts(normalize = True).plot(kind = 'bar')
前五行数据信息:

样本分布比例:

下采样方案代码实现:
#异常样本的个数
fraud_number = len(df[df.Class == 1])
#异常样本的索引
fraud_indices = df[df.Class == 1].index
fraud_indices = np.array(fraud_indices)
#正常样本的索引
norm_indices = df[df.Class == 0].index
norm_indices = np.array(norm_indices)
#在所有正常样本中选出与异常样本个数一样多的样本索引
undersample_norm_indices = np.random.choice(norm_indices,fraud_number,replace = False)
undersample_norm_indices = np.array(undersample_norm_indices)
#将下采样正常样本的索引和异常样本的索引进行组合
undersample_indices = np.concatenate((fraud_indices,undersample_norm_indices))
#下采样的样本
undersample_data = df.loc[undersample_indices,:]
#查看下采样正常样本和异常样本的分布
print('下采样数据集的大小:',undersample_indices.shape[0])
print('下采样数据集正样本的个数:',undersample_data[undersample_data.Class == 0].shape[0])
print('下采样数据集负样本的个数:',undersample_data[undersample_data.Class == 1].shape[0])
下采样数据集的大小: 984
下采样数据集正样本的个数: 492
下采样数据集负样本的个数: 492
2.过采样:
让异常样本的数量与正常样本一样多,这就需要对少数样本进行生成,这里的生成可不是复制,一模一样的样本是没有用的。最长用的就是SMOTE数据生成算法。
SMOTE算法的流程如下:
- 1.对于少数类中的每一个样本x,以欧式距离为标准,计算它到少数样本集中所有样本的距离,经过排序,得到其近邻样本。
- 2.根据样本不平衡比例设置一个采样倍率N,对于每一个少数样本x,从其近邻依次选择N个样本,如图(a)所示。
- 3.对于每一个选出的近邻样本,分别与原样本按照如下的公式构建新的样本数据。其中x为少数样本,x^ 为少数样本x的近邻样本,从0~1之间选择一个随机数乘以x与近邻样本x的距离再加上x,即可得到x与x之间的新样本,如图(b)所示。
SMOTE function.jpg
SMOTE sample.jpg
SMOTE算法代码实现:
对于SMOTE算法,可以使用imblearn这个工具包来完成,首先需要在prompt命令行中安装该工具包(pip install imblearn)。对于SMOTE算法而言,只需要传入特征和标签即可得到一个大量的异常样本集。
#提取标签
labels = df['Class']
#提取特征
features = df.drop('Class',axis = 1)
#导入SMOTE算法
from imblearn.over_sampling import SMOTE
oversampler = SMOTE(random_state = 0)
os_features,os_labels = oversampler.fit_sample(features,labels)
#查看原始数据集中正常和异常样本的分布
print('原始数据集正常样本的个数:',df[df['Class'] == 0].shape[0])
print('原始数据集异常样本的个数:',df[df['Class'] == 1].shape[0])
print('原始数据集的长度:',df.shape[0])
原始数据集正常样本的个数: 284315
原始数据集异常样本的个数: 492
原始数据集的长度: 284807
#新构造的数据集中正常样本和异常样本的分布
print('过采样数据集中正常样本的个数:',len(os_labels[os_labels == 0]))
print('过采样数据集中异常样本的个数:',len(os_labels[os_labels == 1]))
print('过采样数据集的长度:',len(os_labels))
过采样数据集中正常样本的个数: 284315
过采样数据集中异常样本的个数: 284315
过采样数据集的长度: 568630
从结果中可以看出异常样本的数量从492个变成了284315个,与正常样本的数量一致。
这一小节,我们总结了处理样本不均衡数据集的两种方法,下采样和过采样。其中不涉及数据的预处理、建模以及模型评估等内容。在具体的任务当中,采样方案与数据预处理、建模、模型评估,参数优化构成了一个整体。
3.采样方案总结:
- 1.下采样方案比较简单,从多数样本中随机选择与少数样本一样多的数据,方便我们处理样本不均衡数据。但下采样方案牺牲了原有数据的丰富性,只使用了一小部分的数据。
- 过采样方案,通过数据生成策略,使得异常样本的数量与正常样本一样多,方便我们处理样本不均衡数据。但生成的数据毕竟是不真实的,是通过一定的规则伪造出来的。
- 3.下采样方案和过采样各有优缺点,在处理具体的任务时,需通过实验对比两种方案的优劣。
网友评论