1.7 非平衡数据的处理方法大全
Posted 炫云云
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了1.7 非平衡数据的处理方法大全相关的知识,希望对你有一定的参考价值。
非平衡数据的处理
前言
在实际应用中,读者可能会碰到一种比较头疼的问题,那就是分类问题中类别型的因变量可能存在严重的偏倚,即类别之间的比例严重失调。所谓的不平衡指的是不同类别的样本量差异非常大,或者少数样本代表了业务的关键数据(少量样本更重要),需要对少量样本的模式有很好的学习。样本类别分布不平衡主要出现在分类相关的建模问题上。
样本类别分布不均衡从数据规模上可以分为大数据分布不均衡和小数据分布不均衡两种:
- 大数据分布不均衡——整体数据规模较大,某类别样本占比较小。例如拥有1000万条记录的数据集中.其中占比5万条的少数分类样本便于属于这种情况。
- 小数据分布不均衡——整体数据规模小,则某类别样本的数量也少,这种情况下,由于少量祥本数太少,很难提取特征进行有/无监督算法学习,此时属于严重的小数据样本分布不均衡。例如拥有100个样本,20个A类祥本,80个B类样本。
如欺诈问题中,欺诈类观测在样本集中毕竟占少数;客户流失问题中,忠实的客户往往也是占很少一部分;在某营销活动的响应问题中,真正参与活动的客户也同样只是少部分。 如果数据存在严重的不平衡,预测得出的结论往往也是有偏的,即分类结果会偏向于较多观测的类。
典型场景
- CTR预估:广告点击率,通常只有百分之几,点击的样本占比非常少,大量的未点击样本
- 异常检测:比如恶意刷单、黄牛订单、信用卡欺诈、电力窃电、设备故障等,这些数据样本所占的比例通常是整体样本中很少的一部分,以信用卡欺诈为例,刷实体信用卡的欺诈比例一般都在0.1%以内。
- 罕见事件的分析:罕见事件分析与异常事件的区别在于异常检测通常都有是预先定义好的规则和逻辑,并且大多数异常事件都对会企业运营造成负面影响,因此针对异常事件的检测和预防非常重要;但军见事件则无法预判,并且也没有明显的积极和消极影响倾向
对于这种问题该如何处理呢?
最简单粗暴的办法就是构造1:1的数据,要么将多的那一类砍掉一部分(欠采样、下采样),要么将少的那一类进行Bootstrap抽样(过采样)。但这样做会存在问题,对于第一种方法,砍掉的数据会导致某些隐含信息的丢失;而第二种方法中,有放回的抽样形成的简单复制,又会使模型产生过拟合。
不平衡案例
在解决问题之前,我们要更好地理解问题。为此我们考虑一个非常简单的例子。假设我们有两个类:C0 和 C1,其中 C0 的点遵循均值为 0、方差为 4 的一维高斯分布;C1 的点遵循均值为 2 、方差为 1 的一维高斯分布。假设数据集中 90% 的点来自 C0,其余 10% 来自 C1。下图是包含 50 个点的数据集按照上述假设的理论分布情况:
不 平 衡 案 例 图 示 不平衡案例图示 不平衡案例图示
虚线表示每个类的概率密度,实线加入了对数据比例的考量。
在这个例子中,我们可以看到 C0 的曲线总是在 C1 曲线之上,因此对于任意给定点,它出自 C0 类的概率总是大于出自 C1 类的概率。用贝叶斯公式来表示,即:
P
(
C
0
∣
x
)
=
P
(
x
∣
C
0
)
P
(
C
0
)
P
(
x
)
>
P
(
x
∣
C
1
)
P
(
C
1
)
P
(
x
)
=
P
(
C
1
∣
x
)
\\mathbb{P}(C 0 \\mid x)=\\frac{\\mathbb{P}(x \\mid C 0) \\mathbb{P}(C 0)}{\\mathbb{P}(x)}>\\frac{\\mathbb{P}(x \\mid C 1) \\mathbb{P}(C 1)}{\\mathbb{P}(x)}=\\mathbb{P}(C 1 \\mid x)
P(C0∣x)=P(x)P(x∣C0)P(C0)>P(x)P(x∣C1)P(C1)=P(C1∣x)
在这里我们可以清楚地看到先验概率的影响,以及它如何导致一个类比另一个类更容易发生的情况。这就意味着,即使从理论层面来看,只有当分类器每次判断结果都是 C0 时准确率才会最大。所以假如分类器的目标就是获得最大准确率,那么我们根本就不用训练,直接全部判为 C0 即可。
关于可分离性
在前面的例子中,我们可以观察到两个类似乎不能很好地分离开(彼此相距不远)。但是,数据不平衡不代表两个类无法很好地分离。例如,我们仍假设数据集中 C0、C1 的比例分别为 90% 和 10%;但 C0 遵循均值为 0 、方差为 4 的一维高斯分布、C1 遵循均值为 10 、方差为 1 的一维高斯分布。如下图所示:
在这个例子中,如果均值差别足够大,即使不平衡类也可以分离开来。
在这里我们看到,与前一种情况相反,C0 曲线并不总是高于 C1 曲线,因此有些点出自 C1 类的概率就会高于出自 C0 的概率。在这种情况下,两个类分离得足够开,足以补偿不平衡,分类器不一定总是得到 C0 的结果。
理论最小误差概率
我们应当明白这一点,分类器具有理论意义上的最小误差概率。对于单特征二分类分类器,用图表来看的话,理论最小误差概率是由两条曲线最小值下的面积给出的:
两
个
类
在
不
同
分
离
度
下
的
理
论
最
小
误
差
两个类在不同分离度下的理论最小误差
两个类在不同分离度下的理论最小误差
我们可以用公式的形式来表示。实际上,从理论的角度来看,最好的分类器将从两个类中选择点
x
x
x 最有可能属于的类。这自然就意味着对于给定的点
x
x
x,最好的理论误差概率由这两个类可能性较小的一个给出,即
P
(
wrong
∣
x
)
=
min
(
P
(
C
0
∣
x
)
,
P
(
C
1
∣
x
)
)
=
min
(
P
(
x
∣
C
0
)
P
(
C
0
)
,
P
(
x
∣
C
1
)
P
(
C
1
)
)
P
(
x
)
\\mathbb{P}(\\text { wrong } \\mid x)=\\min (\\mathbb{P}(C 0 \\mid x), \\mathbb{P}(C 1 \\mid x))=\\frac{\\min (\\mathbb{P}(x \\mid C 0) \\mathbb{P}(C 0), \\mathbb{P}(x \\mid C 1) \\mathbb{P}(C 1))}{\\mathbb{P}(x)}
P( wrong ∣x)=min(P(C0∣x),P(C1∣x))=P(x)min(P(x∣C0)P(C0),P(x∣C1)P(C1))
然后我们可以对全体进行积分,得到总误差概率:
P
(
wrong
)
=
∫
R
P
(
wrong
∣
x
)
P
(
x
)
d
x
=
∫
R
min
(
P
(
x
∣
C
0
)
P
(
C
0
)
,
P
(
x
∣
C
1
)
P
(
C
1
)
)
d
x
\\mathbb{P}(\\text { wrong })=\\int_{\\mathbb{R}} \\mathbb{P}(\\text { wrong } \\mid x) \\mathbb{P}(x) d x=\\int_{\\mathbb{R}} \\min (\\mathbb{P}(x \\mid C 0) \\mathbb{P}(C 0), \\mathbb{P}(x \\mid C 1) \\mathbb{P}(C 1)) d x
P( wrong )=∫RP( wrong ∣x)P(x)dx=∫Rmin(P(x∣C0)P(C0),P(x∣C1)P(C1))dx
即上图中两条曲线最小值下区域的面积。
重新处理数据集并不总是解决方案
面对不平衡数据集,我们的第一个反应可能会认为这个数据没有代表现实。如果这是正确的,也就是说,实际数据应该是(或几乎是)平衡的,但由于我们采集数据时的方法问题造成数据存在比例偏差。因此我们必须尝试收集更具代表性的数据。
下采样、过采样和 组合采样
这三种方法通常在训练分类器之前使用以平衡数据集。
1 、欠采样
欠采样(也叫下采样、under sampling,US)是通过减少多数样本的大小来平衡数据集,当数据量足够时就该使用此方法。通过保存所有少数类样本,并在多数样本类别中随机选择与少数类别样本相等数量的样本,可以检索平衡的新数据集以进一步建模。通过欠采样,在保留少量样本的同时,会丢失多数类样本中的一些信息。经过欠采样,样本总量在减少。
from sklearn.datasets import make_classification
from collections import Counter
X, y = make_classification(n_samples=5000, n_features=2, n_informative=2,
n_redundant=0, n_repeated=0, n_classes=3,
n_clusters_per_class=1,
weights=[0.01, 0.05, 0.94],
class_sep=0.8, random_state=0)
Counter(y)
Out[1]: Counter({0: 64, 1: 262, 2: 4674})
1.1 原型选择(prototype selection)
随机删除
随机地删除一些多量样本,使少量样本和多量样本数量达到均衡
- 分别确定样本集中多量样本数 N m a j N_{m a j} Nmaj 和少量样本数 N min ; N_{\\min } ; Nmin;
- 确定采样样本集中多量样本与少量样本比值 N ratio N_{\\text {ratio }} Nratio ;
- 以少量样本数为基准,确定多量样本采样总数 N sample = N ratio ∗ N min N_{\\text {sample }}=N_{\\text {ratio }} * N_{\\min } Nsample =Nratio ∗Nmin
- 以 N sample N_{\\text {sample }} Nsample 为限,对多量样本进行随机抽样。
python中random.sample()方法可以随机地从指定列表中提取出N个不同的元素,列表的维数没有限制。
import tensorflow as tf
import numpy as np
import random
seed_idx = list(np.arange(0,100,1))
sample_idx = random.sample(seed_idx,40)
与原型生成不同的是, 原型选择算法是直接从原始数据集中进行抽取. 抽取的方法大概可以分为两类: (i) 控制下采样技术;(ii) 采样下的清洁技术
第一类的方法可以由用户指定下采样抽取的子集中样本的数量;
RandomUnderSampler
函数是一种快速并十分简单的方式来平衡各个类别的数据: 随机选取数据的子集.
from imblearn.under_sampling import RandomUnderSampler
under_sampling = RandomUnderSampler(random_state=0)
X_resampled, y_resampled = under_sampling.fit_sample(X, y)
print(sorted(Counter(y_resampled).items()))
Out[2]:
[(0, 64), (1, 64), (2, 64)]
import numpy as np
np.vstack({tuple(row) for row in X_resampled}).shape
Out[3]:
(192, 2)
很明显, 使用默认参数的时候, 采用的是不重复采样;通过设置RandomUnderSampler
中的replacement=True
参数, 可以实现自助法(boostrap)抽样.通过设置RandomUnderSampler中的rratio参数,可以设置数据采样比例
under_sampling=RandomUnderSampler(random_state=1,replacement=True) #采用随机欠采样(下采样)
X_resampled, y_resampled = under_sampling.fit_sample(X, y)
print(sorted(Counter(y_resampled).items()))
print(np.vstack({tuple(row) for row in X_resampled}).shape)
Out[4]:
[(0, 64), (1, 64), (2, 64)]
(185, 2)
NearMiss
函数则添加了一些启发式(heuristic)的规则来选择样本, 通过设定version
参数来实现三种启发式的规则.
下面通过一个例子来说明这三个启发式的选择样本的规则, 首先我们假设正样本是需要下采样的(多数类样本), 负样本是少数类的样本.
NearMiss-1
: 选择离N个近邻的负样本的平均距离最小的正样本;
NearMiss-2
: 选择离N个负样本最远的平均距离最小的正样本;
NearMiss-3
: 是一个两段式的算法. 首先, 对于每一个负样本, 保留它们的M个近邻样本; 接着, 那些到N个近邻样本平均距离最大的正样本将被选择.
from imblearn.under_sampling import NearMiss
nm1 = NearMiss( version=1)
X_resampled_nm1, y_resampled = nm1.fit_sample(X, y)
print(sorted(Counter(y_resampled).items()))
print(np.vstack({tuple(row) for row in X_resampled}).shape)
[(0, 64), (1, 64), (2, 64)]
(185, 2)
第二类方法则不接受这种用户的干预.
使用最近的邻居编辑数据集
EditedNearestNeighbours
这种方法应用最近邻算法来编辑(edit)数据集, 找出那些与邻居不太友好的样本然后移除. 对于每一个要进行下采样的样本, 那些不满足一些准则的样本将会被移除; 他们的绝大多数(kind_sel='mode'
)或者全部(kind_sel='all'
)的近邻样本都属于同一个类, 这些样本会被保留在数据集中.
print(sorted(Counter(y).items()))
from imblearn.under_sampling import EditedNearestNeighbours
enn = EditedNearestNeighbours(random_state=0)
X_resampled, y_resampled = enn.fit_sample(X, y)
print(sorted(Counter(y_resampled).items()))
Out:
[(0, 64), (1, 262), (2, 4674)]
[(0, 64), (1, 213), (2, 4568)]
在此基础上, 延伸出了RepeatedEditedNearestNeighbours
算法, 重复基础的EditedNearestNeighbours
算法多次.
from imblearn.under_sampling import RepeatedEditedNearestNeighbours
renn = RepeatedEditedNearestNeighbours(random_state=0)
X_resampled, y_resampled = renn.fit_sample(X, y)
print(sorted(Counter(y_resampled).items()))
Out:
[(0, 64), (1, 208), (2, 4551)]
与RepeatedEditedNearestNeighbours
算法不同的是, ALLKNN
算法在进行每次迭代的时候, 最近邻的数量都在增加.
from imblearn.under_sampling import AllKNN
allknn = AllKNN(random_state=0)
X_resampled, y_resampled = allknn.fit_sample(X, y)
print(sorted(Counter(y_resampled).items()))
Out:
[(0, 64), (1, 220), (2, 4601)]
以上是关于1.7 非平衡数据的处理方法大全的主要内容,如果未能解决你的问题,请参考以下文章