跳转至

imblearn SMOTE算法处理样本类别不平衡

日常工作、比赛的分类问题中常遇到类别型的因变量存在严重的偏倚,即类别之间的比例严重失调。样本量差距过大会导致建模效果偏差。 例如逻辑回归不适合处理类别不平衡问题,会倾向于将样本判定为大多数类别,虽然能达到很高的准确率,但是很低的召回率。

出现样本不均衡场景主要有:

  • 异常检测:恶意刷单、黄牛、欺诈问题(欺诈用户样本可能少于1%);
  • 客户流失:流失用户占比也非常低;
  • 偶发事件:无法预判;
  • 低频事件:频率很大,例如:双11/618大促活动;

如果数据存在严重的不平衡,预测得出的结论往往也是有偏的,即分类结果会偏向于较多观测的类。

处理方法

针对此类问题,有几种处理办法。

1. 正负样本惩罚权重

在算法实现过程中,对于分类不同样本数量的类别分别赋予不同的权重,再进行建模计算。 小样本量类别权重高,大样本权重低。

例如,XgBoost 算法提供参数 scale_pos_weight:

1
2
3
4
5
xgb.XGBCIassifier(
learning_rate =0.1, n_estimators=1000, eval_metric=['logloss',,auc','error'], max_depth=5,
min_child_weight=1,
gamma=0, subsample=0.8, colsample_bytree=0.8, objective= 'binary:logistic', nthread=4,
scale_pos_weight=883, #负样本/正样本之比 seed=42)

2. 组合、集成

每次生成训练集时,使用所有分类中的小样本量,而大样本量进行随机抽取,类似于随机森林的做法,进行Bootstrap釆样。

3. 抽样

最简单的上采样方法可以直接将少数类样本复制几份后添加到样本集中,最简单的下采样则可以直接只取一定百分比的多数类样本作为训练集。

  • 欠釆样、下采样(under-sampling):删掉多的一类

    from imblearn.under_sampling import RandomUnderSampler
    

  • 过釆样、上采样(over-sampling):通过Bootstrap抽样少的一类实现样本均衡

    from imblearn.over_sampling import SMOTE
    

注意:使用imblearn时,数据中不能有缺失值,否则会报错! 欠采样容易导致某些隐含信息丢失,过采样中有返回的抽样形成简单复制,容易产生模型过拟合。

SMOTE算法

1. 合成分类问题中的少数类样本,使得样本达到平衡。

2002 年 Chawla 提出 SMOTE 算法。

2. 原理

合成的策略是对每个少数类样本A,从它的最近邻(KNN欧氏距离)中随机选取一个样本B,然后在A、B之间的连线上随机选取一点作为新合成的少 数类样本(近似填充)。

  • 采样最邻近算法,计算出每个少数类样本的K个近邻
  • 从K个近邻中随机挑选N个样本进行随机线性插值
  • 构造新的少数类样本
  • 将新样本与原数据合成,产生新的训练集

imblearn包解释

1. 安装

直接安装 pip install imblearn, pip install -user imblearn

2. 参数解释

from imblearn.over_sampling import SMOTE

SMOTE(
    radio='auto', # 旧版本
    sampling_strategy="auto", # 新版本抽样比例
    random_state=None, # 随机种子
    k_neighbors=5, # 近邻个数
    m_neighbors=10, #随机抽取个数
    out_step=0.5, # 使用 kind='svm,
    kind='regular', #生成样本选项随机选取少数类的样本'borderline^、'borderline?、'svm' svm_estimator=None, # 指定SVM分类器
    njobs=-1)#CPU 数量并行

kind解释

  • borderlinel:最近邻中的随机样本b与该少数类样本a来自于不同的类
  • borderline2:随机样本b可以是属于任何一个类的样本
  • svm:使用支持向量机分类器产生支持向量然后再生成新的少数类样本

实操

1. 数据准备

1
2
3
4
####数据集_ ####
#导入数据
import pandas as pd
df = pd.read_clipboard()

输出:

a b c d e label
0 5 3 4 9 7 0
13 8 9 4 10 0
26  8   8   8   7   1
35  7   1   5   4   0
45  5   5   8   6   0
54  1   2   7   3   0
66 6 6 10 2 1
76  9   6   7   6   0
83  1   5   5   2   0
98  4   5   2   6   1

#数据分布
df.groupby('label').count()

输出:

1
2
3
4
    a b o d e
label
0   7 7 7 7 7
1   3 3 3 3 3

1
2
3
4
#切片
x, y = df.iloc[:, :-1], df.iloc[:, -1]
#同上
x, y = df.loc[:, df.columns != 'label'], df.loc[:, df.columns == 'label']

另外一种数据集准备方法。

1
2
3
4
5
6
7
8
9
#生成类别不平衡数据
from sklearn.datasets import make_classification
#0和1比例为9:1
X, y = make_classification(n_classes=2,class_sep=2, weights=[0.9,0.1],\
n_informative=3, n_redundant=1, flip_y=O, n_features=20, n_clusters_per_class=1,\
n_samples=1000, random_state=10)
#数据分布
from collections import Counter
Counter(y) # Counter({0:900,1:100})

2. SMOTE过采样

imblearn中过釆样接口提供了随机过采样RandomOverSampler、SMOTE、ADASYN三种方式,调用方式基本一致。 SMOTE只适合处理连续性变量特征,不适合离散型特征。

1
2
3
4
5
6
7
8
9
#导包
from imblearn.over_sampling import SMOTE
#建模
smote_model = SMOTE(k^neighbors=2, random_state=42) #fit
x_smote, y_smote = smote_model.fit_resample(x, y)
#组合
df_smote = pd.concat([x_smote, y_smote], axis=1)
#数据分布
df_smote.groupby('label').count()
输出:
1
2
3
4
    a b c d e
label
0   7 7 7 7 7
1   7 7 7 7 7

fit_resample与fit_sample因版本不同,修改方法名。 SMOTE算法默认生成1:1的数据,如果想生成其他比例,可通过ratio参数设置。

#SMOTE
from imblearn.over_sampling import SMOTE
#转换类型
X = X.astype('float64')
#SMOTE
smo = SMOTE(random_state=42)
X_smo, y_smo = smo.fit_resample(X, y)
#查看分布
Counter(y_smo) # Counter({0: 900,1: 900})
#设置比例
#旧版本
# smo = SMOTE(ratio={1:300}, random_state=42)
#新版本
smo = SMOTE(sampling_strategy=1 /3, random_state=42)
X_smo, y_smo = smo.fit_resample(X, y)
Counter(y_smo) # Counter({0: 900,1: 300})

新版本通过sampling_strategy参数设置。否则会报错。 TypeError: _init_() got an unexpected keyword argument 'ratio'

3. RandomUnderSampler 欠采样

1
2
3
4
5
6
7
#导包
from imblearn.under_sampling import RandomUnderSampler
under_model = RandomUnderSampler()
x_under, y_under = under_model.fit_resample(x, y)
df_under = pd.concat([x_under, y_under], axis=1) #合并
#数据分布
df_under.groupby('label').count()
1
2
3
4
    a b c d e
label
0   3 3 3 3 3
1   3 3 3 3 3

其他问题

ValueError: Unknown label type: 'continuous' 标签类型必须是整型int。

ValueError: Expected n_neighbors <= n_samples, but n_samples = 1, n_neighbors = 6 数据量过少,导致无法求近邻样本。可通过设置k_neighbors参数值,修改低一点数值。

参考