欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何处理样本不平衡问题?

程序员文章站 2022-06-12 16:58:26
...

  在实际场景中存在数据分布不平衡的问题,比如在金融风控项目中,逾期和未逾期的用户占比相差很大。这就造成了实际分布不均的数据和算法的假设存在偏差的情况。同时,不均衡的样本对模型训练后的精度及相关的评价指标也会造成影响。
  除了在模型训练时指定class_weight参数外,常用的解决样本不平衡的方法主要是以重采样为主,即对过多的样本进行欠采样、对过少的样本进行过采样等。

  • 随机欠采样(undersampling):随机欠采样就是随机去除一些多数样本使得正、反例样数据接近,然后再进行学习。随机欠采样的优点在于平衡数据的同时减小了数据量,加速了训练;缺点是数据减少会影响力模型的特征学习能力和泛化能力。
  • 随机过采样(oversampling): 随机过采样就是多次复制少数样本。随机过采样的优点相对于欠采样,其没有导致数据信息的损失,缺点是对较少类别的复制增加了过拟合的可能性。随机过采样方法的一个变种是基于聚类的过采样方法,这种方法提供过聚类作为中介,不但在一定程度上缓解了类间的不平衡问题,同时还缓解了类内的不平衡问题。
  • SMOTE(Synthetic Minority Oversampling Technique,合成少数过采样技术)是基于随机过采样的一种改进方法。SMOTE方法采用对少数样本进行人工合成的方式将新样本加入训练集中,从而使模型训练更具有泛化能力。其主要步骤如下:
    (1). 计算少数类别中的样本 x i x_{i} xi与所有样本点的集合;
    (2). 根据样本的不平衡设定过采样倍率,找到最近的 k k k个样本;
    (3). 对于每一个新生成的样本按 x n e w j = x i + r a n d ( 0 , 1 ) ∗ ( x j − x i ) x_{newj}=x_{i}+rand(0,1)*(x_{j}-x_{i}) xnewj=xi+rand(0,1)(xjxi)
    SMOTE算法的缺点则在于人工合成样本时并没有考虑样本可能来自不同类别,导致类间重叠加大。为了克服这个问题,可以先使用数据清洗技术去除掉一些重叠样本,移除了重叠样本之后再合成新的样本(即Tomek Links算法)。
import pandas as pd
from sklearn.linear_model import LogisticRegression
from imblearn.over_sampling import SMOTE
from imblearn.over_sampling import RandomOverSampler
from imblearn.under_sampling import RandomUnderSampler
from sklearn.model_selection import train_test_split
from imblearn.combine import SMOTETomek
from sklearn.metrics import accuracy_score

data=pd.read_csv('new_loan_2021.csv')
target=data['loan_status']
data=data.drop(['loan_status'],axis=1)

X_train,X_test,y_train,y_test=train_test_split(data,target,random_state=0,
                                               test_size=0.2,stratify=target)
result_train=pd.DataFrame(y_train.values,columns=['real'])
result_test=pd.DataFrame(y_test.values,columns=['real'])

#1.不做任何处理
lr=LogisticRegression()
lr.fit(X_train,y_train)
y_test1=lr.predict(X_test)
y_train1=lr.predict(X_train)
result_test['Nothing']=y_test1.flatten()
result_train['Nothing']=y_train1.flatten()

#2.使用LogisticRegression()中的class_weight参数
lr1=LogisticRegression(class_weight='balanced')
lr1.fit(X_train,y_train)
y_test1=lr1.predict(X_test)
y_train1=lr1.predict(X_train)
result_train['Balanced']=y_train1.flatten()
result_test['Balanced']=y_test1.flatten()

#3.SMOTE算法
sm=SMOTE(random_state=1)
sm_train,sm_target=sm.fit_resample(X_train,y_train)

lr2=LogisticRegression()
lr2.fit(sm_train,sm_target)
y_train2=lr2.predict(X_train)
y_test2=lr2.predict(X_test)
result_train['SMOTE']=y_train2.flatten()
result_test['SMOTE']=y_test2.flatten()

#3_1. Tomek Links算法
stk=SMOTETomek()
stk_train,stk_target=stk.fit_resample(X_train, y_train)
lr2_1=LogisticRegression()
lr2_1.fit(stk_train,stk_target)
y_train2_1=lr2_1.predict(X_train)
y_test2_1=lr2_1.predict(X_test)
result_train['TomekLinks']=y_train2_1.flatten()
result_test['TomekLinks']=y_test2_1.flatten()

#4.随机过采样
ros=RandomOverSampler(random_state=1)
ros_train,ros_target=ros.fit_resample(X_train,y_train)
lr3=LogisticRegression()
lr3.fit(ros_train,ros_target)
y_train3=lr3.predict(X_train)
y_test3=lr3.predict(X_test)
result_train['ROS']=y_train3.flatten()
result_test['ROS']=y_test3.flatten()

#5.随机欠采样
rus=RandomUnderSampler()
rus_train,rus_target=rus.fit_resample(X_train,y_train)
lr4=LogisticRegression()
lr4.fit(rus_train,rus_target)
y_train4=lr4.predict(X_train)
y_test4=lr4.predict(X_test)
result_train['RUS']=y_train4.flatten()
result_test['RUS']=y_test4.flatten()


for col in result_train.columns[1:]:
    TP=((result_train[col]==1)&(result_train['real']==1)).sum()
    TN=((result_train[col]==0)&(result_train['real']==0)).sum()
    FP=((result_train[col]==1)&(result_train['real']==0)).sum()
    FN=((result_train[col]==0)&(result_train['real']==1)).sum()
    TPR=TP/(TP+FN)
    FPR=FP/(FP+TN)
    score=accuracy_score(result_train['real'],result_train[col])
    tmp='不做任何操作' if col=='nothing' else '使用'+col+'算法'
    print(tmp+'时模型的训练集准确率为:{:.3f},真正率为:{:.3f},假正率为:{:.3f}'.format(score,TPR,FPR))

    TP=((result_test[col]==1)&(result_test['real']==1)).sum()
    TN=((result_test[col]==0)&(result_test['real']==0)).sum()
    FP=((result_test[col]==1)&(result_test['real']==0)).sum()
    FN=((result_test[col]==0)&(result_test['real']==1)).sum()
    TPR=TP/(TP+FN)
    FPR=FP/(FP+TN)
    score=accuracy_score(result_test['real'], result_test[col])
    tmp='不做任何操作' if col=='nothing' else '使用'+col+'算法'
    print(tmp+'时模型的测试集准确率为:{:.3f},真正率为:{:.3f},假正率为:{:.3f}'.format(score,TPR,FPR))

其结果如下:

使用Nothing算法时模型的训练集准确率为:0.857,真正率为:0.999,假正率为:0.997
使用Nothing算法时模型的测试集准确率为:0.857,真正率为:0.999,假正率为:0.999
使用Balanced算法时模型的训练集准确率为:0.569,真正率为:0.558,假正率为:0.364
使用Balanced算法时模型的测试集准确率为:0.571,真正率为:0.567,假正率为:0.404
使用SMOTE算法时模型的训练集准确率为:0.604,真正率为:0.605,假正率为:0.398
使用SMOTE算法时模型的测试集准确率为:0.603,真正率为:0.610,假正率为:0.436
使用TomekLinks算法时模型的训练集准确率为:0.574,真正率为:0.566,假正率为:0.379
使用TomekLinks算法时模型的测试集准确率为:0.576,真正率为:0.575,假正率为:0.417
使用ROS算法时模型的训练集准确率为:0.577,真正率为:0.567,假正率为:0.361
使用ROS算法时模型的测试集准确率为:0.581,真正率为:0.577,假正率为:0.398
使用RUS算法时模型的训练集准确率为:0.571,真正率为:0.560,假正率为:0.367
使用RUS算法时模型的测试集准确率为:0.573,真正率为:0.569,假正率为:0.406

imblearn中的各类中有一个重要参数sampling_strategy,以类SMOTE中的该参数进行说明:

  • 当其值为float时:其值表示期望的少数样本与多数样本的比例。只有二分类任务时,这个参数才能为float型数据。
  • 当其值为str型时:指示需要进行重采样的目标字段,采样之后各个类别样本数目相同。此时该参数的可取范围为:
    (1)‘minority’: 只对少数样本进行重采样。
    (2)‘not minority’:对除少数样本之外的所有类别样本进行重采样。
    (3)‘not majority’:对除多数样本之外的所有类别样本进行重采样。
    (4)‘all’:对所有类别样本进行重采样。
    (5)‘auto’:等价于’not majority’
  • 当其值为dict型时:可以通过dict指定目标样本中各个类别的样本数目。
  • 自定义函数:返回dict型
参考资料:
  1. 《阿里云天池大赛赛题解析》
相关标签: 机器学习