Python数据分析与数据化运营

3.4 解决样本类别分布不均衡的问题

Author
宋天龙
发布于 2018-04-02
2691 次阅读
0 次赞
0 次分享
3.4 解决样本类别分布不均衡的问题
AI 智能核心导读

样本类别分布不均衡易导致分类模型过拟合与鲁棒性差,常见于异常检测、客户流失等场景。主要解决方案包括:过抽样与欠抽样(如SMOTE)、调整样本惩罚权重、组合集成方法(如EasyEnsemble)及特征选择。在Python实操中,可借助imbalanced-learn和scikit-learn库高效完成数据均衡处理,从而提升模型的准确性。

说明:本文是《Python 数据分析与数据化运营》中的“3.4 解决样本类别分布不均衡的问题”。

-----------------------------下面是正文内容--------------------------

所谓的不平衡指的是不同类别的样本量异非常大。样本类别分布不平衡主要出现在分类相关的建模问题上。样本类别分布不均衡从数据规模上可以分为大数据分布不均衡和小数据分布不均衡两种。

  • 大数据分布不均衡。这种情况下整体数据规模大,只是其中的少样本类的占比较少。但是从每个特征的分布来看,小样本也覆盖了大部分或全部的特征。例如拥有 1000 万条记录的数据集中,其中占比 50 万条的少数分类样本便于属于这种情况。
  • 小数据分布不均衡。这种情况下整体数据规模小,并且占据少量样本比例的分类数量也少,这会导致特征分布的严重不平衡。例如拥有 1000 条数据样本的数据集中,其中占有 10 条样本的分类,其特征无论如何拟合也无法实现完整特征值的覆盖,此时属于严重的数据样本分布不均衡。

样本分布不均衡将导致样本量少的分类所包含的特征过少,并很难从中提取规律;即使得到分类模型,也容易产生过度依赖于有限的数据样本而导致过拟合的问题,当模型应用到新的数据上时,模型的准确性和鲁棒性将很差。

样本分布不平衡主要在于不同类别间的样本比例差异,以笔者的工作经验看,如果不同分类间的样本量差异达到超过 10 倍就需要引起警觉并考虑处理该问题,超过 20 倍就要一定要解决该问题。

3.4.1 哪些运营场景中容易出现样本不均衡

在数据化运营过程中,以下场景会经常产生样本分布不均衡的问题:

  • 异常检测场景。大多数企业中的异常个案都是少量的,比如恶意刷单、黄牛订单、信用卡欺诈、电力窃电、设备故障等,这些数据样本所占的比例通常是整体样本中很少的一部分,以信用卡欺诈为例,刷实体信用卡的欺诈比例一般都在 0.1% 以内。
  • 客户流失场景。大型企业的流失客户相对于整体客户通常是少量的,尤其对于具有垄断地位的行业巨擘,例如电信、石油、网络运营商等更是如此。
  • 罕见事件的分析。罕见事件与异常检测类似,都属于发生个案较少;但不同点在于异常检测通常都有是预先定义好的规则和逻辑,并且大多数异常事件都对会企业运营造成负面影响,因此针对异常事件的检测和预防非常重要;但罕见事件则无法预判,并且也没有明显的积极和消极影响倾向。例如由于某网络大 V 无意中转发了企业的一条趣味广告导致用户流量明显提升便属于此类。
  • 发生频率低的事件。这种事件是预期或计划性事件,但是发生频率非常低。例如每年 1 次的双 11 盛会一般都会产生较高的销售额,但放到全年来看这一天的销售额占比很可能只有 1% 不到,尤其对于很少参与活动的公司而言,这种情况更加明显。这种属于典型的低频事件。

3.4.2 通过过抽样和欠抽样解决样本不均衡

抽样是解决样本分布不均衡相对简单且常用的方法,包括过抽样和欠抽样两种。

  1. 过抽样 过抽样(也叫上采样、over-sampling)方法通过增加分类中少数类样本的数量来实现样本均衡,最直接的方法是简单复制少数类样本形成多条记录,这种方法的缺点是如果样本特征少而可能导致过拟合的问题;经过改进的过抽样方法通过在少数类中加入随机噪声、干扰数据或通过一定规则产生新的合成样本,例如 SMOTE 算法。
  2. 欠抽样 欠抽样(也叫下采样、under-sampling)方法通过减少分类中多数类样本的样本数量来实现样本均衡,最直接的方法是随机地去掉一些多数类样本来减小多数类的规模,缺点是会丢失多数类样本中的一些重要信息。

总体上,过抽样和欠抽样更适合大数据分布不均衡的情况,尤其是第一种(过抽样)方法应用更加广泛。

3.4.3 通过正负样本的惩罚权重解决样本不均衡

通过正负样本的惩罚权重解决样本不均衡的问题的思想是在算法实现过程中,对于分类中不同样本数量的类别分别赋予不同的权重(一般思路分类中的小样本量类别权重高,大样本量类别权重低),然后进行计算和建模。

使用这种方法时需要对样本本身做额外处理,只需在算法模型的参数中进行相应设置即可。很多模型和算法中都有基于类别参数的调整设置,以 scikit-learn 中的 SVM 为例,通过在 class_weight: {dict, 'balanced'} 中针对不同类别针对不同的权重,来手动指定不同类别的权重。如果使用其默认的方法 balanced,那么 SVM 会将权重设置为与不同类别样本数量呈反比的权重来做自动均衡处理,计算公式为:n_samples / (n_classes * np.bincount(y))。 如果算法本身支持,这种思路是更加简单且高效的方法。

3.4.4 通过组合/集成方法解决样本不均衡

组合/集成方法指的是在每次生成训练集时使用所有分类中的小样本量,同时从分类中的大样本量中随机抽取数据来与小样本量合并构成训练集,这样反复多次会得到很多训练集和训练模型。最后在应用时,使用组合方法(例如投票、加权投票等)产生分类预测结果。

例如,在数据集中的正、负例的样本分别为 100 和 10000 条,比例为 1:100。此时可以将负例样本(类别中的大量样本集)随机分为 100 份(当然也可以分更多),每份 100 条数据;然后每次形成训练集时使用所有的正样本(100 条)和随机抽取的负样本(100 条)形成新的数据集。如此反复可以得到 100 个训练集和对应的训练模型。

这种解决问题的思路类似于随机森林。在随机森林中,虽然每个小决策树的分类能力很弱,但是通过大量的“小树”组合形成的“森林”具有良好的模型预测能力。

如果计算资源充足,并且对于模型的时效性要求不高的话,这种方法比较合适。

3.4.5 通过特征选择解决样本不均衡

上述几种方法都是基于数据行的操作,通过多种途径来使得不同类别的样本数据行记录均衡。除此以外,还可以考虑使用或辅助于基于列的特征选择方法。

一般情况下,样本不均衡也会导致特征分布不均衡,但如果小类别样本量具有一定的规模,那么意味着其特征值的分布较为均匀,可通过选择具有显著型的特征配合参与解决样本不均衡问题,也能在一定程度上提高模型效果。

提示 上述几种方法的思路都是基于分类问题解决的。实际上,这种从大规模数据中寻找罕见数据的情况,也可以使用非监督式的学习方法,例如使用 One-class SVM 进行异常检测。分类是监督式方法,前期是基于带有标签(Label)的数据进行分类预测;而采用非监督式方法,则是使用除了标签以外的其他特征进行模型拟合,这样也能得到异常数据记录。所以,要解决异常检测类的问题,先是考虑整体思路,然后再考虑方法模型。

3.4.6 代码实操:Python处理样本不均衡

本示例中,我们主要使用一个新的专门用于不平衡数据处理的 Pythonimbalanced-learn,读者需要先在系统终端的命令行使用 pip install imbalanced-learn 进行安装;

安装成功后,在 PythonIPython 命令行窗口通过使用 import imblearn(注意导入的库名)检查安装是否正确,示例代码包版本为 0.2.1。

除此以外,我们还会使用 sklearnSVM 在算法中通过调整类别权重来处理样本不均衡问题。本示例使用的数据源文件 data2.txt 位于“附件-chapter3”中,默认工作目录为“附件-chapter3”(如果不是,请 cd 切换到该目录下,否则会报“IOError: File data2.txt does not exist”)。完整代码如下:

python
1import pandas as pd 2from imblearn.over_sampling import SMOTE # 过抽样处理库SMOTE 3from imblearn.under_sampling import RandomUnderSampler # 欠抽样处理库RandomUnderSampler 4from sklearn.svm import SVC # SVM中的分类算法SVC 5from imblearn.ensemble import EasyEnsemble # 简单集成方法EasyEnsemble 6 7# 导入数据文件 8df = pd.read_table('data2.txt', sep=' ', names=['col1', 'col2', 'col3', 'col4', 'col5', 'label']) # 读取数据文件 9x = df.iloc[:, :-1] # 切片,得到输入x 10y = df.iloc[:, -1] # 切片,得到标签y 11groupby_data_orgianl = df.groupby('label').count() # 对label做分类汇总 12print(groupby_data_orgianl) # 打印输出原始数据集样本分类分布 13 14# 使用SMOTE方法进行过抽样处理 15model_smote = SMOTE() # 建立SMOTE模型对象 16x_smote_resampled, y_smote_resampled = model_smote.fit_sample(x, y) # 输入数据并作过抽样处理 17x_smote_resampled = pd.DataFrame(x_smote_resampled, columns=['col1', 'col2', 'col3', 'col4', 'col5']) # 将数据转换为数据框并命名列名 18y_smote_resampled = pd.DataFrame(y_smote_resampled, columns=['label']) # 将数据转换为数据框并命名列名 19smote_resampled = pd.concat([x_smote_resampled, y_smote_resampled], axis=1) # 按列合并数据框 20groupby_data_smote = smote_resampled.groupby('label').count() # 对label做分类汇总 21print(groupby_data_smote) # 打印输出经过SMOTE处理后的数据集样本分类分布 22 23# 使用RandomUnderSampler方法进行欠抽样处理 24model_RandomUnderSampler = RandomUnderSampler() # 建立RandomUnderSampler模型对象 25x_RandomUnderSampler_resampled, y_RandomUnderSampler_resampled = model_RandomUnderSampler.fit_sample(x, y) # 输入数据并作欠抽样处理 26x_RandomUnderSampler_resampled = pd.DataFrame(x_RandomUnderSampler_resampled, columns=['col1', 'col2', 'col3', 'col4', 'col5']) # 将数据转换为数据框并命名列名 27y_RandomUnderSampler_resampled = pd.DataFrame(y_RandomUnderSampler_resampled, columns=['label']) # 将数据转换为数据框并命名列名 28RandomUnderSampler_resampled = pd.concat([x_RandomUnderSampler_resampled, y_RandomUnderSampler_resampled], axis=1) # 按列合并数据框 29groupby_data_RandomUnderSampler = RandomUnderSampler_resampled.groupby('label').count() # 对label做分类汇总 30print(groupby_data_RandomUnderSampler) # 打印输出经过RandomUnderSampler处理后的数据集样本分类分布 31 32# 使用SVM的权重调节处理不均衡样本 33model_svm = SVC(class_weight='balanced') # 创建SVC模型对象并指定类别权重 34model_svm.fit(x, y) # 输入x和y并训练模型 35 36# 使用集成方法EasyEnsemble处理不均衡样本 37model_EasyEnsemble = EasyEnsemble() # 建立EasyEnsemble模型对象 38x_EasyEnsemble_resampled, y_EasyEnsemble_resampled = model_EasyEnsemble.fit_sample(x, y) # 输入数据并应用集成方法处理 39print(x_EasyEnsemble_resampled.shape) # 打印输出集成方法处理后的x样本集概况 40print(y_EasyEnsemble_resampled.shape) # 打印输出集成方法处理后的y标签集概况 41 42# 抽取其中一份数据做审查 43index_num = 1 # 设置抽样样本集索引 44x_EasyEnsemble_resampled_t = pd.DataFrame(x_EasyEnsemble_resampled[index_num], columns=['col1', 'col2', 'col3', 'col4', 'col5']) # 将数据转换为数据框并命名列名 45y_EasyEnsemble_resampled_t = pd.DataFrame(y_EasyEnsemble_resampled[index_num], columns=['label']) # 将数据转换为数据框并命名列名 46EasyEnsemble_resampled = pd.concat([x_EasyEnsemble_resampled_t, y_EasyEnsemble_resampled_t], axis=1) # 按列合并数据框 47groupby_data_EasyEnsemble = EasyEnsemble_resampled.groupby('label').count() # 对label做分类汇总 48print(groupby_data_EasyEnsemble) # 打印输出经过EasyEnsemble处理后的数据集样本分类分布

示例代码以空行分为 6 部分。

第一部分导入库。 本示例中用到了第三方库 imbalanced-learn 实现主要的样本不均衡处理,而 pandas 的引入主要用于解释和说明不同处理方法得到的结果集样本的分布情况,sklearn.svm 中的 SVC 主要用于说明 SVM 如何在算法中自动调整分类权重。

第二部分导入数据文件。 该过程中使用 pandasread_table 读取本地文件,为了更好的区别不同的列,通过 names 指定列名;对数据框做切片分割得到输入的 x 和目标变量 y;通过 pandasgroupby() 方法按照 label 类做分类汇总,汇总方式是使用 count() 函数计数。输入原始数据集样本分类分布如下:

code
1col1 col2 col3 col4 col5 2label 30.0 942 942 942 942 942 41.0 58 58 58 58 58

输出结果显示了原始数据集中,正样本(label 为 1)的数量仅有 58 个,占总样本量的 5.8%,属于严重不均衡分布。

第三部分使用 SMOTE 方法进行过抽样处理。 该过程中首先建立 SMOTE 模型对象,并直接应用 fit_sample 对数据进行过抽样处理,如果要获得有关 smote 的具体参数信息,可先使用 fit(x, y) 方法获得模型信息,并得到模型不同参数和属性;从 fit_sample 方法分别得到对 xy 过抽样处理后的数据集,将两份数据集转换为数据框然后合并为一个整体数据框;最后通过 pandas 提供的 groupby() 方法按照 label 类做分类汇总,汇总方式是使用 count() 函数计数。经过 SMOTE 处理后的数据集样本分类分布如下:

code
1col1 col2 col3 col4 col5 2label 30.0 942 942 942 942 942 41.0 942 942 942 942 942

通过对比第二部分代码段的原始数据集返回结果发现,该结果中的正样本(label 为 1)的数量增加,并与负样本数量相同,均为 942 条,数据分类样本得到平衡。

第四部分使用 RandomUnderSampler 方法进行欠抽样处理。 该过程与第三部分步骤完全相同,在此略过各模块介绍,用途都已在代码备注中注明。经过 RandomUnderSampler 处理后的数据集样本分类分布如下:

code
1col1 col2 col3 col4 col5 2label 30.0 58 58 58 58 58 41.0 58 58 58 58 58

通过对比第二部分代码段的原始数据集返回的结果,该结果中的负样本(label 为 0)的数量减少,并跟正样本相同,均为 58 条,样本得到平衡。

第五部分使用 SVM 的权重调节处理不均衡样本。 该过程主要通过配置 SVC 中的 class_weight 参数和值的设置来处理样本权重,该参数可设置为字典、None 或字符串 balanced 三种模式:

  • 字典:通过手动指定的不同类别的权重,例如 {1:10, 0:1}
  • None:代表类别的权重相同
  • balanced:代表算法将自动调整与输入数据中的类频率成反比的权重,具体公式为 n_samples / (n_classes * np.bincount(y)),程序示例中使用了该方法

经过设置后,算法自动处理样本分类权重,无需用户做其他处理。要对新的数据集做预测,只需要调用 model_svm 模型对象的 predict 方法即可。

第六部分使用集成方法 EasyEnsemble 处理不均衡样本。 该方法的主要过程与其他 imblearn 方法过程类似,不同点在于集成方法返回的数据为三维数据,即将数据在原来的基础上新增了一个维度——“份数”,集成方法返回的数据 xy 的形状为 (10, 116, 5)(10, 116)。为了更详细的查看其中每一份数据,抽取其中一份数据做审查,得到的每份数据返回结果如下:

code
1col1 col2 col3 col4 col5 2label 30.0 58 58 58 58 58 41.0 58 58 58 58 58

通过对比第二部分代码段的原始数据集返回的结果,该结果中的负样本(label 为 0)的数量减少,并跟正样本相同,均为 58 条,样本集得到平衡。随后的应用中,可以通过循环读取每一份数据训练模型并得到结果,然后将 10(x 处理后返回的结果,通过形状名年龄返回的元组中的第一个数值,x_EasyEnsemble_resampled.shape[0])份数据的结果通过一定方法做汇总。

上述过程中,主要需要考虑的关键点是:

  • 如何针对不同的具体场景选择最合适的样本均衡解决方案,选择过程中既要考虑到每个类别样本的分布情况以及总样本情况,又要考虑后续数据建模算法的适应性,以及整个数据模型计算的数据时效性。

代码实操小结:本小节示例中,主要用了几个知识点:

  • 通过 pandasread_table 方法读取文本数据文件,并指定列名
  • 对数据框做切片处理
  • 通过 pandas 提供的 groupby() 方法配合 count() 做分类汇总
  • 使用 imblearn.over_sampling 中的 SMOTE 做过抽样处理
  • 使用 imblearn.under_sampling 中的 RandomUnderSampler 做欠抽样处理
  • 使用 imblearn.ensemble 中的 EasyEnsemble 做集成处理
  • 使用 sklearn.svm 中的 SVC 自动调整算法对不同类别的权重设置

提示 第三方库 imblearn 提供了非常多的样本不均衡处理方法,限于篇幅无法做一一介绍,建议读者自行安装并学习和了解不同的用法。

分享
最后修订: 2018-04-02