当前位置: 首页 > 科技观察

教你用Python解决数据不平衡问题(附代码)

时间:2023-03-17 12:15:15 科技观察

前言好久没更新文章了。相信很多读者都会失望,甚至取消。在此向所有网友致歉。文章没有及时更新的主要原因是我目前正在写Python和R语言相关的书籍。激动的是,基于Python的数据分析与挖掘的书已经写好了,后面会继续写R语言相关的内容。希望得到网友们的谅解,新文迟到再次表示歉意。本次分享的主题是关于数据挖掘中常见的不平衡数据的处理。内容涉及数据不平衡的解决方法和原理,以及如何使用Python这个强大的工具实现平衡转换。SMOTE算法介绍在实际应用中,读者可能会遇到一个比较头疼的问题,即分类问题中的类别因变量可能存在严重的偏差,即类别之间的比例严重失衡。例如,在欺诈问题中,欺诈性观察在样本集中占少数;在客户流失问题中,非忠诚客户往往只占一小部分;在某个营销活动的响应问题中,真正参与到活动中的客户数量也只是一小部分。如果数据存在严重的不平衡,则预测得出的结论往往是有偏差的,即分类结果会偏向观测值较多的那一类。如何处理这类问题?最简单粗暴的方式就是构造1:1的数据,要么切掉more类的一部分(即欠采样),要么对less类进行Bootstrap采样(即过采样)。但是这样做会有问题。对于第一种方法,切割数据会导致一些隐藏信息丢失;而在第二种方法中,通过抽样和放回形成的简单复制会使模型产生过拟合。为了解决数据不平衡问题,Chawla在2002年提出了SMOTE算法,即合成少数过采样技术,它是在随机过采样算法的基础上改进的方案。该技术是目前处理不平衡数据的常用方法,得到了学术界和工业界的一致认可。接下来,简要描述该算法的理论思想。SMOTE算法的基本思想是对少数类别样本进行分析和模拟,将人为模拟的新样本加入到数据集中,使原始数据中的类别不再严重不平衡。该算法的模拟过程采用了KNN技术,模拟生成新样本的步骤如下:采样最近邻算法计算每个少数类样本的K个近邻;从K个近邻中随机选取N个样本进行随机线性插值;构造一个新的少数类样本;将新样本与原始数据结合生成新的训练集;为了便于读者理解SMOTE算法模拟新样本的过程,可以参考下图以及人工新样本的生成过程:如上图所示,实心圆圈代表的样本数量明显多于五角星代表的样本点。如果使用SMOTE算法模拟添加少量样本点,需要经过以下步骤:使用KNN算法选择最近样本点x1K个相似样本点(可能最近邻为5);从最近的K个相似样本点中,随机选择M个样本点(可能M为2),M的选择取决于最终想要的平衡率;对每一个随机选取的样本点构造一个新的样本点;构建新的样本点需要使用以下公式:其中,xi表示少数类别中的样本点(图中五角星表示的x1样本);xj表示从K个最近邻中随机选取的样本点j;rand(0,1)表示生成一个0到1之间的随机数,假设图中样本点x1的观测值为(2,3,10,7),从5个邻居中随机选择2个样本点图中,它们的观测值分别为(1,1,5,8)和(2,1,7,6),所以这样得到的两个新样本点为:重复步骤1),2)和3),通过迭代少数类中的每个样本xi,将原来的少数类样本量扩大到一个理想的比例;通过SMOTE算法实现过采样的技巧并不难,读者可以按照上述步骤自定义一个采样函数。当然,读者也可以使用imblearn模块,使用其子模块over_sampling中的SMOTE“类”来生成新的样本。这个“类”的语法和参数含义如下:SMOTE(ratio='auto',random_state=None,k_neighbors=5,m_neighbors=10,out_step=0.5,kind='regular',svm_estimator=None,n_jobs=1)ratio:用于指定重采样的比例。如果指定字符值,可以是'minority',表示采样少数类别的样本,'majority'表示采样多数类别的样本,'notminority'表示使用欠采样方法,'all'表示使用过采样方式,默认为'auto',相当于'all'和'notminority';如果指定字典的值,则key为每个类别的标签,value为类别下的Samplesize;random_state:用于指定随机数生成器的种子,默认为None,表示使用默认的随机数生成器;k_neighbors:指定邻居个数,默认为5;m_neighbors:指定从邻居样本中随机抽取的样本个数,默认为10;kind:用于指定SMOTE算法在生成新样本时使用的选项,默认为'regular',即随机抽取少数类别的样本,也可以为'borderline1'、'borderline2'和'svm';svm_estimator:用于指定SVM分类器,默认为sklearn.svm.SVC,该参数的作用是使用支持向量机分类器生成支持向量,进而生成新的少数类样本;n_jobs:用于指定SMOTE算法在过采样时需要的CPU数量。默认为1,表示只使用一个CPU来运行算法,即不使用并行计算功能;分类算法的实际应用本次分享的数据集来自德国一家电信行业客户的历史交易数据。数据集共包含4681条记录,19个变量,其中因变量churn为二元变量,yes表示customers流失,不代表客户没有流失;剩下的自变量包括客户是否开通国际长途套餐、语音套餐、短信条数、话费、通话次数等。接下来我们将使用这个数据集来探究数据传输不平衡对平衡。#导入第三方包importpandasaspdimportnumpyasnpimportmatplotlib.pyplotaspltfromsklearnimportmodel_selectionfromsklearnimporttreefromsklearnimportmetricsfromimblearn.over_samplingimportSMOTE#读取数据churn=pd.read_excel(r'C:\Users\Administrator\Desktop\Customer_Churn.xlsx')churn.head()#中文乱码的处理plt.rcParams['font.sans-serif']=['MicrosoftYaHei']#为了保证绘制的饼图是圆形的,需要执行以下代码plt.axes(aspect='equal')#统计是否交易是不是造假counts=churn.churn.value_counts()#画饼图plt.pie(x=counts,#画数据labels=pd.Series(counts.index).map({'yes':'loss','no':'notlost'}),#添加文字标签autopct='%.2f%%'#设置百分比的格式,此处保留一位小数)#显示图形plt.show()如图上图中,流失的用户占比仅为8.3%,与未流失的用户相比,还是有比较大的差距。可以认为这两类客户是不平衡的。如果直接对此类数据进行建模,则模型的结果可能不够准确。您可能希望先在数据上构建一个随机森林模型,看看是否存在任何偏差。原数据表中的state变量和Area_code变量分别表示用户所属的“州”和区号,直观上可能不是用户流失的重要原因,所以将这两个变量从表中删除。另外,用户是否开通了国际长途业务的international_plan和语音业务voice_mail_plan是字符型二进制值,不能直接代入模型,需要转化为0-1二进制值。#数据清洗#删除state变量和area_code变量churn.drop(labels=['state','area_code'],axis=1,inplace=True)#将二进制变量international_plan和voice_mail_plan转换成0-1虚拟变量churn.international_plan=churn.international_plan.map({'no':0,'yes':1})churn.voice_mail_plan=churn.voice_mail_plan.map({'no':0,'yes':1})churn.head()如上表所示,是清洗后的clean数据。接下来对数据集进行拆分,分别构建训练数据集和测试数据集,并使用训练数据集构建分类器,测试数据集对分类器进行测试。:#Allindependentvariablesformodelingpredictors=churn.columns[:-1]#DatasplitintotrainingsetandtestsetX_train,X_test,y_train,y_test=model_selection.train_test_split(churn[predictors],churn.churn,random_state=12复制代码)#构建决策树dt=tree.DecisionTreeClassifier(n_estimators=300)dt.fit(X_train,y_train)#模型在测试集上的预测pred=dt.predict(X_test)#模型的预测准确率print(metrics.accuracy_score(y_test,pred))#模型评估报告print(metrics.classification_report(y_test,pred))如上结果所示,决策树的预测准确率超过93%,没有预测的覆盖率召回是97%%,但预测为是的覆盖率召回率是62%。两者相差甚远,说明分类器确实偏向于样本量大的类别(没有)。#绘制ROC曲线#计算流失用户的概率值,用于生成ROC曲线的数据y_score=dt.predict_proba(X_test)[:,1]fpr,tpr,threshold=metrics.roc_curve(y_test.map({'no':0,'yes':1}),y_score)#计算AUC的值roc_auc=metrics.auc(fpr,tpr)#绘制面积图plt.stackplot(fpr,tpr,color='steelblue',alpha=0.5,edgecolor='black')#添加边界线plt.plot(fpr,tpr,color='black',lw=1)#添加对角线plt.plot([0,1],[0,1],color='red',linestyle='--')#添加文字信息plt.text(0.5,0.3,'ROCcurve(area=%0.3f)'%roc_auc)#添加x轴和y轴标签plt.xlabel('1-Specificity')plt.ylabel('Sensitivity')#显示图形plt.show()如上图,ROC曲线下面积为0.79***UC,数值小于0.8,所以模型被认为是不合理的。(通常将AUC与0.8进行比较,如果大于0.8,则认为该模型合理)。接下来使用SMOTE算法对数据进行处理:#平衡训练数据集over_samples=SMOTE(random_state=1234)over_samples_X,over_samples_y=over_samples.fit_sample(X_train,y_train)#重采样前的类别比率print(y_train.value_counts()/len(y_train))#重采样后的类别比print(pd.Series(over_samples_y).value_counts()/len(over_samples_y))如上结果所示,对于训练数据集本身,其类别比还是有很大的差异,但经过SMOTE算法处理后,两个类别可以达到1:1的平衡。接下来就可以使用这个平衡数据重建决策树分类器了:#根据平衡数据重建决策树模型dt2=ensemble.DecisionTreeClassifier(n_estimators=300)dt2.fit(over_samples_X,over_samples_y)#测试上的模型setPredictionpred2=dt2.predict(np.array(X_test))#modelpredictionaccuracyprint(metrics.accuracy_score(y_test,pred2))#modelevaluationreportprint(metrics.classification_report(y_test,pred2))如上图结果,用平衡数据重构后,模型的准确率也很高,达到了92.6%(相比于原来用不平衡数据建立的模型,准确率只下降了1%),但是预测的覆盖率是提升10%%,达到72%,这就是平衡带来的好处。#计算丢失用户的概率值,用于生成ROC曲线的数据y_score=rf2.predict_proba(np.array(X_test))[:,1]fpr,tpr,threshold=metrics.roc_curve(y_test.map({'no':0,'yes':1}),y_score)#计算AUC的值roc_auc=metrics.auc(fpr,tpr)#绘制面积图plt.stackplot(fpr,tpr,color='steelblue',alpha=0.5,edgecolor='black')#添加边界线plt.plot(fpr,tpr,color='black',lw=1)#添加对角线plt.plot([0,1],[0,1],color='red',linestyle='--')#添加文字信息plt.text(0.5,0.3,'ROCcurve(area=%0.3f)'%roc_auc)#添加x轴和y轴标签plt.xlabel('1-Specificity')plt.ylabel('Sensitivity')#显示图形plt.show()得到的最终AUC值为0.836。这时,模型可以认为是比较合理的。