机器学习样本不平衡问题

type
Post
status
Published
summary
样本不平衡问题是在生产过程中普遍存在的问题,在很多场景中样本的比例极度失衡,如果不进行处理,那么模型最终的效果可能不尽如人意。本篇收集整理了常用的样本平衡手段和方法。
slug
machine-learning-balance
date
Nov 20, 2023
tags
样本不平衡
SMOTE
四分位
欠采样
过采样
机器学习
category
机器学习
password
icon
URL
Property
Feb 29, 2024 08:23 AM

一、概述

样本不平衡顾名思义就是指各个类别的样本比例悬殊较大,所以,样本不平衡问题也只是在分类问题中需要注意和处理。那问题来了,一个数据集内的样本比例超过多少可以认定样本不平衡呢?
网上较多的说法是: 4:1,10:1,30:1等等不尽相同。
ChatGPT 的回答是:
💡
在机器学习中,对于样本不平衡问题,没有一个固定的比例阈值来定义何时样本不平衡。这是因为适用于不同问题和算法的阈值可能会有所不同。一种常见的做法是使用类别间样本数量的比例作为衡量标准。例如,当某个类别的样本数量少于总样本数量的10%时,可以认为存在样本不平衡。
然而,在我的工作环境中,数据量往往是百万起步甚至是千万,分类任务中的样本比例通常也是 大几百比 1,在多分类中更是几万几十万比1。
所以我的经验是,网上说的4:1,10:1这些在使用公开数据集,做学习研究的时候可能适用;在我的工作中通常以 100:1 为判断基准,并且 ChatGPT 给出的方法也是一个不错的选择。可以根据实际情况酌情选择。

二、选择合适的评估指标

如果在不平衡的数据集上继续使用准确率、错误率则很难反映出模型真正的效果。比如正负样本比例 1:100,此时只需要将所有样本都预测为负样本,则准确率依然可以高达99%,错误率同理。所以我们应该选择一些能反应真实情况的指标。详细内容参见:机器学习模型评估方法
  • 精确率:衡量的是所有被预测为正例的样本中有多少是真正例
  • 召回率:衡量的是所有的正例中有多少是被正确分类了
  • f1 值:为Precision和Recall的调和平均,数值上一般接近于二者中的较小值,因此如果F1 score比较高的话,意味着Precision和Recall都较高。
  • ROC 曲线:模型的整体表现,正例与负例的比例发生了很大变化,ROC曲线也不会产生大的变化。在类别分布发生明显改变的情况下依然能客观地识别出较好的分类器(模型选择?)
  • PR 曲线:PR曲线的两个指标都聚焦于正例,类别不平衡问题中由于主要关心正例(类别不平衡?)
以上指标,PR 曲线和 ROC 曲线相对来说会比较全面,而在样本不平衡这个场景中,PR 曲线更适合

2.1、ROC 和 PR 使用场景

  1. ROC曲线由于兼顾正例与负例,所以适用于评估分类器的整体性能,相比而言PR曲线完全聚焦于正例。
  1. 如果有多份数据且存在不同的类别分布,比如信用卡欺诈问题中每个月正例和负例的比例可能都不相同,这时候如果只想单纯地比较分类器的性能且剔除类别分布改变的影响,则ROC曲线比较适合,因为类别分布改变可能使得PR曲线发生变化时好时坏,这种时候难以进行模型比较;反之,如果想测试不同类别分布下对分类器的性能的影响,则PR曲线比较适合。
  1. 如果想要评估在相同的类别分布下正例的预测情况,则宜选PR曲线。
  1. 类别不平衡问题中,ROC曲线通常会给出一个乐观的效果估计,所以大部分时候还是PR曲线更好。
  1. 最后可以根据具体的应用,在曲线上找到最优的点,得到相对应的precision,recall,f1 score等指标,去调整模型的阈值,从而得到一个符合具体应用的模型。

2.2、ROC-AUC 与 PR-AUC 的实现

sklearn中有完整的 API 可以调用,详见:机器学习模型评估方法——sklearn 实现

三、权重法

权重法是比较简单的方法,我们可以对训练集里的每个类别加一个权重class weight。如果该类别的样本数多,那么它的权重就低,反之则权重就高。如果更细致点,我们还可以对每个样本加权重sample weight,思路和类别权重也是一样,即样本数多的类别样本权重低,反之样本权重高。sklearn中,绝大多数分类算法都有class weight和 sample weight可以使用。
如果权重法做了以后发现预测效果还不好,可以考虑采样法。

四、采样法

采样法基于imblearn库实现

4.1、欠采样(下采样、子采样)

4.1.1、随机欠采样

随机欠采样的思想同样比较简单,就是从多数类样本中随机选取一些剔除掉。这种方法的缺点是被剔除的样本可能包含着一些重要信息,致使学习出来的模型效果不好。
from imblearn.under_sampling import RandomUnderSampler rus = RandomUnderSampler(random_state=42) X_res, y_res = rus.fit_resample(X, y)

4.1.2、imblearn 库欠采样算法对比

我对欠采样用的比较少,简单整理一下先:
算法
改进之处
优缺点
ClusterCentroids
使用聚类算法将多数类样本聚类为几个簇,然后从每个簇中选择代表性样本进行欠采样。
优点:能够保留多数类样本的整体分布特征,有助于减少多数类样本数量。 缺点:可能会选择少数类样本附近的多数类样本作为代表性样本,可能会导致一些重要的多数类样本被删除。
CondensedNearestNeighbour
通过逐步选择重要的样本,减少数据集大小,并仅保留能够正确分类少数类样本和错误分类多数类样本的样本。
优点:能够减少数据集的规模,同时保留重要的样本,有助于提高分类器的性能。 缺点:可能会忽略一些较小的类别或噪声数据,可能会导致欠拟合问题。
EditedNearestNeighbours
通过检查每个多数类样本的最近邻来删除多数类样本,以减少与少数类样本重叠的多数类样本。
优点:能够移除与少数类样本接近的多数类样本,有助于提高分类器的性能。 缺点:可能会删除一些有用的多数类样本,有可能导致欠拟合问题。
RepeatedEditedNearestNeighbours
通过多次迭代来重复应用EditedNearestNeighbours算法
优点:能够更加彻底地减少与少数类样本重叠的多数类样本,提高欠采样效果。 缺点:计算成本较高,可能会导致训练时间增加。
AIlKNN
使用K最近邻算法来识别和移除多数类样本
优点:能够根据多数类样本与少数类样本之间的相对重要性选择合适的多数类样本进行移除。 缺点:计算成本较高,可能会导致训练时间增加。
InstanceHardnessThreshold
使用分类器的置信度(hardness)来识别和移除多数类样本,将置信度低的样本视为易混淆样本进行移除。
优点:能够根据分类器的置信度选择易混淆的多数类样本进行移除。 缺点:需要训练一个分类器来计算样本的置信度,可能会增加计算成本。
NearMiss
基于距离的欠采样方法,通过选择与多数类样本距离最近的少数类样本来进行欠采样。
优点:能够针对性地选择与多数类样本接近的少数类样本,有助于保留重要的少数类样本信息。 缺点:可能会过度关注与多数类样本接近的少数类样本,而忽略其他重要的少数类样本。
NeighbourhoodCleaningRule
首先使用K最近邻算法来识别和移除多数类样本,然后使用一个清洗规则来进一步移除可能会导致错误分类的样本。
优点:能够综合考虑K最近邻和清洗规则,减少与少数类样本重叠的多数类样本,并移除可能导致错误分类的样本。 缺点:计算成本较高,可能会导致训练时间增加。
OneSidedSelection
通过选择与少数类样本距离较近的多数类样本和一些随机选择的多数类样本来进行欠采样。
优点:能够保留与少数类样本接近的多数类样本,有助于提高分类器的性能。 缺点:可能会删除一些重要的多数类样本,有可能导致欠拟合问题。
TomekLinks
通过识别多数类样本和少数类样本之间的Tomek链(Tomek link),然后删除多数类样本,从而平衡数据集。
优点:能够有效地移除多数类样本与少数类样本之间的重叠部分,有助于提高分类器的性能。 缺点:可能会过度删除一些可能有用的多数类样本,有可能导致欠拟合问题。
内容参考:Under-sampling methods

4.2、过采样(上采样)

4.2.1、随机过采样

随机过采样顾名思义就是从样本少的类别中随机抽样,再将抽样得来的样本添加到数据集中。然而这种方法如今已经不大使用了,因为重复采样往往会导致严重的过拟合
随机过采样实现:
from imblearn.over_sampling import RandomOverSampler ros = RandomOverSampler(random_state=42) X_res, y_res = ros.fit_resample(X, y) """ ros =RandomOverSampler( sampling_strategy="auto", # 抽样比例,用法与SMOTE相同,详见下文 random_state=None, # 随机种子 shrinkage=None) # 应用于协方差矩阵的收缩的参数 """

4.2.2、SMOTE

SMOTE (synthetic minority oversampling technique) 的思想概括起来就是在少数类样本之间进行插值来产生额外的样本。具体地,对于某一个缺少样本的类别,它会随机找出几个该类别的样本,再找出最靠近这些样本的若干个该类别样本,组成一个候选合成集合,然后在这个集合中不停的选择距离较近的两个样本,在这两个样本之间,比如中点,构造一个新的该类别样本。
在SMOTE的原始论文中,建议将少数类别SMOTE过采样与多数类别的随机欠采样相结合。
SMOTE实现:
# pip install imblearn from imblearn.over_sampling import SMOTE smo = SMOTE(random_state=42) X_smo, y_smo = smo.fit_sample(X, y) """ smo =SMOTE( sampling_strategy="auto", # 抽样比例,详细参数参阅下文 random_state=None, # 随机种子 k_neighbors=5, # 近邻个数 n_jobs=None) # CPU数量 并行 """
SMOTE参数详解:
参数
默认值
含义
说明
sampling_strategy
float, str, dict, callable, default=’auto’
采样信息
1️⃣当给 float 值时,表示采样之后少样本数量与多样本数量的比值,如 0.2 表示 采样后样本比例为 1:5,也可以直接给一个分数 1/5只在二分类时可以给定float值 2️⃣当给 str 值时,采样后不同类别的样本数量将相等;只能选择特定的 str,可选值分别有: 1、'minority' :仅对少数类进行重采样; 2、'not minority' :对除最少数类之外的所有类进行重新采样; 3、'not majority' :对除最多数类之外的所有类进行重新采样; 4、'all' :对所有类重新采样; 5、'auto' :相当于 'not majority' 3️⃣当给 dict 值时,字典由类别和数量组成;如原数据 0:1 的比例为 900:100, 给定 dict 参数 {0:900, 1:300},表示采样后 0 类数量为 900,1 类数量为 300。如果只对 1 类采样,也可以只写{1:300}。 4️⃣当给 callable(可调用函数) 值时,函数的返回值应该是 dict 类型。
random_state
None
随机数种子
给定一个整数,保证可复现
k_neighbors
5
用于合成新样本的领域样本数
n_jobs
None
CPU 核心数
None 表示 1, -1 使用所有 CPU 核心
SMOTE会随机选取少数类样本用以合成新样本,而不考虑周边样本的情况,这样容易带来两个问题: 1. 如果选取的少数类样本周围也都是少数类样本,则新合成的样本不会提供太多有用信息。这就像支持向量机中远离margin的点对决策边界影响不大。 2. 如果选取的少数类样本周围都是多数类样本,这类的样本可能是噪音,则新合成的样本会与周围的多数类样本产生大部分重叠,致使分类困难。
SMOTE的几种改进算法对比:
算法
改进之处
优缺点
效率
SMOTE
通过对少数类样本中的每个样本与其邻居样本进行插值来生成新的合成样本
优点:简单易实现,通过合成样本增加了少数类样本的数量,有助于提高模型对少数类的识别能力。 缺点:在生成样本时,SMOTE不考虑样本类别之间的分界线,可能会生成一些不真实的样本,导致生成样本过多或者与真实数据分布不一致的问题。
BorderlineSMOTE
仅考虑位于少数类样本边界上的样本进行插值,以降低生成不真实样本的风险
优点:减少了生成不真实样本的可能性,生成的合成样本更接近真实数据分布。 缺点:对于一些边界样本较少的情况,仍可能生成不真实样本。
SVMSMOTE
通过使用SVM分类器来识别边界样本,并在边界样本周围生成新的合成样本。
优点:考虑了样本的边界信息,生成的合成样本更接近真实数据分布,有助于提高分类器性能。 缺点:对于高维数据和大规模数据集的计算复杂度较高。
ADASYN
自适应的合成采样方法,它根据样本的密度分布来决定生成合成样本的数量,不用人为指定采样信息
优点:ADASYN能够根据样本分布的密度变化自适应地生成合成样本,使得更多的合成样本生成在密度较低的区域,有助于更好地平衡数据集。 缺点:对于极度不平衡的数据集,ADASYN可能会过度生成合成样本,导致过拟合。
KMeansSMOTE
通过将少数类样本和其最近邻的多数类样本进行聚类,然后在聚类中心周围生成合成样本
优点:KMeansSMOTE考虑了样本之间的距离信息,生成的合成样本更接近真实数据分布。 缺点:KMeansSMOTE可能对数据集中的噪声和异常值敏感,生成的合成样本可能会受到这些异常样本的影响。
SMOTEN
只用于处理分类特征数据集,随机插值
SMOTENC
用于处理包含分类和数值特征的数据集。它们通过对分类特征进行随机插值和对数值特征进行线性插值来生成合成样本。
优点:适用于具有分类和数值特征的数据集,可以处理更广泛的情况。 缺点:在处理数值特征时,线性插值可能无法准确地反映数据的分布情况,有时可能会生成不真实的样本。
内容参考:

4.3、重采样

如果试过所有的方法之后,模型还是没有提升,那就需要考虑是不是在获取数据的时候出现了什么问题,或者是数据本身就不适合建模。

五、聚类法

其实聚类法的应该也是欠采样的一种,也是将样本量多的那一类的数据量尽可能的减少。
聚类法的思路是:先将数据集的特征和标签分离,用聚类算法对将所有特征数据进行聚类,然后将聚类结果和标签组合进行分析,分析标签和簇类的重合度,尽可能的过滤掉一些大类样本。
在使用聚类法时,应该选择合适的聚类算法,如果数据量非常大,那MiniBatchKMeans(增量训练多几次也行)也许会是一个不错的选择,我的经验是 1000 万的数据,我聚7 类,每次的 batch_size 为 51200,增量训练 10 次也才五分多钟,然后用训练好的模型去预测原本所有的数据,得到聚类结果。
待补充
💡
注意:训练数据与验证数据都需要聚类平衡。不像其他欠采样方法,只需平衡训练数据即可。其中缘由细思既得。

六、异常值识别

有时候如果以上方法都不管用的话,可以将数量少的那一类样本当作异常值,使用异常值识别算法来进行建模。异常值识别算法详见:多变量异常检测
当然,异常值识别的结果也需要进行评估,此时异常值的识别结果不是概率,而是(1,-1)的标记,-1 表示的就是异常值,所以评估之前需要进行转换。
  • 如果评估结果较优,精确率召回率不错,那说明少数样本和异常值重合较多,异常识别模型比较合适;
  • 如果评估结果不好,相关指标表现不佳,那说明异常值只是异常值,这时可以将异常值进一步处理(要么删除,要么替换或者其他操作)

参考文章

If you have any questions, please contact me.