一、样本不均衡

所谓的不均衡指的是不同类别(标签)的样本量差异非常大。样本类别分布不均衡主要出现在分类相关的建模问题上。样本不均衡将导致样本量小的分类所包含的特征过少,并很难从中提取规律;即使得到分类模型,也容易产生过度依赖于有限的数据样本而导致过拟合的问题,当模型应用到新的数据上时,模型的准确性和健壮性将很差。

样本不均衡从数据规模的角度分为:

  • 大数据分布不均衡:例如1000万条数据集中,50万条的小类别。

  • 小数据分布不均衡:例如1000条数据集中,10条的小类别。此情况属于严重的数据样本分布不均衡。

 二、解决样本不均衡的方法

我们通过具体的案例来解析:

import pandas as pd

#导入数据文件
df=pd.read_table('data2.txt',sep=' ',names=['col1','col2','col3','col4','col5','label']) #读取数据文件
df
x=df.iloc[:,:-1]    #特征
y=df.iloc[:,-1]    #标签

groupby_data_o=df.groupby(['label'])['label'].count()   #标签类别分类计数
groupby_data_o 

输出:

可以看出,数据集有1000个样本记录,其中最后一列为标签字段,其中标签=0的样本有942个,标签=1的样本有58个,是典型的样本不均衡。

1、抽样

1)过抽样

通过增加分类中少数类样本的数量来实现样本均衡,缺点在于:如果样本特征少则可能导致过拟合的问题。可通过在少数类中加入随机噪声、干扰数据或通过一定规则产生新的合成样本。

#使用SMOTE方法进行过抽样处理
from imblearn.over_sampling import SMOTE #过抽样处理库SMOTE

model_smote=SMOTE()
x_smote_resampled,y_smote_resampled=model_smote.fit_sample(x,y)  #输入数据进行过抽样处理
y_smote_resampled=pd.DataFrame(y_smote_resampled,columns=['label'])

smote_resampled=pd.concat([x_smote_resampled,y_smote_resampled],axis=1)  #将特征和标签重新拼接
group_data_smote=smote_resampled.groupby(['label'])['label'].count()   #查看标签类别个数
group_data_smote

输出:

label
0.0    942
1.0    942
Name: label, dtype: int64

 经过SMOTE处理后的数据集,增加label=1的样本量,使得label=1的样本量和label=0的样本量相同,从而使分类样本得到平衡。

2)欠抽样

通过减少分类中多数类样本的数量来实现样本均衡,例如随机去掉一些多数类中的样本,缺点是会丢失多数类样本中的一些重要信息。

#使用RandomUnderSampler进行欠抽样处理
from imblearn.under_sampling import RandomUnderSampler #欠抽样处理库RandomUnderSampler

model_RandomUnderSampler=RandomUnderSampler()   #实例化
x_RandomUnderSampler_resampled,y_RandomUnderSampler_resampled=model_RandomUnderSampler.fit_sample(x,y)  #输入数据进行欠抽样处理
y_RandomUnderSampler_resampled=pd.DataFrame(y_RandomUnderSampler_resampled,columns=['label'])

RandomUnderSampler_resampled=pd.concat([x_RandomUnderSampler_resampled,y_RandomUnderSampler_resampled],axis=1)  #将特征和标签重新拼接
group_data_RandomUnderSampler=RandomUnderSampler_resampled.groupby(['label'])['label'].count()   #查看标签类别个数
group_data_RandomUnderSampler

输出:

label
0.0    58
1.0    58
Name: label, dtype: int64

经过RandomUnderSampler处理后的数据集,减少label=0的样本量,使得label=0的样本量和label=1的样本量相同,从而使分类样本得到平衡。

2、通过正负样本的惩罚权重

核心思想在于:不同样本数量的类别分别赋予不同的权重,小样本量类别权重高,大样本量类别权重低。

使用这种方法无需对样本本身做额外处理,只需在算法模型的参数中进行相应设置即可。如scikit-learn中的SVM为例,通过设置参数class_weight,针对不同类别来手动指定权重。该参数可设置为字典、None或字符串balanced三种模式:

  • 字典:手动指定不同类别的权重,例如{1:10,0:1}

  • None:代表类别的

  • 如果使用默认的balanced,SVM会将权重设置为与不同类别样本数量呈反比的权重来进行自动均衡处理,公式为:n_samples/n_classes*np.bincount(y)。例如本例:

    import numpy as np
        1000/(2*np.bincount(y))

    输出:

    array([0.53078556, 8.62068966])
    

    即为各分类样本的权重。

#使用SVM的权重调节处理不均衡样本
from sklearn.svm import SVC 

model_svm=SVC(class_weight='balanced')  #创建SVC对象并指定类别权重
model_svm.fit(x,y)

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

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

组合集成方法指的是每次生成训练集时使用所有分类中的小样本量,同时从分类中的大样本量中随机抽取数据来与小样本量合并构成训练集,这样反复多次会得到很多训练集和训练模型。

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

这种方式其实借鉴了集成学习中的bagging思想,随机森林就是决策时基于这种思想组合而成的。如果计算资源充足,并且效率要求不高,这种方法比较合适。

在scikit-learn中,有一个BaggingClassifier类,专门用来实现Bagging思想。只是这个包只能对正负样本等比例采样,也就是采样后的正负类别比例和原来数据集正负样本比例是相同,并没有实现我们使采样后正负样本趋于平衡的目的。可喜可贺的是,imblearn包也基于scikit-learn实现了随机森林算法,而且加了一个sampling_strategy参数,来控制自定义采样后正负样本比例,sampling_strategy参数可选参数如下:

  • 浮点数:该数值 = 采样之后的少数类样本数/多数类样本数。需要注意的是,浮点数只能应用在二分类问题中。
  • 'majority': 只对数量较多的类别进行重新采样
  • 'not minority': 对除数量较少的类以外的所有类重新采样
  • 'not majority': 对除数量较多的类以外的所有类重新采样
  • 'all': 重采样所有类别,类似于scikit-learn中的
  • 'auto': 和 'not minority'效果相同,也是这个参数的默认值
  • 字典形式:形式为:{类别1:个数1,类别2:个数2}

我们对比一下不加处理的随机森林算法和采样时注重数据集均衡的情况下模型的表现。

1)sklearn包中的随机森林算法训练模型

from sklearn.ensemble import RandomForestClassifier #导入sklearn中的随机森林包
from sklearn.model_selection import train_test_split
from sklearn.metrics import recall_score  #召回率
from sklearn.metrics import confusion_matrix   #混淆矩阵


train_x,test_x,train_y,test_y = train_test_split(x,y,test_size=0.1,random_state=0)  #切分训练集和测试,比例9:1


#随机森林实现
RF = RandomForestClassifier(random_state = 66
                           # ,n_estimators = 100
                           )  #n_estimators越大越好,但占用的内存与训练和预测的时间也会相应增长,
                             #且边际效益是递减的,所以要在可承受的内存/时间内选取尽可能大的n_estimators。
                             #而在sklearn中,n_estimators默认为10。

RF.fit(train_x, train_y)
y_pred1 = RF.predict(test_x)

print('混淆矩阵:',confusion_matrix(test_y, y_pred1))
print('召回率: ',recall_score(test_y, y_pred1))

输出:

混淆矩阵: [[95  0]
 [ 2  3]]
召回率:  0.6

注意:召回率等于3/(2+3)=0.6 

2)imblearn包中的随机森林算法训练模型

# imblearn包中的随机森林算法训练模型
from imblearn.ensemble import EasyEnsembleClassifier

model_EasyEnSemble=EasyEnsembleClassifier(random_state = 66
                                          ,sampling_strategy = 'not minority'
                                          #,n_estimators = 100
                                         )   #实例化
model_EasyEnSemble.fit(train_x, train_y)  

y_pred2 = model_EasyEnSemble.predict(test_x)
print('混淆矩阵:',confusion_matrix(test_y, y_pred2))
print('召回率: ',recall_score(test_y, y_pred2))

输出:

混淆矩阵: [[87  8]
 [ 1  4]]
召回率:  0.8

注意:召回率等于4/(1+4)=0.8

在两个算法中,我们控制其生成了相同的树的棵数,只是第二个随机森林算法中,每次使用所有的少数类样本,然后抽取等量的多数类样本组成训练集进行训练。

可以看到,虽然第二种算法可能因为采样过程更为复杂,所以耗时更长,但是获得了更好的召回率。

如果想训练多个SVM等算法进行集成学习,可以使用imblearn中的ensemble.BalancedBaggingClassifier这个包,可以自己定义子算法。

【注:在0.2版本中,还可以使用imblearn.ensemble.EasyEnsemble这个函数来随机划分数据集,但是这个 函数在0.6版本之后这个函数就已经移除了。】


 

 

 

更多推荐