应对机器学习中类不平衡的10种技巧
介绍
当一个类的观察值高于其他类的观察值时,则存在类失衡。
示例:检测信用卡欺诈交易。如下图所示,欺诈交易大约为400,而非欺诈交易大约为90000。
类不平衡是机器学习中的常见问题,尤其是在分类问题中。不平衡数据可能会长时间妨碍我们的模型准确性。
类不平衡出现在许多领域,包括:
- 欺诈识别
- 垃圾邮件过滤
- 疾病筛查
- SaaS订阅流失
- 广告点击
类失衡问题
当每个类别中的样本数量大致相等时,大多数机器学习算法效果最佳。这是因为大多数算法都是为了最大化精确度和减少误差而设计的。
然而,如果数据集不平衡,那么在这种情况下,仅仅通过预测多数类就可以获得相当高的准确率,但是无法捕捉少数类,这通常是创建模型的首要目的。
信用卡欺诈检测示例
假设我们有一个信用卡公司的数据集,我们必须找出信用卡交易是否是欺诈性的。
但是这里有个陷阱……欺诈交易相对罕见,只有6%的交易是欺诈行为。
现在,在你还没有开始之前,你是否能想到问题应该如何解决?想象一下,如果你根本不花时间训练模型。相反,如果你只编写了一行总是预测“没有欺诈性交易”的代码,该怎么办?
def transaction(transaction_data): return 'No fradulent transaction'
好吧,你猜怎么着?你的“解决方案”将具有94%的准确性!
不幸的是,这种准确性令人误解。
所有这些非欺诈性的交易,你都将拥有100%的准确性。
那些欺诈性的交易,你的准确性为0%。
仅仅因为大多数交易不是欺诈性的(不是因为你的模型很好),你的总体准确性就很高。
这显然是一个问题,因为许多机器学习算法都旨在最大程度地提高整体准确性。在本文中,我们将看到处理不平衡数据的不同技术。
数据
我们将在本文中使用信用卡欺诈检测数据集,你可以从此处找到该数据集。
https://www.kaggle.com/mlg-ulb/creditcardfraud
加载数据后,显示数据集的前五行。
# check the target variable that is fraudulet and not fradulent transactiondata['Class'].value_counts()# 0 -> non fraudulent # 1 -> fraudulent
# visualize the target variable g = sns.countplot(data['Class']) g.set_xticklabels(['Not Fraud','Fraud']) plt.show()
你可以清楚地看到数据集之间存在巨大差异。9000次非欺诈性交易和492次欺诈性交易。
指标陷阱
新开发人员用户在处理不平衡数据集时遇到的一个主要问题与用于评估其模型的指标有关。使用更简单的指标,比如准确度得分,可能会产生误导。在具有高度不平衡类的数据集中,分类器总是在不进行特征分析的情况下“预测”最常见的类,并且它的准确率很高,显然不是正确的。
让我们做这个实验,使用简单的XGBClassifier和无特征工程:
# import linrary from xgboost import XGBClassifier xgb_model = XGBClassifier().fit(x_train, y_train)# predictxgb_y_predict = xgb_model.predict(x_test)# accuracy scorexgb_score = accuracy_score(xgb_y_predict, y_test)print('Accuracy score is:', xbg_score)OUTPUT Accuracy score is: 0.992
我们可以看到99%的准确度,我们得到的是非常高的准确度,因为它预测大多数类别为0(非欺诈性)。
重采样技术
一种处理高度不平衡数据集的广泛采用的技术称为重采样。它包括从多数类中删除样本(欠采样)和/或从少数类中添加更多示例(过采样)。
尽管平衡类有很多好处,但是这些技巧也有缺点。
过采样最简单的实现是复制少数群体类别的随机记录,这可能会导致过度捕捞。
欠采样最简单的实现涉及从多数类中删除随机记录,这可能会导致信息丢失。
让我们用信用卡欺诈检测示例来实现它。
我们将首先将类0和类1分开。
# class count class_count_0, class_count_1 = data['Class'].value_counts() # Separate classclass_0 = data[data['Class'] == 0] class_1 = data[data['Class'] == 1]# print the shape of the class print('class 0:', class_0.shape) print('class 1:', class_1.shape
1.随机欠采样
欠采样可以定义为删除多数类的观察值。这是在多数类和少数类被平衡之前进行的。
当你拥有大量数据时,欠采样可能是一个不错的选择,比如数百万行。但是欠采样的一个缺点是我们可能删除了有价值的信息。
class_0_under = class_0.sample(class_count_1) test_under = pd.concat([class_0_under, class_1], axis=0) print("total class of 1 and0:",test_under['Class'].value_counts())# plot the count after under-sampeling test_under['Class'].value_counts().plot(kind='bar', title='count (target)')
2.随机过采样
过采样可以定义为向少数类添加更多副本。当你没有大量数据要处理时,过采样可能是一个不错的选择。
欠采样时要考虑的一个弊端是,它可能导致过拟合并导致测试集泛化不佳。
class_1_over = class_1.sample(class_count_0, replace=True) test_over = pd.concat([class_1_over, class_0], axis=0) print("total class of 1 and 0:",test_under['Class'].value_counts())# plot the count after under-sampeling test_over['Class'].value_counts().plot(kind='bar', title='count (target)')
使用不平衡学习python模块平衡数据
在科学文献中已经提出了许多更复杂的重采样技术。
例如,我们可以将多数类的记录聚类,并通过从每个聚类中删除记录来进行欠采样,从而寻求保留信息。在过采样中,我们可以为这些副本引入较小的变化,从而创建更多样的合成样本,而不是创建少数群体记录的精确副本。
让我们使用Python库 imbalanced-learn应用其中一些重采样技术。它与scikit-learn兼容,并且是scikit-learn-contrib项目的一部分。
import imblearn
3.使用imblearn进行随机欠采样
RandomUnderSampler通过为目标类别随机选择数据子集来平衡数据的快速简便方法。通过随机选择有或没有替代品的样本对多数类别进行欠采样。
# import library from imblearn.under_sampling import RandomUnderSampler rus = RandomUnderSampler(random_state=42, replacement=True)# fit predictor and target variable x_rus, y_rus = rus.fit_resample(x, y)print('original dataset shape:', Counter(y)) print('Resample dataset shape', Counter(y_rus))
4.使用imblearn进行随机过采样
解决不平衡数据的一种方法是在少数群体中生成新样本。最幼稚的策略是通过随机采样替换当前可用的采样来生成新的采样。随机过采样提供了这样一个方案。
# import library from imblearn.over_sampling import RandomOverSampler ros = RandomOverSampler(random_state=42) # fit predictor and target variablex_ros, y_ros = ros.fit_resample(x, y)print('Original dataset shape', Counter(y)) print('Resample dataset shape', Counter(y_ros))
5.欠采样:Tomek链接
Tomek链接是一对非常接近的实例,但类别相反。删除每对多数类的实例会增加两个类之间的空间,从而有助于分类过程。
如果两个样本是彼此的最近邻,则存在Tomek的链接
在下面的代码中,我们将用于ratio='majority'对多数类进行重新采样。
# import library from imblearn.under_sampling import TomekLinks tl = RandomOverSampler(sampling_strategy='majority') # fit predictor and target variablex_tl, y_tl = ros.fit_resample(x, y)print('Original dataset shape', Counter(y)) print('Resample dataset shape', Counter(y_ros))
6. Synthetic Minority Oversampling Technique (SMOTE)
该技术为合成少数过采样技术。
SMOTE(合成少数过采样技术)的工作原理是从少数类中随机选取一个点并计算该点的k近邻。合成点被添加到所选的点和它的相邻点之间。
SMOTE算法通过以下四个简单步骤工作:
- 选择少数类作为输入向量
- 查找其k个最近邻(在SMOTE()函数中将k_neighbors指定为参数)
- 选择这些邻居中的一个,并将合成点放置在连接考虑中的点及其所选邻居的线上的任何位置
- 重复这些步骤,直到数据平衡
# import library from imblearn.over_sampling import SMOTE smote = SMOTE()# fit predictor and target variablex_smote, y_smote = smote.fit_resample(x, y)print('Original dataset shape', Counter(y)) print('Resample dataset shape', Counter(y_ros))
7. NearMiss
NearMiss是欠采样技术。与其使用距离重新采样少数类,不如将多数类等同于少数类。
from imblearn.under_sampling import NearMiss nm = NearMiss()x_nm, y_nm = nm.fit_resample(x, y)print('Original dataset shape:', Counter(y)) print('Resample dataset shape:', Counter(y_nm))
8.更改性能指标
评估不平衡数据集时,准确性不是最佳的度量标准,因为它可能会产生误导。
可以提供更好洞察力的指标是:
- 混淆矩阵:显示正确预测和错误预测类型的表。
- 精度:真实阳性的数目除以所有阳性预测。精度也称为正预测值。它是分类器准确性的度量。低精度表示大量误报。
- 召回率:真实阳性的数量除以测试数据中的阳性值的数量。召回也称为敏感度或真实阳性率。它是分类器完整性的度量。较低的召回率表示大量假阴性。
- F1:得分:准确性和召回率的加权平均值。
- ROC曲线下面积(AUROC):AUROC表示模型将观测值与两个类区分开来的可能性。
换句话说,如果你从每个类中随机选择一个观察值,你的模型能够正确“排序”它们的概率有多大?
9.惩罚算法(成本敏感训练)
下一个策略是使用惩罚性学习算法,该算法会增加少数类分类错误的成本。
这项技术的一种流行算法是Penalized-SVM。
在训练过程中,我们可以使用参数class_weight='balanced'来惩罚少数类的错误,惩罚量与代表性不足的程度成正比。
如果我们想为支持向量机算法启用概率估计,我们还希望包含参数probability=True。
让我们在原始不平衡数据集上使用Penalized-SVM训练模型:
# load library from sklearn.svm import SVC # we can add class_weight='balanced' to add panalize mistake svc_model = SVC(class_weight='balanced', probability=True) svc_model.fit(x_train, y_train)svc_predict = svc_model.predict(x_test)# check performanceprint('ROCAUC score:',roc_auc_score(y_test, svc_predict)) print('Accuracy score:',accuracy_score(y_test, svc_predict)) print('F1 score:',f1_score(y_test, svc_predict))
10.更改算法
尽管在每个机器学习问题中,尝试各种算法都是一个很好的经验法则,但是对于不平衡的数据集而言,这尤其有利。
决策树经常在不平衡的数据上表现良好。在现代机器学习中,树集成(随机森林,梯度增强树等)几乎总是胜过单个决策树,因此我们将直接跳到:
基于树的算法通过学习 if / else 问题的层次结构来工作。这可以强制解决两个类。
# load library from sklearn.ensemble import RandomForestClassifier rfc = RandomForestClassifier()# fit the predictor and targetrfc.fit(x_train, y_train)# predictrfc_predict = rfc.predict(x_test)# check performanceprint('ROCAUC score:',roc_auc_score(y_test, rfc_predict)) print('Accuracy score:',accuracy_score(y_test, rfc_predict)) print('F1 score:',f1_score(y_test, rfc_predict))
欠采样的优点和缺点
优点
- 当训练数据集很大时,它可以通过减少训练数据样本的数量来帮助改善运行时间和存储问题。
缺点
- 它可以丢弃可能有用的信息,这对于构建规则分类器可能很重要。
- 通过随机欠采样选择的样本可能是有偏差的样本。可能导致实际测试数据集的结果不准确。
过采样的优缺点
优点
- 与欠采样不同,此方法不会导致信息丢失。
- 在抽样条件下表现更佳
缺点
- 由于它复制了少数群体事件,因此增加了过度拟合的可能性。
你可以在我的GitHub存储库中检查代码的实现。
https://github.com/benai9916/Handle-imbalanced-data/tree/master