如何用 Python 处理不平衡数据集
作者:wedo实验君 发布时间:2023-02-21 07:35:31
1. 什么是数据不平衡
所谓的数据不平衡(imbalanced data)是指数据集中各个类别的数量分布不均衡;不平衡数据在现实任务中十分的常见。如
信用卡欺诈数据:99%都是正常的数据, 1%是欺诈数据
贷款逾期数据
不平衡数据一般是由于数据产生的原因导致的,类别少的样本通常是发生的频率低,需要很长的周期进行采集。
在机器学习任务(如分类问题)中,不平衡数据会导致训练的模型预测的结果会偏向于样本数量多的类别,这个时候除了要选择合适的评估指标外,想要提升模型的性能,就要对数据和模型做一些预处理。
处理数据不平衡的主要方法:
欠采样
过采样
综合采样
模型集成
调整类别权重或者样本权重
2. 数据不平衡处理方法
imbalanced-learn库提供了许多不平衡数据处理的方法,本文的例子都以imbalanced-learn库来实现。
pip install -U imbalanced-learn
https://github.com/scikit-learn-contrib/imbalanced-learn
本文例子的数据来自进行中的比赛山东省第二届数据应用创新创业大赛-日照分赛场-公积金贷款逾期预测
先来看下数据
import pandas as pd
train_data = './data/train.csv'
test_data = './data/test.csv'
train_df = pd.read_csv(train_data)
test_df = pd.read_csv(test_data)
print(train_df.groupby(['label']).size())
# label为是否违约, 1为违约, 0为非违约
# label
# 0 37243
# 1 2757
2.1 欠采样
所谓欠采样,就是将数量多类别(记为majority)的样本进行抽样,使之数量与数量少的类别(minority)的数量相当,以此达到数量的平衡。
由于欠采样是丢失了一部分数据,不可避免的使得数量多类别样本的分布发生了变化(方差变大)。好的欠采样策略应该尽可能保持原有数据分布。
欠采样是删除majority的样本,那哪些样本可以删除呢?
一种是overlapping的数据,就是多余的数据
一种是干扰的数据,干扰minority的分布
基于此,有两种思路来欠采样
边界相邻匹配,考虑在近邻空间内删除majority样本,方法如TomekLinks, NearMiss
下面这张图,展示6NN(6个最近邻居)
这里重点讲下TomekLinks, TomekLinks方法简单的说:对每一个minority样本找1NN(最近的邻居),如果最近的邻居是majority, 就形成一个tome-links,该方法人为这个majority是干扰的,将它删除。
from imblearn.under_sampling import TomekLinks
X_train = train_df.drop(['id', 'type'], axis=1)
y = train_df['label']
tl = TomekLinks()
X_us, y_us = tl.fit_sample(X_train, y)
print(X_us.groupby(['label']).size())
# label
# 0 36069
# 1 2757
从上可知, 有1174个tomek-link被删除,好像删除还不够多,可以测试下是否对分类结果有帮助。需要注意的因为需要计算最近邻,所以样本属性必须数值属性,或者可以转化为数值属性。
聚类
这类方法通过多个聚类,把原始样本划分成多个聚类簇,然后用每个聚类簇的中心来代替这个聚类簇的特性,完成采样的目的。可知,这种采样的样本不是来自原始样本集,而是聚类生成的。
from imblearn.under_sampling import ClusterCentroids
cc = ClusterCentroids(random_state=42)
X_res, y_res = cc.fit_resample(X_train, y)
X_res.groupby(['label']).size()
# label
# 0 2757
# 1 2757
im-balance提供的欠采样的方法如下:
Random majority under-sampling with replacement
Extraction of majority-minority Tomek links
Under-sampling with Cluster Centroids
NearMiss-(1 & 2 & 3)
Condensed Nearest Neighbour
One-Sided Selection
Neighboorhood Cleaning Rule
Edited Nearest Neighbours
Instance Hardness Threshold
Repeated Edited Nearest Neighbours
AllKNN
2.2 过采样
所谓过采样,就是将数量少的类别(minority)的样本进行copy,使之数量与数量多的类别(majortity)的数量相当,以此达到数量的平衡。由于复制了多份minoruty样本,过采样会改变minority方差。
过采样一种简单的方式是随机copy minority的样本;另外一种是根据现有样本生成人造样本。这里介绍人造样本的经典算法SMOTE(Synthetic Minority Over-sampling Technique)。
SMOTE基于minority样本相似的特征空间构造新的人工样本。步骤如下:
选择一个minority样本,计算其KNN邻居
在K个邻居中,随机选择一个近邻
修改某一个特征,偏移一定的大小:偏移的大小为该minority样本与该近邻差距乘以一个小的随机比率(0, 1), 就此生成新样本
from imblearn.over_sampling import SMOTE
smote = SMOTE(k_neighbors=5, random_state=42)
X_res, y_res = smote.fit_resample(X_train, y)
X_res.groupby(['label']).size()
# label
# 0 37243
# 1 37243
对于SMOTE方法,对每一个minority都会构造新样本。但是并不总是这样的,考虑下面A,B,C三个点。从数据分布来看,C点很可能是一个异常点(Noise),B点是正常分布的点(SAFE),而A点分布在边界位置(DANGER);
直观上,对于C点我们不应该去构造新样本,对B点,构造新样本不会丰富minority类别的分布。只有A点,如果构造新样本能够使得A点从(DANGER)到(SAFE),加强minority类别的分类边界。这个就是Borderline-SMOTE
from imblearn.over_sampling import BorderlineSMOTE
bsmote = BorderlineSMOTE(k_neighbors=5, random_state=42)
X_res, y_res = bsmote.fit_resample(X_train, y)
X_res.groupby(['label']).size()
# label
# 0 37243
# 1 37243
ADASYN方法从保持样本分布的角度来确定生成数据,生成数据的方式和SMOTE是一样的,不同在于每个minortiy样本生成样本的数量不同。
先确定要生成样本的数量 beta为[0, 1]
对每个每个minortiy样本,确定有它生成样本的比例。先找出K最近邻,计算K最近邻中属于majority的样本比例(即分子),Z是归一化因子,保证所有的minortiry的比例和为1,可以认为是所有分子的和。
计算每个minortiy生成新样本的数量
按照SMOTE方式生成样本
from imblearn.over_sampling import ADASYN
adasyn = ADASYN(n_neighbors=5, random_state=42)
X_res, y_res = adasyn.fit_resample(X_train, y)
X_res.groupby(['label']).size()
# label
# 0 37243
# 1 36690
im-balance提供的过采样的方法如下(包括SMOTE算法的变种):
Random minority over-sampling with replacement
SMOTE - Synthetic Minority Over-sampling Technique
SMOTENC - SMOTE for Nominal Continuous
bSMOTE(1 & 2) - Borderline SMOTE of types 1 and 2
SVM SMOTE - Support Vectors SMOTE
ADASYN - Adaptive synthetic sampling approach for imbalanced learning
KMeans-SMOTE
ROSE - Random OverSampling Examples
2.3 综合采样
过采样是针对minority样本,欠采样是针对majority样本;而综合采样是既对minority样本,又对majority样本,同时进行操作的方法。主要有SMOTE+Tomek-links和SMOTE+Edited Nearest Neighbours。
综合采样的方法,是先进行过采样,在进行欠采样。
from imblearn.combine import SMOTETomek
smote_tomek = SMOTETomek(random_state=0)
X_res, y_res = smote_tomek.fit_sample(X_train, y)
X_res.groupby(['label']).size()
# label
# 0 36260
# 1 36260
2.4 模型集成
这里的模型集成主要体现在数据上,即用众多平衡的数据集(majortiry的样本进行欠采样加上minority样本)训练多个模型,然后进行集成。imblearn.ensemble提供几种常见的模型集成算法,如BalancedRandomForestClassifier
from imblearn.ensemble import BalancedRandomForestClassifier
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=1000, n_classes=3,
n_informative=4, weights=[0.2, 0.3, 0.5],
random_state=0)
clf = BalancedRandomForestClassifier(max_depth=2, random_state=0)
clf.fit(X, y)
print(clf.feature_importances_)
print(clf.predict([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]))
im-balance提供的模型集成的方法如下
Easy Ensemble classifier
Balanced Random Forest
Balanced Bagging
RUSBoost
2.5 调整类别权重或者样本权重
对于很多用梯度下降方法来学习(使得某个损失Loss最小)的机器学习的方法,可以通过调整类别权重或样本权重的方式,来一定程度上平衡不平衡数据。如gbdt模型lightgbm 中 class_weight
import lightgbm as lgb
clf = lgb.LGBMRegressor(num_leaves=31,
min_child_samples= np.random.randint(20,25),
max_depth=25,
learning_rate=0.1,
class_weight={0:1, 1:10},
n_estimators=500,
n_jobs=30)
3. 总结
本文分享了常见的几种处理不平衡数据集的方法,并且提供imbalanced-learn的简单例子。总结如下:
欠采样: 减少majoritry样本
过采样:增加minority样本
综合采样:先过采样,在欠采样
模型集成:制造平衡数据(majoritry样本欠采样+minority样本),多次不同的欠采样,训练不同的模型,然后融合
不管是欠采样和过采样,都一定程度的改变了原始数据的分布,可能造成模型过拟合。需要去尝试哪种方法,符合实际的数据分布。当然不一定有效果,去勇敢尝试吧 just do it!
4. 参考资料
Learning from Imbalanced Data
Two Modifications of CNN(Tomek links,CNN乍一看还以为卷积神经网络,其实是condensed nearest-neighbor)
imbalanced-learn API:https://imbalanced-learn.org/stable/
来源:https://mp.weixin.qq.com/s/BU9mNNKhvR_LGo7IWKrAmQ


猜你喜欢
- 1. 认识数据库1.1 数据库和数据结构的关系数据结构:是指相互之间存在一种或多种特定关系的数据元素的集合,是一个抽象的学科我们熟知的数据结
- 1:定义存储过程,用于分隔字符串DELIMITER $$USE `mess`$$DROP PROCEDURE IF EXISTS `spli
- 第一步:python中安装selenium库和其他所有Python库一样,selenium库需要安装pip install selenium
- 今天打算玩个好玩的,也是基于一个优秀的图像处理库——PIL,使用ascii字符把图片转为黑白字符画。
- 问题:变量名是否合法: 1.变量名可以由字母
- 1 装饰器背景知识1.1 基本概念装饰器(Decorator)是 Python 中一种函数或类,用来修饰其他函数或类。装饰器可以改变被装饰函
- 我们编写程序最终目的还是来解决实际问题,所以必然会遇到输入输出的交互问题,python中提供了input函数用来获取用户的输入,我们可以用以
- 官方说明链接:https://intellij-support.jetbrains.com/hc/en-us/community/posts
- 前言提到太阳系,大家可能会想到哥白尼和他的日心说,或是捍卫、发展日心说的斗士布鲁诺,他们像一缕光一样照亮了那个时代的夜空,对历史感兴趣的小伙
- 上一文,介绍了vue.js动态添加、删除绑定的radio选项,本文介绍如何选中radio的某一项绑定的数据和上文的model是一致的,选中r
- 前言前些日子了解到mqtt这样一个协议,可以在web上达到即时通讯的效果,但网上并不能很方便地找到一篇目前版本的在node下正确实现这个协议
- 1.今天网上下载一个博客项目,发现本地访问,js,css加载不了.我想应该是项目上线的安全措施,但是我想调试项目.找到方法如下在settin
- 一、jupyter notebook是什么官网的介绍是:Jupyter Notebook是一个Web应用程序,允许您创建和共享包含实时代码,
- 其实 Oracle数据库的分页还是比较容易理解的。此文以oracle数据库中的SCOTT用户的EMP表为例,用PL/SQL Develope
- Jinja是组成Flask的模板引擎。可能你还不太了解它是干嘛的,但你对下面这些百分号和大括号肯定不陌生:{% block body %}
- 问题:测试时 收发流采用TestCenter、SmartBit等仪表来进行。如果仍采用其进行自动化冒烟,则会带来效率低、成本高的问题。解决方
- 一、维数的变形1. 一维数组转二维数组以及同维变换import numpy as nparr_1d = np.arange(12)# 使用
- 今天搭了个“发短信”的页面,找朋友测试,没想到一位大侠直接弄了本长篇小说发我手机上……为了我的宝贝手机能继续健康澎湃,给文本区域(texta
- 前言在了解了REST farmwork封装的视图类之后,我对python的面向对象有了更深刻的理解。Django RESR framewor
- 1.Python hasattr() 函数描述hasattr() 函数用于判断对象是否包含对应的属性。语法hasattr 语法:hasatt