数据不平衡问题思考与代码实践

【数据不平衡问题思考与实践】


仅为愚者个人理解,如有错误恳请指正,更欢迎大家补充其他方法,如有发现实时补充更新
推荐阅读:https://blog.csdn.net/u012879957/article/details/82459538

前言

  • 问题背景:在比赛和实际工业应用中,我们很容易发现对于某些分类问题,往往某一类特别多,而其他类别样本数量较少。比如工业故障分类问题中设备故障的样本往往仅占所有样本的百分之一,这样一个二分类问题,其中故障标签数量远远小于无故障,如果直接当作二分类去交给模型训练,只会得到一个效果很差的分类器模型,因为即使模型全部输出无故障,也会有99%的正确率。同样电商用户购买预测也类似,在所有用户中,针对某一样商品购买用户是很少的,我们想要针对这样的商品去对用户预测是否购买进而方便推荐,如果不考虑数据不平衡则同样很难得到较好的预测效果。
  • 通俗解释:如二分类问题(多分类同理),A类:B类=100:1,直接交给模型训练,模型是很难学到B类的特征的,从而较难预测出B类,若B类权重大则模型效果将很差。

所用数据说明

data.csv是某风机结冰预测的二分类比赛数据,因比赛官方也未公开数据下载,故在此不进行展示
简单描述包含了29个特征76065条数据,其中标签1占比:6.2360600670019775%,特征如下
在这里插入图片描述

解决方法

方法1:采样

采样方法又分为上采样和下采样两种方法:下采样和上采样

  • 下采样:随机删除一部分多数类,导致数据缺失。简单方便,但没有意义,不推荐使用。
    即随机在多数类中采样少量样本,与少数类全部样本进行模型预测
    补充:当然下采样有很多的优化方法尽可能减少数据缺失,这里仅提供给大家一个清晰易于理解和实现的方法
# 控制sample中的frac采样比率即可
sample_data0 = data[data['label']==0].sample(frac=0.08,random_state=2022)
sample_data1 = data[data['label']==1]
sample_data  = pd.concat([sample_data0, sample_data1], ignore_index=True)
print('sample_data.shape:{}'.format(sample_data.shape))
print("标签1占比:{}%".format(sample_data['label'].sum()/sample_data.shape[0]*100))
  • 上采样:同样可以简单复制少数类,但这样更加毫无意义。为此推荐大家使用SMOTE上采样方法:(若可以联想到数据增强,则更好理解)
    SMOTE(Synthetic Minority Oversampling Technique),合成少数类过采样技术.它是基于随机过采样算法的一种改进方案,由于随机过采样采取简单复制样本的策略来增加少数类样本,这样容易产生模型过拟合的问题,即使得模型学习到的信息过于特别(Specific)而不够泛化(General),SMOTE算法的基本思想是对少数类样本进行分析并根据少数类样本人工合成新样本添加到数据集中,具体如下图所示,算法流程如下:
    (1)对于少数类中每一个样本x,以欧氏距离为标准计算它到少数类样本集中所有样本的距离,得到其k近邻。
    (2)根据样本不平衡比例设置一个采样比例以确定采样倍率N,对于每一个少数类样本x,从其k近邻中随机选择若干个样本,假设选择的近邻为o。
    (3)对于每一个随机选出的近邻o,分别与原样本按照公式o(new)=o+rand(0,1)*(x-o)构建新的样本。
sm = SMOTE(sampling_strategy='minority', random_state=2022)
sample_trainX,sample_trainY=sm.fit_resample(data.drop('label', axis=1), data['label'])
sample_data=pd.concat([pd.DataFrame(sample_trainY), pd.DataFrame(sample_trainX)], axis=1)
print('sample_data.shape:{}'.format(sample_data.shape))
print("标签1占比:{}%".format(sample_data['label'].sum()/sample_data.shape[0]*100))

”在建立预测分类模型的时候,需要考虑风机结冰数据的类 不平衡问题。一般来说,对数据进行重采样能够有效降低类不 平衡带来的建模误差。将结冰样本进行过采样,将非结冰样本 进行欠采样,或者两者同时进行, 以达到结冰和非结冰样本在 模型训练时有基本相近的比例。如果结冰样本足够多,也可以 选择对类不平衡问题不敏感的分类模型进行建模。“

  • 阈值移动:除了上述两种过采样和欠采样方法外,西瓜书还介绍了一种简单有效的方法
    在这里插入图片描述
      根据上述理论,我们可以将分类器基于式3.46进行决策,实际是在执行式3.47,只需要令
    在这里插入图片描述
      这样直接基于原始训练集进行学习,但是在训练好的分类器进行预测时,将式3.48嵌入到其决策过程中,称为“阈值移动”

方法2:数据EDA画图

通过数据可视化,筛选异常点,直接将其剔除
在这里插入图片描述
这种方法的实用效果最好,对数据集有专业背景知识的一定要使用这种方法去解决数据不平衡的方法,最推荐!(某国家电网的比赛的topline就是用画图方法,成功找到故障风机,从而获奖)

方法3:模型增加惩罚项或模型集成

对模型设置合理的惩罚参数或模型集成这种方法不推荐,算是锦上添花,但不能很好地解决我们实际问题的预测。但这方面的算法理论研究较多,可以学习研究。

  • 惩罚项:较多机器学习模型中均有参数p或者penalty作为惩罚项使用(个人理解这算调参,不完全算解决数据不平衡问题)
  • 模型集成:
       bagging:该方法通常考虑的是同质弱学习器,相互独立地并行学习这些弱学习器,并按照某种确定性的平均过程将它们组合起来。
       boosting:该方法通常考虑的也是同质弱学习器。它以一种高度自适应的方法顺序地学习这些弱学习器(每个基础模型都依赖于前面的模型),并按照某种确定性的策略将它们组合起来。
       stacking:该方法通常考虑的是异质弱学习器,并行地学习它们,并通过训练一个「元模型」将它们组合起来,根据不同弱模型的预测结果输出一个最终的预测结果。
# 调参方法如下
# Logistic Regression 
log_reg_params = {"penalty": ['l1','l2'], 'C': [0.001, 0.01, 0.1, 1, 10]}

grid_log_reg = GridSearchCV(LogisticRegression(), log_reg_params)
grid_log_reg.fit(X_train, y_train)
# We automatically get the logistic regression with the best parameters.
log_reg = grid_log_reg.best_estimator_
print('LR done')

knears_params = {"n_neighbors": list(range(2,5)), 'algorithm': ['auto', 'ball_tree', 'kd_tree']}

grid_knears = GridSearchCV(KNeighborsClassifier(), knears_params)
grid_knears.fit(X_train, y_train)
# KNears best estimator
knears_neighbors = grid_knears.best_estimator_
print('KNN done')

# Support Vector Classifier
svc_params = {'C': [0.5, 0.7, 1], 'kernel': ['rbf', 'poly', 'sigmoid', 'linear']}
grid_svc = GridSearchCV(SVC(), svc_params)
grid_svc.fit(X_train, y_train)

# SVC best estimator
svc = grid_svc.best_estimator_
print('SVC done')

# DecisionTree Classifier
tree_params = {"criterion": ["gini", "entropy"], "max_depth": list(range(2,4,1)), 
              "min_samples_leaf": list(range(5,7,1))}
grid_tree = GridSearchCV(DecisionTreeClassifier(), tree_params)
grid_tree.fit(X_train, y_train)

# tree best estimator
tree_clf = grid_tree.best_estimator_
print('DecisionTree done')

方法4:一分类问题

这个是个人在数据不平衡问题思考学习中发现的非常好的一种思考角度,一分类问题可以简单理解为故障检测,传统意义上,很多的分类问题试图解决两类或者多类情况,机器学习应用的目标是采用训练数据,将测试数据属于哪个类进行区分。但是如果只有一类数据,而目标是测试新的数据并且检测它是否与训练数据相似,此时我们就可以使用One-Class SVM,具体算法原理请自行参考其论文学习。
这种SVM的通俗解释是对数据集训练,学习找到数据的概率密度区域,对需要预测的数据进行计算,判断其是否在训练确定好的数据点区域内,若是则为正常类,否则为异类,就是平面和距离的确定比较。这种方法可以理解成降维聚类,异常检测。

对于正负样本极不平衡的场景,我们可以换一个完全不同的角度来看待问题:
把它看做一分类(One Class Learning)或异常检测(Novelty Detection)问题。
这类方法的重点不在于捕捉类间的差别,而是为其中一类进行建模,经典的工作包括One-class SVM等。

features = [i for i in data.columns if i not in ['id']]
trainSet = data[features]
clf = svm.OneClassSVM(nu=0.0623, kernel='linear', gamma=0.1)
clf.fit(trainSet)
y_pred_train = clf.predict(trainSet)
normal = trainSet[y_pred_train == 1]
abnormal = trainSet[y_pred_train == -1]
print(normal.shape)
print(abnormal.shape)

plt.plot(normal['label'], normal['wind_direction'], 'bx')
plt.plot(abnormal['label'], abnormal['wind_direction'], 'ro')
plt.show()

未完待续…

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿芒Aris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值