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

简介:

好久没有更新自己写的文章了,相信很多读者都会比较失望,甚至取关了吧,在此向各位网友道个歉。文章未及时更新的主要原因是目前在写Python和R语言相关的书籍,激动的是基于Python的数据分析与挖掘的书已经编写完毕,后期还继续书写R语言相关的内容。希望得到网友的理解,为晚来的新文章再次表示抱歉。

本次分享的主题是关于数据挖掘中常见的非平衡数据的处理,内容涉及到非平衡数据的解决方案和原理,以及如何使用Python这个强大的工具实现平衡的转换。

SMOTE算法的介绍

在实际应用中,读者可能会碰到一种比较头疼的问题,那就是分类问题中类别型的因变量可能存在严重的偏倚,即类别之间的比例严重失调。如欺诈问题中,欺诈类观测在样本集中毕竟占少数;客户流失问题中,非忠实的客户往往也是占很少一部分;在某营销活动的响应问题中,真正参与活动的客户也同样只是少部分。

如果数据存在严重的不平衡,预测得出的结论往往也是有偏的,即分类结果会偏向于较多观测的类。对于这种问题该如何处理呢?最简单粗暴的办法就是构造1:1的数据,要么将多的那一类砍掉一部分(即欠采样),要么将少的那一类进行Bootstrap抽样(即过采样)。但这样做会存在问题,对于第一种方法,砍掉的数据会导致某些隐含信息的丢失;而第二种方法中,有放回的抽样形成的简单复制,又会使模型产生过拟合。

为了解决数据的非平衡问题,2002年Chawla提出了SMOTE算法,即合成少数过采样技术,它是基于随机过采样算法的一种改进方案。该技术是目前处理非平衡数据的常用手段,并受到学术界和工业界的一致认同,接下来简单描述一下该算法的理论思想。

SMOTE算法的基本思想就是对少数类别样本进行分析和模拟,并将人工模拟的新样本添加到数据集中,进而使原始数据中的类别不再严重失衡。该算法的模拟过程采用了KNN技术,模拟生成新样本的步骤如下:

d47e62d2b349aca45e42305ed6714efbe5ed61d9采样最邻近算法,计算出每个少数类样本的K个近邻;
d47e62d2b349aca45e42305ed6714efbe5ed61d9从K个近邻中随机挑选N个样本进行随机线性插值;
d47e62d2b349aca45e42305ed6714efbe5ed61d9构造新的少数类样本;
d47e62d2b349aca45e42305ed6714efbe5ed61d9将新样本与原数据合成,产生新的训练集;

为了使读者理解SMOTE算法实现新样本的模拟过程,可以参考下图和人工新样本的生成过程:

2c40b13b7e9778ffbeacbdfeb02c824f9fc06667

如上图所示,实心圆点代表的样本数量要明显多于五角星代表的样本点,如果使用SMOTE算法模拟增加少类别的样本点,则需要经过如下几个步骤:

d47e62d2b349aca45e42305ed6714efbe5ed61d9 利用KNN算法,选择离样本点x1最近的K个同类样本点(不妨最近邻为5);
d47e62d2b349aca45e42305ed6714efbe5ed61d9 从最近的K个同类样本点中,随机挑选M个样本点(不妨M为2),M的选择依赖于最终所希望的平衡率;

d47e62d2b349aca45e42305ed6714efbe5ed61d9对于每一个随机选中的样本点,构造新的样本点;新样本点的构造需要使用下方的公式:

3d92e2a0fcd29db11cbb32384f840bbdddb5939e

其中,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),所以,由此得到的两个新样本点为:

e65782e76f577550493b3b439cca3099419f84d5

d47e62d2b349aca45e42305ed6714efbe5ed61d9 重复步骤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)

d47e62d2b349aca45e42305ed6714efbe5ed61d9ratio: 用于指定重抽样的比例,如果指定字符型的值,可以是’minority’,表示对少数类别的样本进行抽样、’majority’,表示对多数类别的样本进行抽样、’not minority’表示采用欠采样方法、’all’表示采用过采样方法,默认为’auto’,等同于’all’和’not minority’;如果指定字典型的值,其中键为各个类别标签,值为类别下的样本量;
d47e62d2b349aca45e42305ed6714efbe5ed61d9random_state: 用于指定随机数生成器的种子,默认为None,表示使用默认的随机数生成器;
d47e62d2b349aca45e42305ed6714efbe5ed61d9k_neighbors: 指定近邻个数,默认为5个;
d47e62d2b349aca45e42305ed6714efbe5ed61d9m_neighbors: 指定从近邻样本中随机挑选的样本个数,默认为10个;
d47e62d2b349aca45e42305ed6714efbe5ed61d9kind: 用于指定SMOTE算法在生成新样本时所使用的选项,默认为’regular’,表示对少数类别的样本进行随机采样,也可以是’borderline1’、’borderline2’和’svm’;
d47e62d2b349aca45e42305ed6714efbe5ed61d9svm_estimator: 用于指定SVM分类器,默认为sklearn.svm.SVC,该参数的目的是利用支持向量机分类器生成支持向量,然后再生成新的少数类别的样本;
d47e62d2b349aca45e42305ed6714efbe5ed61d9n_jobs: 用于指定SMOTE算法在过采样时所需的CPU数量,默认为1表示仅使用1个CPU运行算法,即不使用并行运算功能;

分类算法的应用实战

本次分享的数据集来源于德国某电信行业的客户历史交易数据,该数据集一共包含条4,681记录,19个变量,其中因变量churn为二元变量,yes表示客户流失,no表示客户未流失;剩余的自变量包含客户的是否订购国际长途套餐、语音套餐、短信条数、话费、通话次数等。接下来就利用该数据集,探究非平衡数据转平衡后的效果。

# 导入第三方包

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

from sklearn import model_selection

from sklearn import tree

from sklearn import metrics

from imblearn.over_sampling import SMOTE


# 读取数据churn = pd.read_excel(r'C:\Users\Administrator\Desktop\Customer_Churn.xlsx')
churn.head()
64fd84c8c09ccb56824a6325f59c292da0f2243d
# 中文乱码的处理

plt.rcParams['font.sans-serif']=['Microsoft YaHei']



# 为确保绘制的饼图为圆形,需执行如下代码

plt.axes(aspect = 'equal')

# 统计交易是否为欺诈的频数

counts = churn.churn.value_counts()



# 绘制饼图

plt.pie(x = counts, # 绘图数据

 labels=pd.Series(counts.index).map({'yes':'流失','no':'未流失'}), # 添加文字标签

autopct='%.2f%%' # 设置百分比的格式,这里保留一位小数
 )

# 显示图形

plt.show()

c9aabecce9557a44bb7cd346fe1346ac393d8da4

如上图所示,流失用户仅占到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()

4b48e409c1d4262b8d13109e640618a4aa363297

如上表所示,即为清洗后的干净数据,接下来对该数据集进行拆分,分别构建训练数据集和测试数据集,并利用训练数据集构建分类器,测试数据集检验分类器:

# 用于建模的所有自变量

predictors = churn.columns[:-1]

# 数据拆分为训练集和测试集

X_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))

cc85f7c842027278d0e58e3368aa5de636963153

如上结果所示,决策树的预测准确率超过93%,其中预测为no的覆盖率recall为97%,但是预测为yes的覆盖率recall却为62%,两者相差甚远,说明分类器确实偏向了样本量多的类别(no)。

# 绘制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,'ROC curve (area = %0.3f)' % roc_auc)

# 添加x轴与y轴标签

plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')

# 显示图形

plt.show()

acdfc1edcb7774b3623cec0c5af495ecbf4b6cfc

如上图所示,ROC曲线下的面积为0.795,AUC的值小于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))

0d3fd47991be9b3a6069f123efc68d8c53d4f27f

如上结果所示,对于训练数据集本身,它的类别比例还是存在较大差异的,但经过SMOTE算法处理后,两个类别就可以达到1:1的平衡状态。下面就可以利用这个平衡数据,重新构建决策树分类器了:

# 基于平衡数据重新构建决策树模型

dt2 = ensemble.DecisionTreeClassifier(n_estimators = 300)
dt2.fit(over_samples_X,over_samples_y)

# 模型在测试集上的预测

pred2 =dt2.predict(np.array(X_test))



# 模型的预测准确率

print(metrics.accuracy_score(y_test, pred2))

# 模型评估报告

print(metrics.classification_report(y_test, pred2))

4a924fbded5e20c2142807cf9f3003963bad8da5

如上结果所示,利用平衡数据重新建模后,模型的准确率同样很高,为92.6%(相比于原始非平衡数据构建的模型,准确率仅下降1%),但是预测为yes的覆盖率提高了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,'ROC curve (area = %0.3f)' % roc_auc)

# 添加x轴与y轴标签

plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')



# 显示图形

plt.show()

2173d9b7a503b80535b303cbd96e4bf8b58af4ec

最终得到的AUC值为0.836,此时就可以认为模型相对比较合理了。


原文发布时间为:2018-05-13

本文作者:刘顺祥

本文来自云栖社区合作伙伴“数据派THU”,了解相关信息可以关注“数据派THU”。

相关文章
|
23天前
|
数据采集 数据可视化 数据挖掘
利用Python自动化处理Excel数据:从基础到进阶####
本文旨在为读者提供一个全面的指南,通过Python编程语言实现Excel数据的自动化处理。无论你是初学者还是有经验的开发者,本文都将帮助你掌握Pandas和openpyxl这两个强大的库,从而提升数据处理的效率和准确性。我们将从环境设置开始,逐步深入到数据读取、清洗、分析和可视化等各个环节,最终实现一个实际的自动化项目案例。 ####
|
2月前
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用Python实现深度学习模型:智能数据隐私保护
使用Python实现深度学习模型:智能数据隐私保护 【10月更文挑战第3天】
164 0
|
21天前
|
数据采集 分布式计算 大数据
构建高效的数据管道:使用Python进行ETL任务
在数据驱动的世界中,高效地处理和移动数据是至关重要的。本文将引导你通过一个实际的Python ETL(提取、转换、加载)项目,从概念到实现。我们将探索如何设计一个灵活且可扩展的数据管道,确保数据的准确性和完整性。无论你是数据工程师、分析师还是任何对数据处理感兴趣的人,这篇文章都将成为你工具箱中的宝贵资源。
|
1月前
|
传感器 物联网 开发者
使用Python读取串行设备的温度数据
本文介绍了如何使用Python通过串行接口(如UART、RS-232或RS-485)读取温度传感器的数据。详细步骤包括硬件连接、安装`pyserial`库、配置串行端口、发送请求及解析响应等。适合嵌入式系统和物联网应用开发者参考。
53 3
|
2月前
|
数据采集 JSON 数据处理
抓取和分析JSON数据:使用Python构建数据处理管道
在大数据时代,电商网站如亚马逊、京东等成为数据采集的重要来源。本文介绍如何使用Python结合代理IP、多线程等技术,高效、隐秘地抓取并处理电商网站的JSON数据。通过爬虫代理服务,模拟真实用户行为,提升抓取效率和稳定性。示例代码展示了如何抓取亚马逊商品信息并进行解析。
抓取和分析JSON数据:使用Python构建数据处理管道
|
1月前
|
图形学 Python
SciPy 空间数据2
凸包(Convex Hull)是计算几何中的概念,指包含给定点集的所有凸集的交集。可以通过 `ConvexHull()` 方法创建凸包。示例代码展示了如何使用 `scipy` 库和 `matplotlib` 绘制给定点集的凸包。
31 1
|
2月前
|
数据处理 Python
Python实用记录(十):获取excel数据并通过列表的形式保存为txt文档、xlsx文档、csv文档
这篇文章介绍了如何使用Python读取Excel文件中的数据,处理后将其保存为txt、xlsx和csv格式的文件。
110 3
Python实用记录(十):获取excel数据并通过列表的形式保存为txt文档、xlsx文档、csv文档
|
2月前
|
计算机视觉 Python
Python实用记录(九):将不同的图绘制在一起、将不同txt文档中的数据绘制多条折线图
这篇文章介绍了如何使用Python的OpenCV库将多张图片合并为一张图片显示,以及如何使用matplotlib库从不同txt文档中读取数据并绘制多条折线图。
50 3
Python实用记录(九):将不同的图绘制在一起、将不同txt文档中的数据绘制多条折线图
|
1月前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
|
1月前
|
数据采集 Web App开发 iOS开发
如何使用 Python 语言的正则表达式进行网页数据的爬取?
使用 Python 进行网页数据爬取的步骤包括:1. 安装必要库(requests、re、bs4);2. 发送 HTTP 请求获取网页内容;3. 使用正则表达式提取数据;4. 数据清洗和处理;5. 循环遍历多个页面。通过这些步骤,可以高效地从网页中提取所需信息。