前言
在数据分析与挖掘过程中,预测性或分类性问题往往是企业需要解决的主要问题,例如下一季度的营收可能会达到多少、什么样的用户可能会流失、一场营销活动中哪些用户的参与度会比较高等。
本章将通过Python语言,以一个实战案例介绍分类性问题的解决步骤。通过本章的学习,你将会了解到基于Python的数据处理和建模方法:
- 外部数据的读取;
- 数据的预处理;
- 数据的探索性分析;
- 数据建模;
- 模型预测与评估。
2.1 下载与安装Anoconda
本书中的所有代码都是基于Python 3实现的,所以必须确保你的电脑已经安装好了Python工具。如果没有安装也不用担心,本节的主要内容就是引导读者如何下载并安装一款好用的Python工具。
Anoconda是不错的选择,专门用于科学计算的Python发行版,支持Windows、Linux和Mac系统,可以很方便地解决多版本Python并存、切换以及各种第三方模块安装的问题。更重要的是,当你下载并安装好Anoconda后,它就已经集成了上百个科学计算的第三方模块,例如书中将要使用的numpy、pandas、matplotlib、seaborn、statsmodels、sklearn等。用户需要使用这些模块时,直接导入即可,不用再去下载。
接下来将针对Windows、Linux和Mac系统,分别介绍各自的安装方法,以便读者按步操作。首先你需要到Anoconda官网(https://www.anaconda.com/download/) 下载对应系统的Anoconda工具。注意,本书是基于Python 3的应用,所以你需要下载Python 3.X的Anoconda。
2.1.1 基于Windows系统安装
步骤01 从官网中下载好Windows版本的Anoconda后,双击该软件并进入安装向导,并单击“Next”按钮,如图2-1所示。
步骤02 进入阅读“License Agreement”窗口,单击“I Agree”按钮。
步骤03 推荐选择“Just Me (recommended)”,如果选择的是“All Users”,就需要Windows的管理员权限。
步骤04 选择目标路径用于Anodonda工具的安装,并单击“Next”按钮,如图2-2所示。
步骤05 建议不添加Anoconda到环境变量中,因为它可能会影响到其他软件的正常运行,故选择将Python3.×作为Anoconda的默认版本。单击“Install”按钮,进入安装环节,如图2-3所示。
步骤06 大概5分钟就可以完成安装,单击“Finish”按钮即可,如图2-4所示。
2.1.2 基于Mac系统安装
步骤01 从官网中下载好Mac版本的Anoconda后,双击该软件,进入Anoconda的安装向导,单击“Continue”按钮。
步骤02 进入“Read Me”窗口,继续单击“Continue”按钮。
步骤03 进入阅读“License”窗口,勾选“I Agree”,并单击“Continue”按钮。
步骤04 进入“Destination Select”窗口,推荐选择“Install for me only”,并单击“Continue”按钮,如图2-5所示。
步骤05 进入“Installation Type”窗口,推荐默认设置(将Anoconda安装在主目录下),无须改动安装路径,单击“Install”按钮,进入安装环节,如图2-6所示。
步骤06 经过几分钟,即完成整个安装流程,如图2-7所示。
当然,如果你不习惯在Mac系统中使用图形化的安装方式,也可以通过命令行的方式完成Anoconda的安装(以Anaconda3-5.0.1版本为例),具体步骤如下:
步骤01 同样需要通过官网下载好Mac版本的Anoconda,并将其放在桌面。
步骤02 打开终端,输入“bash Anaconda3-5.0.1-MacOSX-x86_64.sh”。
步骤03 接下来会提示阅读“条款协议”,只需按一下回车键即可。
步骤04 滑动滚动条到协议底部,输入“Yes”。
步骤05 提示“按下回车键”接受默认路径的安装,接下来继续输入“Yes”,进入安装环节。
步骤06 最终完成安装,并提示“Thank you for installing Anaconda!”。
注意,关闭终端,重启后安装才有效。
2.1.3 基于Linux系统安装
步骤01 从官网中下载好Linux版本的Anoconda,并将其放在桌面。
步骤02 打开终端,输入“bash Anaconda3-5.0.1-Linux-x86_64.sh”。
步骤03 接下来会提示阅读“条款协议”,只需按一下回车键即可。
步骤04 滑动滚动条到协议底部,输入“Yes”。
步骤05 提示“按下回车键”接受默认路径的安装,接下来继续输入“Yes”。
步骤06 最终完成安装,并提示“Thank you for installing Anaconda3!”。
注意,关闭终端,重启后安装才有效。
2.2 基于Python的案例实战
2.2.1 数据的预处理
1994年Ronny Kohavi和Barry Becker针对美国某区域的居民做了一次人口普查,经过筛选,一共得到32 561条样本数据。数据中主要包含了关于居民的基本信息以及对应的年收入,其中年收入就是本章中需要预测的变量,具体数据指标和含义见表2-1。
基于上面的数据集,需要预测居民的年收入是否会超过5万美元,从表2-1的变量描述信息可知,有许多变量都是离散型的,如受教育程度、婚姻状态、职业、性别等。通常数据拿到手后,都需要对其进行清洗,例如检查数据中是否存在重复观测、缺失值、异常值等,而且,如果建模的话,还需要对字符型的离散变量做相应的重编码。首先将上面的数据集读入Python的工作环境中:
# 导入第三方包
import pandas as pd
import numpy as np
import seaborn as sns
# 数据读取
income = pd.read_excel(r'C:\Users\Administrator\Desktop\income.xlsx')
# 查看数据集是否存在缺失值
income.apply(lambda x:np.sum(x.isnull()))
见表2-2。
表2-2显示,居民的收入数据集中有3个变量存在数值缺失,分别是居民的工作类型、职业和国籍。缺失值的存在一般都会影响分析或建模的结果,所以需要对缺失数值做相应的处理。
缺失值的处理一般采用三种方法:
一是删除法,即将存在缺失的观测进行删除,如果缺失比例非常小,则删除法是比较合理的,反之,删除比例比较大的缺失值将会丢失一些有用的信息;
二是替换法,即使用一个常数对某个变量的缺失值进行替换,如果缺失的变量是离散型,则可以考虑用众数替换缺失值,如果缺失的变量是数值型,则可以考虑使用均值或中位数替换缺失值;
三是插补法,即运用模型方法,基于未缺失的变量预测缺失变量的值,如常见的回归插补法、多重插补法、拉格朗日插补法等。
由于收入数据集中的3个缺失变量都是离散型变量,这里不妨使用各自的众数来替换缺失值:
# 缺失值处理
income.fillna(value = {'workclass':income.workclass.mode()[0],
'occupation':income.occupation.mode()[0],
'native-country':income['native-country'].mode()[0]}, inplace = True)
2.2.2 数据的探索性分析
在上面的数据清洗过程中,对缺失值采用了替换处理的方法,接下来对居民收入数据集做简单的探索性分析,目的是了解数据背后的特征,如数据的集中趋势、离散趋势、数据形状和变量间的关系等。
首先,需要知道每个变量的基本统计值,如均值、中位数、众数等,只有了解了所需处理的数据特征,才能做到“心中有数”:
# 数值型变量的统计描述,参见表2-3。
income.describe()
如表2-3所示,描述了有关数值型变量的简单统计值,包括非缺失观测的个数(count)、平均值(mean)、标准差(std)、最小值(min)、下四分位数(25%)、中位数(50%)、上四分位数(75%)和最大值(max)。以3万多居民的年龄为例,他们的平均年龄为38.6岁;最小年龄为17岁;最大年龄为90岁;四分之一的居民年龄不超过28岁;一半的居民年龄不超过37岁;四分之三的居民年龄不超过48岁;并且年龄的标准差为13.6岁。同理,读者也可以类似地解释其他数值变量的统计值。
接下来,再来看看数据集中离散型变量的描述性统计值:
# 离散型变量的统计描述,见表2-4。
income.describe(include =[ 'object'])
如表2-4所示,得到的是关于离散变量的统计值,包含每个变量非缺失观测的数量(count)、不同离散值的个数(unique)、出现频次最高的离散值(top)和最高频次数(freq)。以受教育水平变量为例,一共有16种不同的教育水平;3万多居民中,高中毕业的学历是出现最多的;并且一共有10 501名。
数据的分布形状(如偏度、峰度等)可以通过可视化的方法进行展现,这里仅以被调查居民的年龄和每周工作小时数为例,绘制各自的分布形状图:
# 导入绘图模块
import matplotlib.pyplot as plt
# 设置绘图风格
plt.style.use('ggplot')
# 设置多图形的组合
fig, axes = plt.subplots(2, 1)
# 绘制不同收入水平下的年龄核密度图
income.age[income.income == ' <=50K'].plot(kind = 'kde', label = '<=50K', ax = axes[0], legend = True, linestyle = '-')
income.age[income.income == ' >50K'].plot(kind = 'kde', label = '>50K', ax = axes[0], legend = True, linestyle = '--')
# 绘制不同收入水平下的周工作小时数和密度图
income['hours-per-week'][income.income == ' <=50K'].plot(kind = 'kde', label = '<=50K', ax = axes[1], legend = True, linestyle = '-')
income['hours-per-week'][income.income == ' >50K'].plot(kind = 'kde', label = '>50K', ax = axes[1], legend = True, linestyle = '--')
# 显示图形
plt.show()
见图2-8。
如图2-8所示,第一幅图展现的是,在不同收入水平下,年龄的核密度分布图,对于年收入超过5万美元的居民来说,他们的年龄几乎呈现正态分布,而收入低于5万美元的居民,年龄呈现右偏特征,即年龄偏大的居民人数要比年龄偏小的人数多;第二幅图展现了不同收入水平下,周工作小时数的核密度图,很明显,两者的分布趋势非常相似,并且出现局部峰值。如果读者需要研究其他数值型变量的分布形状,按照上面的代码稍做修改即可。
同理,也可以针对离散型变量,对比居民的收入水平高低在性别、种族状态、家庭关系等方面的差异,进而可以发现这些离散变量是否影响收入水平:
# 构造不同收入水平下各种族人数的数据
race = pd.DataFrame(income.groupby(by = ['race','income']).aggregate(np.size).loc[:,'age'])
# 重设行索引
race = race.reset_index()
# 变量重命名
race.rename(columns={'age':'counts'}, inplace=True)
# 排序
race.sort_values(by = ['race','counts'], ascending=False, inplace=True)
# 构造不同收入水平下各家庭关系人数的数据
relationship = pd.DataFrame(income.groupby(by = ['relationship','income']).aggregate(np.size).loc[:,'age'])
relationship = relationship.reset_index()
relationship.rename(columns={'age':'counts'}, inplace=True)
relationship.sort_values(by = ['relationship','counts'], ascending=False, inplace=True)
# 设置图框比例,并绘图
plt.figure(figsize=(9,5))
sns.barplot(x="race", y="counts", hue = 'income', data=race)
plt.show()
plt.figure(figsize=(9,5))
sns.barplot(x="relationship", y="counts", hue = 'income', data=relationship)
plt.show()
见图2-9。
左图反映的是相同的种族下,居民年收入水平高低的人数差异;右图反映的是相同的家庭成员关系下,居民年收入水平高低的人数差异。但无论怎么比较,都发现一个规律,即在某一个相同的水平下(如白种人或未结婚人群中),年收入低于5万美元的人数都要比年收入高于5万美元的人数多,这个应该是抽样导致的差异(数据集中年收入低于5万和高于5万的居民比例大致在75%:25%)。如果读者需要研究其他离散型变量与年收入水平的关系,可以稍稍修改上面的代码,实现可视化的绘制。
2.2.3 数据建模
1.对离散变量重编码
前面提到,由于收入数据集中有很多离散型变量,这样的字符变量是不能直接用于建模的,需要对这些变量进行重编码,关于重编码的方法有多种,如将字符型的值转换为整数型的值、哑变量处理(0-1变量)、One-Hot热编码(类似于哑变量)等。在本案例中,将采用“字符转数值”的方法对离散型变量进行重编码,具体可以通过下面的代码实现:
# 离散变量的重编码
for feature in income.columns:
if income[feature].dtype == 'object':
income[feature] = pd.Categorical(income[feature]).codes
income.head()
表2-5中的结果就是对字符型离散变量的重编码效果,所有的字符型变量都变成了整数型变量,如workclass、education、marital-status等,接下来就基于这个处理好的数据集对收入水平income进行预测。
在原本的居民收入数据集中,关于受教育程度的有两个变量,一个是education(教育水平),另一个是education-num(受教育时长),而且这两个变量的值都是一一对应的,只不过一个是字符型,另一个是对应的数值型,如果将这两个变量都包含在模型中的话,就会产生信息的冗余;fnlwgt变量代表的是一种序号,其对收入水平的高低并没有实际意义。故为了避免冗余信息和无意义变量对模型的影响,考虑将education变量和fnlwgt变量从数据集中删除:
# 删除变量
income.drop(['education','fnlwgt'], axis = 1, inplace = True)
income.head()
见表2-6。
表2-6中呈现的就是经处理“干净”的数据集,所要预测的变量就是income,该变量是二元变量,对其预测的实质就是对年收入水平的分类(一个新样本进来,通过分类模型,可以将该样本分为哪一种收入水平)。
关于分类模型有很多种,如Logistic模型、决策树、K近邻、朴素贝叶斯模型、支持向量机、随机森林、梯度提升树GBDT模型等。本案例将对比使用K近邻和GBDT两种分类器,因为通常情况下,都会选用多个模型作为备选,通过对比才能得知哪种模型可以更好地拟合数据。接下来就进一步说明如何针对分类问题,从零开始完成建模的步骤。
2.拆分数据集
基于上面的“干净”数据集,需要将其拆分为两个部分,一部分用于分类器模型的构建,另一部分用于分类器模型的评估,这样做的目的是避免分类器模型过拟合或欠拟合。如果模型在训练集上表现很好,而在测试集中表现很差,则说明分类器模型属于过拟合状态;如果模型在训练过程中都不能很好地拟合数据,那说明模型属于欠拟合状态。通常情况下,会把训练集和测试集的比例分配为75%和25%:
# 数据拆分
from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(income.loc[:,'age':'native-country'],
income['income'], train_size = 0.75,
random_state = 1234)
print('训练数据集共有%d条观测' %X_train.shape[0])
print('测试数据集共有%d条观测' %X_test.shape[0])
结果显示,运用随机抽样的方法,将数据集拆分为两部分,其中训练数据集包含24 420条样本,测试数据集包含8 141条样本,下面将运用拆分好的训练数据集开始构建K近邻和GBDT两种分类器。
3.默认参数的模型构建
# 导入k近邻模型的类
from sklearn.neighbors import KNeighborsClassifier
# 构建k近邻模型
kn = KNeighborsClassifier()
kn.fit(X_train, y_train)
print(kn)
首先,针对K近邻模型,这里直接调用sklearn子模块neighbors中的KNeighborsClassifier类,并且使用模型的默认参数,即让K近邻模型自动挑选最佳的搜寻近邻算法(algorithm=‘auto’)、使用欧氏距离公式计算样本间的距离(p=2)、指定未知分类样本的近邻个数为5(n_neighbors=5)而且所有近邻样本的权重都相等(weights=‘uniform’)。如果读者想了解更多有关K近邻算法的理论可以翻阅第11章。
其次,针对GBDT模型,可以调用sklearn子模块ensemble中的GradientBoostingClassifier类,同样先尝试使用该模型的默认参数,即让模型的学习率(迭代步长)为0.1(learning_rate=0.1)、损失函数使用的是对数损失函数(loss=‘deviance’)、生成100棵基础决策树(n_estimators=100),并且每棵基础决策树的最大深度为3(max_depth=3),中间节点(非叶节点)的最小样本量为2(min_samples_split=2),叶节点的最小样本量为1(min_samples_leaf=1),每一棵树的训练都不会基于上一棵树的结果(warm_start=False)。如果读者想继续了解更多GBDT相关的理论知识点,可以参考第14章。
如上K近邻模型和GBDT模型都是直接调用第三方模块,并且都是基于默认参数的模型构建,虽然这个方法可行,但是往往有时默认参数并不能得到最佳的拟合效果。所以,需要不停地调整模型参数,例如K近邻模型设置不同的K值、GBDT模型中设置不同的学习率、基础决策树的数量、基础决策树的最大深度等。然后基于这些不同的参数值,验证哪种组合的参数会得到效果最佳的模型,看似可以通过for循环依次迭代来完成,但是效率会比较慢。一个好消息是,读者可以不用手写for循环,找到最佳的参数,在Python的sklearn模块中提供了网格搜索法,目的就是找到上面提到的最佳参数。接下来,就带着大家使用Python中的网格搜索法来完成模型的参数选择。
4.模型网格搜索
同样,先对K近邻模型的参数进行网格搜索,这里仅考虑模型中n_neighbors参数的不同选择。执行脚本如下:
# K近邻模型的网格搜索法
# 导入网格搜索法的函数
from sklearn.grid_search import GridSearchCV
# 选择不同的参数
k_options = list(range(1,12))
parameters = {'n_neighbors':k_options}
# 搜索不同的K值
grid_kn = GridSearchCV(estimator = KNeighborsClassifier(), param_grid = parameters, cv=10, scoring='accuracy', verbose=0, n_jobs=2)
grid_kn.fit(X_train, y_train)
print(grid_kn)
# 结果输出
grid_kn.grid_scores_, grid_kn.best_params_, grid_kn.best_score_
简单解释一下GridSearchCV函数中的几个参数含义,estimator参数接受一个指定的模型,这里为K近邻模型的类;param_grid用来指定模型需要搜索的参数列表对象,这里是K近邻模型中n_neighbors参数的11种可能值;cv是指网格搜索需要经过10重交叉验证;scoring指定模型评估的度量值,这里选用的是模型预测的准确率。
通过网格搜索的计算,得到三部分的结果,第一部分包含了11种K值下的平均准确率(因为做了10重交叉验证);第二部分选择出了最佳的K值,K值为6;第三部分是当K值为6时模型的最佳平均准确率,且准确率为84.78%。
接下来,对GBDT模型的参数进行网格搜索,搜索的参数包含三个,分别是模型的学习速率、生成的基础决策树个数和每个基础决策树的最大深度。具体执行代码如下:
# GBDT模型的网格搜索法
# 选择不同的参数
learning_rate_options = [0.01,0.05,0.1]
max_depth_options = [3,5,7,9]
n_estimators_options = [100,300,500]
parameters = {'learning_rate':learning_rate_options,'max_depth':max_depth_options,'n_estimators':n_estimators_options}
grid_gbdt = GridSearchCV(estimator = GradientBoostingClassifier(), param_grid = parameters, cv=10, scoring='accuracy', n_jobs=4)
grid_gbdt.fit(X_train, y_train)
# 结果输出
grid_gbdt.grid_scores_, grid_gbdt.best_params_, grid_gbdt.best_score_
输出的结果与K近邻结构相似,仍然包含三个部分。限于篇幅的影响,上面的结果中并没有显示所有参数的组合,从第二部分的结果可知,最佳的模型学习率为0.05,生成的基础决策树个数为300棵,并且每棵基础决策树的最大深度为5。这样的组合可以使GBDT模型的平均准确率达到87.51%。
5.模型预测与评估
上文中,我们花了一部分的篇幅来介绍基于“干净”数据集的模型构建,下一步要做的就是使用得到的分类器对测试数据集进行预测,进而验证模型在样本外的表现能力,同时,也可以从横向的角度来比较模型之间的好坏。
通常,验证模型好坏的方法有多种。例如,对于预测的连续变量来说,常用的衡量指标有均方误差(MSE)和均方根误差(RMSE);对于预测的分类变量来说,常用的衡量指标有混淆矩阵中的准确率、ROC曲线下的面积AUC、K-S值等。接下来,依次对上文中构建的四种模型进行预测和评估。
6.默认的K近邻模型
# 预测测试集
kn_pred = kn.predict(X_test)
print(pd.crosstab(kn_pred, y_test))
# 模型得分
print('模型在训练集上的准确率%f' %kn.score(X_train,y_train))
print('模型在测试集上的准确率%f' %kn.score(X_test,y_test))
见表2-7。
模型在训练集上的准确率0.890500
模型在测试集上的准确率0.838840
如上结果所示,第一部分是混淆矩阵,矩阵中的行是模型的预测值,矩阵中的列是测试集的实际值,主对角线就是模型预测正确的数量(5637和1192),589和723就是模型预测错误的数量。经过计算,得到第二部分的结论,即模型在训练集中的准确率为89.1%,但在测试集上的错误率超过16%(1-0.839),说明默认参数下的KNN模型可能存在过拟合的风险。
模型的准确率就是基于混淆矩阵计算的,但是该方法存在一定的弊端,即如果数据本身存在一定的不平衡时(正负样本的比例差异较大),一定会导致准确率很高,但并不一定说明模型就是理想的。这里再介绍一种常用的方法,就是绘制ROC曲线,并计算曲线下的面积AUC值:
# 导入模型评估模块
from sklearn import metrics
# 计算ROC曲线的x轴和y轴数据
fpr, tpr, _ = metrics.roc_curve(y_test, kn.predict_proba(X_test)[:,1])
# 绘制ROC曲线
plt.plot(fpr, tpr, linestyle = 'solid', color = 'red')
# 添加阴影
plt.stackplot(fpr, tpr, color = 'steelblue')
# 绘制参考线
plt.plot([0,1],[0,1], linestyle = 'dashed', color = 'black')
# 往图中添加文本
plt.text(0.6,0.4,'AUC=%.3f' % metrics.auc(fpr,tpr), fontdict = dict(size = 18))
plt.show()
见图2-10。
图2-10中绘制了模型的ROC曲线,经计算得知,该曲线下的面积AUC为0.865。如果读者使用AUC来评估模型的好坏,那应该希望AUC越大越好。一般而言,当AUC的值超过0.8时,基本上就可以认为模型比较合理。所以,基于默认参数的K近邻模型在居民收入数据集上的表现还算理想。
7.网格搜索的K近邻模型
# 预测测试集
grid_kn_pred = grid_kn.predict(X_test)
print(pd.crosstab(grid_kn_pred, y_test))
# 模型得分
print('模型在训练集上的准确率%f' %grid_kn.score(X_train,y_train))
print('模型在测试集上的准确率%f' %grid_kn.score(X_test,y_test))
# 绘制ROC曲线
fpr, tpr, _ = metrics.roc_curve(y_test, grid_kn.predict_proba(X_test)[:,1])
plt.plot(fpr, tpr, linestyle = 'solid', color = 'red')
plt.stackplot(fpr, tpr, color = 'steelblue')
plt.plot([0,1],[0,1], linestyle = 'dashed', color = 'black')
plt.text(0.6,0.4,'AUC=%.3f' % metrics.auc(fpr,tpr), fontdict = dict(size = 18))
plt.show()
见表2-8。
模型在训练集上的准确率0.882473
模型在测试集上的准确率0.845351
见图2-11。
相比于默认参数的K近邻模型来说,经过网格搜索后的模型在训练数据集上的准确率下降了,但在测试数据集上的准确率提高了,这也是我们所期望的,说明优化后的模型在预测效果上更加优秀,并且两者差异的缩小也能够降低模型过拟合的可能。再来看看ROC曲线下的面积,网格搜索后的K近邻模型所对应的AUC为0.87,相比于原先的KNN模型提高了一点。所以,从模型的稳定性来看,网格搜索后的K近邻模型比原始的K近邻模型更加优秀。
8.默认的GBDT模型
# 预测测试集
grid_kn_pred = grid_kn.predict(X_test)
print(pd.crosstab(grid_kn_pred, y_test))
# 模型得分
print('模型在训练集上的准确率%f' %grid_kn.score(X_train,y_train))
print('模型在测试集上的准确率%f' %grid_kn.score(X_test,y_test))
# 绘制ROC曲线
fpr, tpr, _ = metrics.roc_curve(y_test, grid_kn.predict_proba(X_test)[:,1])
plt.plot(fpr, tpr, linestyle = 'solid', color = 'red')
plt.stackplot(fpr, tpr, color = 'steelblue')
plt.plot([0,1],[0,1], linestyle = 'dashed', color = 'black')
plt.text(0.6,0.4,'AUC=%.3f' % metrics.auc(fpr,tpr), fontdict = dict(size = 18))
plt.show()
见表2-9。
模型在训练集上的准确率0.869451
模型在测试集上的准确率0.858985
见图2-12。
如上结果所示,集成算法GBDT在测试集上的表现明显要比K近邻算法优秀,这就是基于多棵决策树进行投票的优点。该模型在训练集和测试集上的表现都非常好,准确率均超过85%,而且AUC值也是前面两种模型中最高的,达到了0.913。
9.网络搜索的GBDT模型
# 预测测试集
grid_gbdt_pred = grid_gbdt.predict(X_test)
print(pd.crosstab(grid_gbdt_pred, y_test))
# 模型得分
print('模型在训练集上的准确率%f' %grid_gbdt.score(X_train,y_train))
print('模型在测试集上的准确率%f' %grid_gbdt.score(X_test,y_test))
# 绘制ROC曲线
fpr, tpr, _ = metrics.roc_curve(y_test, grid_gbdt_pred)
plt.plot(fpr, tpr, linestyle = 'solid', color = 'red')
plt.stackplot(fpr, tpr, color = 'steelblue')
plt.plot([0,1],[0,1], linestyle = 'dashed', color = 'black')
plt.text(0.6,0.4,'AUC=%.3f' % metrics.auc(fpr,tpr), fontdict = dict(size = 18))
plt.show()
见表2-10。
模型在训练集上的准确率0.890336
模型在测试集上的准确率0.870900
见图2-13。
如上展示的是基于网格搜索后的GBDT模型的表现,从准确率来看,是4个模型中表现最佳的,该模型在训练集上的准确率接近90%,同时,在测试集上的准确率也超过87%;从绘制的ROC曲线来看,AUC的值也是最高的,超过0.92。
不论是K近邻模型,还是梯度提升树GBDT模型,都可以通过网格搜索法找到各自的最佳模型参数,而且这些最佳参数的组合一般都会使模型比较优秀和健壮。所以,纵向比较默认参数的模型和网格搜索后的最佳参数模型,后者可能是比较好的选择(尽管后者可能会花费更多的运行时间);横向比较单一模型和集成模型,集成模型一般会比单一模型表现优秀。
2.3 本章小结
本章解决的是一个分类问题的预测,通过实际的案例介绍了有关数据挖掘的重要流程,包括数据的清洗、数据的探索性分析、模型构建和模型的评估。通过本章的学习,进一步加强对数据挖掘流程的理解,以便读者在实际的学习和工作中能够按部就班地完成数据挖掘任务。