风控ML[12] | 不平衡数据的处理方法与代码分享

共 517字,需浏览 2分钟

 ·

2022-02-10 03:05

印象中很久之前有位朋友说要我写一篇如何处理不平衡数据的文章,整理相关的理论与实践知识,于是乎有了今天的文章。失衡样本在我们真实世界中是十分常见的,那么我们在机器学习(ML)中使用这些失衡样本数据会出现什么问题呢?如何处理这些失衡样本呢?以下的内容希望对你有所帮助!

00 Index

01 到底什么是不平衡数据
02 处理不平衡数据的理论方法
03 Python里有什么包可以处理不平衡样本
04 Python中具体如何处理失衡样本

01 到底什么是不平衡数据

失衡数据发生在分类应用场景中,在分类问题中,类别之间的分布不均匀就是失衡的根本,假设有个二分类问题,target为y,那么y的取值范围为0和1,当其中一方(比如y=1)的占比远小于另一方(y=0)的时候,就是失衡样本了。

那么到底是需要差异多少,才算是失衡呢,根本Google Developer的说法,我们一般可以把失衡分为3个程度:

  • 轻度:20-40%
  • 中度:1-20%
  • 极度:<1%

一般来说,失衡样本在我们构建模型的时候看不出什么问题,而且往往我们还可以得到很高的accuracy,为什么呢? 假设我们有一个极度失衡的样本,y=1的占比为1%,那么,我们训练的模型,会偏向于把测试集预测为0,这样子模型整体的预测准确性就会有一个很好看的数字,如果我们只是关注这个指标的话,可能就会被骗了

02 处理不平衡数据的理论方法

在我们开始用Python处理失衡样本之前,我们先来了解一波关于处理失衡样本的一些理论知识,前辈们关于这类问题的解决方案,主要包括以下:

  • 从数据角度: 通过应用一些欠采样or过采样技术来处理失衡样本。欠采样就是对多数类进行抽样,保留少数类的全量,使得两类的数量相当,过采样就是对少数类进行多次重复采样,保留多数类的全量,使得两类的数量相当。但是,这类做法也有弊端,欠采样会导致我们丢失一部分的信息,可能包含了一些重要的信息,过采样则会导致分类器容易过拟合。当然,也可以是两种技术的相互结合。
  • 从算法角度: 算法角度的解决方案就是可以通过对每类的训练实例给予一定权值的调整。比如像在SVM这样子的有参分类器中,可以应用grid search(网格搜索)以及交叉验证(cross validation)来优化C以及gamma值。而对于决策树这类的非参数模型,可以通过调整树叶节点上的概率估计从而实现效果优化。
    此外,也有研究员从数据以及算法的结合角度来看待这类问题,提出了两者结合体的AdaOUBoost(adaptive over-sampling and undersampling boost)算法,这个算法的新颖之处在于自适应地对少数类样本进行过采样,然后对多数类样本进行欠采样,以形成不同的分类器,并根据其准确度将这些子分类器组合在一起从而形成强大的分类器,更多的请参考:

AdaOUBoost:https://dl.acm.org/doi/10.1145/1743384.1743408

03 Python里有什么包可以处理不平衡样本

这里介绍一个很不错的包,叫 imbalanced-learn,大家可以在电脑上安装一下使用。

官方文档:https://imbalanced-learn.readthedocs.io/en/stable/index.html

pip install -U imbalanced-learn

使用上面的包,我们就可以实现样本的欠采样、过采样,并且可以利用pipeline的方式来实现两者的结合,十分方便,我们下一节来简单使用一下吧!

04 Python中具体如何处理失衡样本

为了更好滴理解,我们引入一个数据集,来自于UCI机器学习存储库的营销活动数据集。(数据集大家可以自己去官网下载:https://archive.ics.uci.edu/ml/machine-learning-databases/00222/  下载bank-additional.zip 或者到公众号后台回复关键字bank来获取吧。)

我们在完成imblearn库的安装之后,就可以开始简单的操作了(其余更加复杂的操作可以直接看官方文档),以下我会从4方面来演示如何用Python处理失衡样本,分别是:
🌈 1、随机欠采样的实现
🌈 2、使用SMOTE进行过采样
🌈 3、欠采样和过采样的结合(使用pipeline)
🌈 4、如何获取最佳的采样率?

🚗🚗🚗 那我们开始吧!

# 导入相关的库(主要就是imblearn库)
from collections import Counter
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
import pandas as pd
import numpy as np
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)

from sklearn.svm import SVC
from sklearn.metrics import classification_report, roc_auc_score
from numpy import mean

# 导入数据
df = pd.read_csv(r'./data/bank-additional/bank-additional-full.csv'';'# '';'' 为分隔符
df.head()

数据集是葡萄牙银行的某次营销活动的数据,其营销目标就是让客户订阅他们的产品,然后他们通过与客户的电话沟通以及其他渠道获取到的客户信息,组成了这个数据集。
关于字段释义,可以看下面的截图:

我们可以大致看看数据集是不是失衡样本:

df['y'].value_counts()/len(df)

#no     0.887346
#yes    0.112654
#Name: y, dtype: float64

可以看出少数类的占比为11.2%,属于中度失衡样本。

# 只保留数值型变量(简单操作)
df = df.loc[:,
['age''duration''campaign''pdays',
       'previous''emp.var.rate''cons.price.idx',
       'cons.conf.idx''euribor3m''nr.employed','y']]
# target由 yes/no 转为 0/1
df['y'] = df['y'].apply(lambda x: 1 if x=='yes' else 0)
df['y'].value_counts()

#0    36548
#1     4640
#Name: y, dtype: int64

🌈 1、随机欠采样的实现

欠采样在imblearn库中也是有方法可以用的,那就是 under_sampling.RandomUnderSampler,我们可以使用把方法引入,然后调用它。可见,原先0的样本有21942,欠采样之后就变成了与1一样的数量了(即2770),实现了50%/50%的类别分布。

# 1、随机欠采样的实现
# 导入相关的方法
from imblearn.under_sampling import RandomUnderSampler

# 划分因变量和自变量
X = df.iloc[:,:-1]
y = df.iloc[:,-1]

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.40)

# 统计当前的类别占比情况
print("Before undersampling: ", Counter(y_train))

# 调用方法进行欠采样
undersample = RandomUnderSampler(sampling_strategy='majority')

# 获得欠采样后的样本
X_train_under, y_train_under = undersample.fit_resample(X_train, y_train)

# 统计欠采样后的类别占比情况
print("After undersampling: ", Counter(y_train_under))

# 调用支持向量机算法 SVC
model=SVC()

clf = model.fit(X_train, y_train)
pred = clf.predict(X_test)
print("ROC AUC score for original data: ", roc_auc_score(y_test, pred))

clf_under = model.fit(X_train_under, y_train_under)
pred_under = clf_under.predict(X_test)
print("ROC AUC score for undersampled data: ", roc_auc_score(y_test, pred_under))

# Output:
#Before undersampling:  Counter({0: 21942, 1: 2770})
#After undersampling:  Counter({0: 2770, 1: 2770})
#ROC AUC score for original data:  0.603521152028
#ROC AUC score for undersampled data:  0.829234085179

🌈 2、使用SMOTE进行过采样

过采样技术中,SMOTE被认为是最为流行的数据采样算法之一,它是基于随机过采样算法的一种改良版本,由于随机过采样只是采取了简单复制样本的策略来进行样本的扩增,这样子会导致一个比较直接的问题就是过拟合。因此,SMOTE的基本思想就是对少数类样本进行分析并合成新样本添加到数据集中

算法流程如下:
(1)对于少数类中每一个样本x,以欧氏距离为标准计算它到少数类样本集中所有样本的距离,得到其k近邻。
(2)根据样本不平衡比例设置一个采样比例以确定采样倍率N,对于每一个少数类样本x,从其k近邻中随机选择若干个样本,假设选择的近邻为xn。
(3)对于每一个随机选出的近邻xn,分别与原样本按照如下的公式构建新的样本。

# 2、使用SMOTE进行过采样
# 导入相关的方法
from imblearn.over_sampling import SMOTE

# 划分因变量和自变量
X = df.iloc[:,:-1]
y = df.iloc[:,-1]

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.40)

# 统计当前的类别占比情况
print("Before oversampling: ", Counter(y_train))

# 调用方法进行过采样
SMOTE = SMOTE()

# 获得过采样后的样本
X_train_SMOTE, y_train_SMOTE = SMOTE.fit_resample(X_train, y_train)

# 统计过采样后的类别占比情况
print("After oversampling: ",Counter(y_train_SMOTE))

# 调用支持向量机算法 SVC
model=SVC()

clf = model.fit(X_train, y_train)
pred = clf.predict(X_test)
print("ROC AUC score for original data: ", roc_auc_score(y_test, pred))

clf_SMOTE= model.fit(X_train_SMOTE, y_train_SMOTE)
pred_SMOTE = clf_SMOTE.predict(X_test)
print("ROC AUC score for oversampling data: ", roc_auc_score(y_test, pred_SMOTE))

# Output:
#Before oversampling:  Counter({0: 21980, 1: 2732})
#After oversampling:  Counter({0: 21980, 1: 21980})
#ROC AUC score for original data:  0.602555700614
#ROC AUC score for oversampling data:  0.844305732561

🌈 3、欠采样和过采样的结合(使用pipeline)

那如果我们需要同时使用过采样以及欠采样,那该怎么做呢?其实很简单,就是使用 pipeline来实现。

#  3、欠采样和过采样的结合(使用pipeline)
# 导入相关的方法
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline

# 划分因变量和自变量
X = df.iloc[:,:-1]
y = df.iloc[:,-1]

#  定义管道
model = SVC()
over = SMOTE(sampling_strategy=0.4)
under = RandomUnderSampler(sampling_strategy=0.5)
steps = [('o', over), ('u', under), ('model', model)]
pipeline = Pipeline(steps=steps)

# 评估效果
scores = cross_val_score(pipeline, X, y, scoring='roc_auc', cv=5, n_jobs=-1)
score = mean(scores)
print('ROC AUC score for the combined sampling method: %.3f' % score)

# Output:
#ROC AUC score for the combined sampling method: 0.937

🌈 4、如何获取最佳的采样率?

在上面的栗子中,我们都是默认经过采样变成50:50,但是这样子的采样比例并非最优选择,因此我们引入一个叫 最佳采样率 的概念,然后我们通过设置采样的比例,采样网格搜索的方法去找到这个最优点。

# 4、如何获取最佳的采样率?
# 导入相关的方法
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline

# 划分因变量和自变量
X = df.iloc[:,:-1]
y = df.iloc[:,-1]

# values to evaluate
over_values = [0.3,0.4,0.5]
under_values = [0.7,0.6,0.5]
for o in over_values:
  for u in under_values:
    # define pipeline
    model = SVC()
    over = SMOTE(sampling_strategy=o)
    under = RandomUnderSampler(sampling_strategy=u)
    steps = [('over', over), ('under', under), ('model', model)]
    pipeline = Pipeline(steps=steps)
    # evaluate pipeline
    scores = cross_val_score(pipeline, X, y, scoring='roc_auc', cv=5, n_jobs=-1)
    score = mean(scores)
    print('SMOTE oversampling rate:%.1f, Random undersampling rate:%.1f , Mean ROC AUC: %.3f' % (o, u, score))
    

# Output:    
#SMOTE oversampling rate:0.3, Random undersampling rate:0.7 , Mean ROC AUC: 0.938
#SMOTE oversampling rate:0.3, Random undersampling rate:0.6 , Mean ROC AUC: 0.936
#SMOTE oversampling rate:0.3, Random undersampling rate:0.5 , Mean ROC AUC: 0.937
#SMOTE oversampling rate:0.4, Random undersampling rate:0.7 , Mean ROC AUC: 0.938
#SMOTE oversampling rate:0.4, Random undersampling rate:0.6 , Mean ROC AUC: 0.937
#SMOTE oversampling rate:0.4, Random undersampling rate:0.5 , Mean ROC AUC: 0.938
#SMOTE oversampling rate:0.5, Random undersampling rate:0.7 , Mean ROC AUC: 0.939
#SMOTE oversampling rate:0.5, Random undersampling rate:0.6 , Mean ROC AUC: 0.938
#SMOTE oversampling rate:0.5, Random undersampling rate:0.5 , Mean ROC AUC: 0.938

从结果日志来看,最优的采样率就是过采样0.5,欠采样0.7。

最后,想和大家说的是没有绝对的套路,只有合适的套路,无论是欠采样还是过采样,只有合适才最重要。还有,欠采样的确会比过采样“省钱”哈(从训练时间上很直观可以感受到)。

References

[1] SMOTE算法 https://www.jianshu.com/p/13fc0f7f5565
[2] How to deal with imbalanced data in Python


浏览 50
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报