Titanic:数据挖掘入门的第一步

关键词: 数据挖掘 机器学习 预测 Python

这个项目是kaggle上的一个入门级项目,专门给新手做的,这个项目比较简单,但是涉及到了数据挖掘和机器学习的各个方面,有关的文章和解释和很多,所以呢,这个比较适合新手上车
看完这个教程并从头到尾跟着做,恭喜你,已经入了数据挖掘的坑,如果你要在kaggle上完成这个项目,其中的步骤我就不一一赘述,请移步kaggle

泰坦尼克号的失事大家都应该有所了解,这是历史上最著名的船只失事事件。1912年4月15日,泰坦尼克号在处女航途中撞上冰山沉没,造成2224名乘客和船组人员中1502人遇难。这一耸人听闻的悲剧震惊了国际社会,从此为船只制定了更好的安全条例。
泰坦尼克号沉没事件是一个历史悠久的悲剧,不仅被拍成了电影,写成了歌,现在它又被用作例子来学习数据挖掘。用这个广为人知的事件作为背景来入门,可以说非常有文化底蕴了。
虽然说生还概率什么的总有一些运气的成分,但是这里面毫无疑问的有一些因素可以让乘客的生还概率大大增加,比如女性、孩子、以及社会上层的乘客,所以我们才有了可挖掘的余地,如果是想要预测一件完全玄学的事情,那我建议你去空间转一下锦鲤

要求

It is your job to predict if a passenger survived the sinking of the Titanic or not.
For each PassengerId in the test set, you must predict a 0 or 1 value for the Survived variable.

这个项目你要做的,就是分析哪些乘客更容易生还,然后预测一些乘客中哪些人能够成功生还,0表示不能生还,1表示能够生还。
声明:这个文章的notebook是fork自 i,Coder 的 EDA To Prediction(DieTanic),大家喜欢可以去为他vote一下哦

本项目数据挖掘的步骤

一. 探索性数据分析

1. 对特征的分析
2. 寻找可能的特征之间的联系或者趋势

二. 特征工程以及数据清洗

1. 增加一些新的特征
2. 去除一些冗余特征
3. 将特征转化为适合建模的数据

三. 建立预测模型

1. 运行基本的算法
2. 交叉验证
3. 算法集成
4. 导入特征提取

探索性数据分析

这个步骤的作用是对整体数据进行一个概览,将数据用可视化形式表现出来,然后初步判断可能的特征。

import numpy as np 
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('fivethirtyeight')
import warnings
warnings.filterwarnings('ignore')

首先导入相应的包,在这个项目中我们主要用到numpy+pandas+matplotlib+seaborn+sklearn

numpy是Python的一个开源的科学计算包,提供了强大的矩阵处理、矢量处理、使用的线性代数、傅里叶变换等运算库。

pandas提供了高效操作大型数据集所需的工具,可以利用它快速便捷的处理数据,我们主要用到它提供的dataframe数据结构来存储我们的信息。

matplotlib是一个Python的2D绘图库,主要用于数据可视化

seaborn也是Python的一个绘图库,基于matplotlib进行开发封装的,matplotlib虽然很强大,但是本身很复杂,画的图要经过大量调整才能变精致,而seaborn十分好用,操作简单,并且能理解pandas的dataframe。

sklearn是机器学习领域最知名的Python模块之一,包括了很多种机器学习的方式。

读取数据

要获取数据的童鞋请到Titanic项目相关数据链接这里下,一共三个,放在你的.py文件相同目录就好了,像这样:

三个csv文件是下载的数据文件

data=pd.read_csv('train.csv')

从文件中读取数据,保存到一个dataframe中

data.head()

显示头5行数据

第一步是处理空数据,我们来看看有多少空数据 使用dataframe的isnull().sum()方法

data.isnull().sum() #checking for total null values


可以看到Age和Cabin的空数据比较多,这是我们应该处理的,但是别急,我们这是探索性数据分析,等下一步数据清洗的时候再处理

对整体生还率有一个感性认识

我们来看看到底有多惨烈吧

f,ax=plt.subplots(1,2,figsize=(18,8))
data['Survived'].value_counts().plot.pie(explode=[0,0.1],autopct='%1.1f%%',ax=ax[0],shadow=True)
ax[0].set_title('Survived')
ax[0].set_ylabel('')
sns.countplot('Survived',data=data,ax=ax[1])
ax[1].set_title('Survived')
plt.show()

运行结果如下:

这里使用了matplotlib和seaborn两个框架来画图,左边的图是用matplotlib来画的,右边是用seaborn来画的
首先用plt的subplots()方法来获得图像区域,参数一和二分别表示一行,两张图,第三个参数是图像的长宽。
返回的第一个f表示figure,整个图像区域,ax表示子图索引,ax[0]表示第一个子图,在左边,ax[1]表示第二个子图,在右边,使用的时候就让它返回f,ax。这里可以看出这两个框架是怎么联合使用的,matplotlib来生图表区域,seaborn来生成子图,看到第二个图的绘画方式你能够理解为什么seaborn会更受推崇,它的参数非常简洁,想要一张直方图的话就调用countplot方法,第一个参数是你想要统计的列,输入标签名来表示,第二个参数是你的数据集,这里传入data,第三个参数指定图表表现在哪个子图上,这里指定ax[1]。

通过图表我们可以得知,在891名乘客中,仅有350生还,生还率有38.4%,我们接下来还要探究一下为什么这些人可以生还

下一步,我们分析一下,在我们的数据集里面,有哪几种类型的数据

认识数据集的数据类型

因为针对不同的数据类型,我们有不同的处理方法,因此,我们要先了解我们的数据集里面,有哪些数据类型
我们所有的影响因素有:

标签 释义 取值类型
PassengerId 乘客ID int型数据
Surviveed 是否生还 0或者1
Pclass 乘客的社会等级 1或者2或者3
Name 乘客名字 string
Sex 乘客性别 male或者female
Age 乘客年龄 float一位小数
SibSP 在船上的家庭人员数 int型数据
Parch 在船上的双亲数 int型数据
Ticket 船票编号 string
Fare 船票费用 float四位小数
Cabin 船舱号 string
Embarked 上船港口 C或者Q或者S

一共有12个标签,看上去什么都给我们整理好了,我们还需要处理啥呢,但是接下来我会向你证明他们的处理是十分必要的。

在数据挖掘领域,数据的属性分为四种:

  1. 标称属性(norminal),比如红色、蓝色、绿色
  2. 布尔属性(binary),比如生存或者死亡
  3. 序数属性(ordinary),比如乘客社会等级(贵族、爵士、商人……)
  4. 数值属性(numeric),比如年龄(12,32,55……)

有同学可能会问了,标称属性和序数属性有什么不同?其实标称属性的每个值代表一种类别、一种状态,可以看做是枚举的,其中每一个值跟其他值没有联系;而序数属性的每个值之间是有序的,比如饮料的大小有小杯、中杯、大杯,那这个就是序数属性,饮料的口味有草莓味、香橙味和葡萄味,那个就是标称属性,现在明白了哇。

下面我们按照这个分类一一来看看我们的标签

标称属性

标称属性有Name,Ticket,Cabin,Embarked。其中大部分是没什么用的。
Name,Ticket和生还率没有什么太大的联系,我们先暂时不管它
Cabin的数据缺失率达到了687/891=77.1%,就别指望它了吧
我们来看看Embarked标签和生还率的关系

f,ax=plt.subplots(1,2,figsize=(18,8))
sns.countplot('Embarked',data=data,order=['Q','C','S'],ax=ax[0])
ax[0].set_title('total number: Embarked')
sns.countplot('Embarked',hue='Survived',order=['Q','C','S'],data=data,ax=ax[1])
ax[1].set_title('Embarked:survived or not')
plt.show()


从图中我们可以看到,登船的人数按Q,C,S逐渐增加,最多的港口是S港口,有600+的乘客;在生还率方面呢,Q港口和S港口上来的人存活人数都比遇难人数要多,C港口上来的人遇难人数比存活人数要多,这说明什么呢?QS港口上来的乘客逃生意识比较强,或者他们是被考虑应该优先逃生的那群人,要知道,同一个港口上船的人有可能是同一社会阶层的。

布尔属性

布尔属性有Sex,Survived。这两个属性都比较重要,下面我们来分析一下sex

f,ax=plt.subplots(1,2,figsize=(18,8))
data[['Sex','Survived']].groupby(['Sex']).mean().plot.bar(ax=ax[0])
ax[0].set_title('Survived vs Sex')
sns.countplot('Sex',hue='Survived',data=data,ax=ax[1])
ax[1].set_title('Sex:Survived vs Dead')
plt.show()


很有趣,男性的总数其实是比女性多的,但是女性幸存的数量几乎是男性的两倍,女性的幸存率是大概75%,而男性的幸存率是18-19%。
“女士优先”这个原则在这里起了主导作用,这也告诉我们,在预测的时候,应该把性别当做一个很重要的指标来看待。

序数属性

序数属性有Pclass,代表乘客的社会等级,也就是阶层,1代表阶层最高,3代表最低

pd.crosstab(data.Pclass,data.Survived,margins=True).style.background_gradient(cmap='summer_r')

f,ax=plt.subplots(1,2,figsize=(18,8))
data['Pclass'].value_counts().plot.bar(color=['#CD7F32','#FFDF00','#D3D3D3'],ax=ax[0])
ax[0].set_title('Number Of Passengers By Pclass')
ax[0].set_ylabel('Count')
sns.countplot('Pclass',hue='Survived',data=data,ax=ax[1])
ax[1].set_title('Pclass:Survived vs Dead')
plt.show()


俗话说“money cannot buy everything”,我反手就是一个板砖,Pclass1的幸存率达到了63%,Pclass2的也有48%,而Pclass3的呢,只有25%。
穷人真是连活下去的权利都没有,啧啧啧。

数值属性

数值属性比较多,有Age,SibSp,Parch,Fare,PassengerId。其中PassengerId是为了方便记录进行随机编的号,对我们的分析毫无影响,不管它,先来看一下Age。

Age

这里要使用到图表里面的violin图,这种图比较适合连续性变量与离散变量之间的关系表示,来看看图就知道了
我们之前说过,幸存率跟性别和社会等级关系非常大,那么我们就用两个图表示:幸存率与年龄与性别,幸存率与年龄与社会等级

f,ax=plt.subplots(1,2,figsize=(18,8))
sns.violinplot("Pclass","Age", hue="Survived", data=data,split=True,ax=ax[0])
ax[0].set_title('Pclass and Age vs Survived')
ax[0].set_yticks(range(0,110,10))
sns.violinplot("Sex","Age", hue="Survived", data=data,split=True,ax=ax[1])
ax[1].set_title('Sex and Age vs Survived')
ax[1].set_yticks(range(0,110,10))
plt.show()


emmmmm,乍一看好像没什么大发现,但是仔细看看,可以发现以下几点:
1. 儿童无论性别,无论社会等级,幸存的概率都会大很多(危急时刻保护儿童优先已经成为常识)
2. 除了第一等级的人群,18-35岁的男性死的最多(因为已婚或者比较有奉献精神)
3. 对于女性而言,22-40岁的人幸存几率都很大,跟社会等级无关

SibSp Parch

那再来看一下SibSp和Parch,他们都是表示乘客的家庭亲属情况的,通常我们都会觉得,拖家带口的不容易逃生,因为谁也割舍不下,但是他们又是最容易逃生的,因为会有人帮着你,拉你一把什么的,到底事实是什么样的呢,我们用数据说话。
因为SibSp和Parch的数量所造成的幸存率影响是一样的(你的兄弟姐妹对象能帮你,你的父母也能,你的兄弟姐妹对象能拖你后腿你的父母也一样)
所以我们把他们合起来

data['Family_size']=data['SibSp']+data['Parch']

我们新建一个列’Family_size’来保存这个结果,而不是修改原来的任何一个数据,要养成好习惯

现在我们再来看一下亲属数量和幸存与否之间的关系

f,ax=plt.subplots(1,2,figsize=(18,8))
sns.countplot('Family_size',data=data,ax=ax[0])
ax[0].set_title('count of Family_size')
sns.countplot('Family_size',hue='Survived',data=data,ax=ax[1])
ax[1].set_title('Survived vs Family size')
plt.show()


居然还有拖家带口10个人的,好吧我们不管他。我们从图上可以看到,当一个人是孤零零一人的时候,生还的几率是非常小的,当有3个及以下家属的时候,生还的几率才会增加,但是家属数量大于3的时候,生还几率又变小了,看来带上一些家属随行还是更加安全的做法,毕竟,在最困难的时候,永远都是你的家人stand by your side.

Fare

下面再来看看Fare,我们再来用直方图看一下吧!

f,ax=plt.subplots(1,2,figsize=(18,8))
sns.countplot('Fare',data=data,ax=ax[0])
ax[0].set_title('distribution of Fare')
sns.countplot('Fare',hue='Survived',data=data,ax=ax[1])
ax[1].set_title('Survived vs Fare')
plt.show()


噗……错了
应该用分布图,因为船费好像不是统一价格

再来

f,ax=plt.subplots(1,3,figsize=(20,8))
sns.distplot(data[data['Pclass']==1].Fare,ax=ax[0])
ax[0].set_title('Fares in Pclass 1')
sns.distplot(data[data['Pclass']==2].Fare,ax=ax[1])
ax[1].set_title('Fares in Pclass 2')
sns.distplot(data[data['Pclass']==3].Fare,ax=ax[2])
ax[2].set_title('Fares in Pclass 3')
plt.show()


这就好看多了嘛!
你可以看到不同人的船费是不同的,为什么呢,我也不懂

好啦!所有标签我们都看完了,大概总结一下:

  1. 影响最大的两个因素是sex 和 Pclass
  2. 年龄:10岁以下的小孩,22-40岁的女性,生存几率都很大
  3. 登船港口:从C口登上的乘客生还几率比较高,甚至高过了大部分是等级1的乘客的S口
  4. 亲属:携带1至3位亲属的乘客生还率都比较高

下面我们再进行更详细的数据分析

特征工程以及数据清洗

处理空数据

前面说过了,Age属性里面有好多缺失数据,怎么填补它们呢?
我们很容易想到用平均值来代替,但是问题是,这些乘客里面的年龄段是很广的,你怎么可以给一个4岁的小孩安排一个29岁的平均值呢,我们得找一个办法大致推测出这个年龄缺失的人他的年龄可能在什么范围,再看看我们还有哪些数据可以用呢?等级、性别和船费好像都不能说明什么,十岁和四十岁的人可能在这些属性上都有可能一样。
我们想到了称呼,注意Name里面:

data.Name


我们可以利用这些称呼,来判断大致年龄,所以我们要先从Name里面提取出称呼来,我们用正则表达式:[A-Za-z]+).
我们新开一列数据,用来保存我们提取出的称呼

data['Initial']=0
for i in data:
    data['Initial']=data.Name.str.extract('([A-Za-z]+)\.') #lets extract the Salutations

来看看我们提取的结果:

pd.crosstab(data.Initial,data.Sex).T.style.background_gradient(cmap='summer_r') #Checking the Initials with the Sex


看来还有一些错误拼写,大辣鸡!我们就原谅他们吧,我们辛苦一点,手动替换。

data['Initial'].replace(['Mlle','Mme','Ms','Dr','Major','Lady','Countess','Jonkheer','Col','Rev','Capt','Sir','Don'],['Miss','Miss','Miss','Mr','Mr','Mrs','Mrs','Other','Other','Other','Mr','Mr','Mr'],inplace=True)

然后再来看一下我们分好组的乘客年龄段是怎么样的

data[['Initial','Age']].groupby(['Initial']).mean()


还是能判断出来一些年龄段的
好,现在我们来填充那些空的数据

data.loc[(data.Age.isnull())&(data.Initial=='Mr'),'Age']=33
data.loc[(data.Age.isnull())&(data.Initial=='Mrs'),'Age']=36
data.loc[(data.Age.isnull())&(data.Initial=='Master'),'Age']=5
data.loc[(data.Age.isnull())&(data.Initial=='Miss'),'Age']=22
data.loc[(data.Age.isnull())&(data.Initial=='Other'),'Age']=46

再来确认一下有没有遗漏的

data['Age'].isnull().sum()

好了我们再看Cabin,可是它的缺失率是在太高,就算填补回来,准确率也会大大下降,我们不能冒着使用错误数据的方法强行凑整,只能放弃

去除冗余数据

我们去掉Cabin和将SibSp、Parch合在一起之后,还有10个标签,是不是会有一些标签是多余的呢?
我们可以用pandas的协方差运算和seaborn的热力图来一探究竟

sns.heatmap(data.corr(),annot=True,cmap='RdYlGn',linewidths=0.2) #data.corr()-->correlation matrix
fig=plt.gcf()
fig.set_size_inches(10,8)
plt.show()


这个图很炫酷,我简单教大家怎么看,要看懂这个图,得明白相关系数是个什么鬼,它是表示两个变量之间一个随着另一个如何变化的关系,分为正相关、负相关和压根不相关,相关系数大于零即为正相关,即一个变大,另一个跟着变大,相关系数小于零则为负相关,即一个变大另一个变小。在这个图里,我们可以看到对角线都是1,因为自己和自己肯定是正相关的。如果两个数据有冗余,那么他们的相关系数一定接近于1,我们可以看到SibSp和Parch的相关系数达到了0.41,这也证实了我们将他们合在一块是合理的。

特征工程

将年龄分段

对于后面的机器学习来说,越连续性的变量越不利,机器学习大部分是个分类器(clasifier),你提供的训练数据集类别越多,就越难以聚合,因此我们要将它们从连续型值变成聚合值,一般使用分箱(binning)或者规范化(normalization)。
我们在这里用分箱的办法将年龄分成5个段,80/5=16,所以箱子大小是16

data['Age_band']=0
data.loc[data['Age']<=16,'Age_band']=0
data.loc[(data['Age']>16)&(data['Age']<=32),'Age_band']=1
data.loc[(data['Age']>32)&(data['Age']<=48),'Age_band']=2
data.loc[(data['Age']>48)&(data['Age']<=64),'Age_band']=3
data.loc[data['Age']>64,'Age_band']=4

来看一下结果:

data['Age_band'].value_counts().to_frame().style.background_gradient(cmap='summer')

新增Family_size和Alone

Family_size的增加应该属于特征工程的范畴,只不过我们在之前的分析之后顺手分好了
现在加上一个Alone属性,因为家属过多或者独身一人都会导致生还率变低,因此有必要将这个人是否孤身一人单独列出作为一个特征

data['Alone']=0
data.loc[data.Family_Size==0,'Alone']=1

将船费分段聚合

前面也说到了,船费变动很大,因人而异,所以我们要将船费分段表示以构建对后面机器学习有利的特征
我们会使用到的是pandas的qcut方法,跟分箱类似,这个方法会根据我们传入的箱子数这个参数来进行分箱,很省事

data['Fare_Range']=pd.qcut(data['Fare'],4)

来看一下船费段与幸存率的关系:

data.groupby(['Fare_Range'])['Survived'].mean().to_frame().style.background_gradient(cmap='summer_r')

但是现在每个箱子里面仍然是连续性值,还不利于后面的机器学习,我们将每个箱子定义为一个值,就像Age_band那样

data['Fare_cat']=0
data.loc[data['Fare']<=7.91,'Fare_cat']=0
data.loc[(data['Fare']>7.91)&(data['Fare']<=14.454),'Fare_cat']=1
data.loc[(data['Fare']>14.454)&(data['Fare']<=31),'Fare_cat']=2
data.loc[(data['Fare']>31)&(data['Fare']<=513),'Fare_cat']=3

将string值转换为数字值

机器学习还不能处理的是string类的值的简单分类,因此我们还是将他们转化为int好一点

data['Sex'].replace(['male','female'],[0,1],inplace=True)
data['Embarked'].replace(['S','C','Q'],[0,1,2],inplace=True)
data['Initial'].replace(['Mr','Mrs','Miss','Master','Other'],[0,1,2,3,4],inplace=True)

丢弃不要的值

有一些值我们已经充分利用过,或者没法用,这时候我们就要狠心一点,将他们从数据集中丢弃,不然白浪费我们那么多资源和时间用在机器学习上

Name:这个值对我们的预测没什么用
Age:我们已经有了Age_band,这个就没什么作用了
Ticket:我们已经有了Fare_cat,这个也没什么用了
Fare:我们已经有了Fare_cat,这个也没什么用了
Cabin:缺失数据太多,放弃
Fare_range:我们已经有了Fare_cat,这个也没什么用了
PassengerId:没用

data.drop(['Name','Age','Ticket','Fare','Cabin','Fare_Range','Initial','SibSp','Parch','PassengerId'],axis=1,inplace=True)

再看看最终的相关系数热力图:

sns.heatmap(data.corr(),annot=True,cmap='RdYlGn',linewidths=0.2,annot_kws={'size':20})
fig=plt.gcf()
fig.set_size_inches(18,15)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.show()

建立预测模型

终于到了最激动人心的一步了,是不是以为自己即将要接触高深复杂,blingbling的神经网络,人工智能了?
别着急,其实也就那么一回事

我们在sklearn中使用这6个机器学习方法:

  1. Logistic Regression 逻辑回归
  2. Support Vector Machines(Linear and radial) 支持向量机(线性和径向两种内核)
  3. Random Forest 随机森林
  4. K-Nearest Neighbours 也叫KNN或者K最近邻分类算法
  5. Naive Bayes 朴素贝叶斯
  6. Decision Tree 决策树

第一步当然是引入sklearn框架

#importing all the required ML packages
from sklearn.linear_model import LogisticRegression #logistic regression
from sklearn import svm #support vector Machine
from sklearn.ensemble import RandomForestClassifier #Random Forest
from sklearn.neighbors import KNeighborsClassifier #KNN
from sklearn.naive_bayes import GaussianNB #Naive bayes
from sklearn.tree import DecisionTreeClassifier #Decision Tree
from sklearn.model_selection import train_test_split #training and testing data split
from sklearn import metrics #accuracy measure
from sklearn.metrics import confusion_matrix #for confusion matrix

下面来划分训练集和测试集,并提取标签

train,test=train_test_split(data,test_size=0.3,random_state=0,stratify=data['Survived'])
train_X=train[train.columns[1:]]
train_Y=train[train.columns[:1]]
test_X=test[test.columns[1:]]
test_Y=test[test.columns[:1]]
X=data[data.columns[1:]]
Y=data['Survived']

将训练集和测试集以3:1划分,并且指定’Survived’为结果指标
这里sklearn调用每个机器学习算法的方式都十分傻瓜式,得到一个模型,再把训练集塞进去就行了,再调用一个预测方法,把测试集往里头一塞就完事,同时它自己也提供了衡量方法,方便你对每个算法的准确率进行衡量。
下面来看看每个算法的表现吧

径向支持向量机

model=svm.SVC(kernel='rbf',C=1,gamma=0.1)
model.fit(train_X,train_Y)
prediction1=model.predict(test_X)
print('Accuracy for rbf SVM is ',metrics.accuracy_score(prediction1,test_Y))

线性支持向量机

model=svm.SVC(kernel='linear',C=0.1,gamma=0.1)
model.fit(train_X,train_Y)
prediction2=model.predict(test_X)
print('Accuracy for linear SVM is',metrics.accuracy_score(prediction2,test_Y))

逻辑回归

model = LogisticRegression()
model.fit(train_X,train_Y)
prediction3=model.predict(test_X)
print('The accuracy of the Logistic Regression is',metrics.accuracy_score(prediction3,test_Y))

决策树

model=DecisionTreeClassifier()
model.fit(train_X,train_Y)
prediction4=model.predict(test_X)
print('The accuracy of the Decision Tree is',metrics.accuracy_score(prediction4,test_Y))

KNN

model=KNeighborsClassifier() 
model.fit(train_X,train_Y)
prediction5=model.predict(test_X)
print('The accuracy of the KNN is',metrics.accuracy_score(prediction5,test_Y))

高斯朴素贝叶斯

model=GaussianNB()
model.fit(train_X,train_Y)
prediction6=model.predict(test_X)
print('The accuracy of the NaiveBayes is',metrics.accuracy_score(prediction6,test_Y))

随机森林

model=RandomForestClassifier(n_estimators=100)
model.fit(train_X,train_Y)
prediction7=model.predict(test_X)
print('The accuracy of the Random Forests is',metrics.accuracy_score(prediction7,test_Y))

但是呢,机器学习所建立的模型准确度是受很多因素影响的,并且模型的精确性也不是衡量一个算法的鲁棒性的唯一指标,并且对于不同的测试集,它的偏向性也不同,测试的数据改变,它的性能也会发生改变,这就是模型变量(model variance)
我们使用交叉验证(Cross Validation)来克服这一缺陷

K叠交叉验证法

这个算法是这样的:把所有数据集分为K个子集,比方说5个子集,我们保留一个子集作为测试集,然后训练其他4个子集,然后我们每次都改变这个测试集,对训练集进行训练,例如,第一次将A集作为测试集,BCDE集用来训练,第二次将B作为测试集,ACDE用来训练,依次循环。

from sklearn.model_selection import KFold #for K-fold cross validation
from sklearn.model_selection import cross_val_score #score evaluation
from sklearn.model_selection import cross_val_predict #prediction
kfold = KFold(n_splits=10, random_state=22) # k=10, split the data into 10 equal parts
xyz=[]
accuracy=[]
std=[]
classifiers=['Linear Svm','Radial Svm','Logistic Regression','KNN','Decision Tree','Naive Bayes','Random Forest']
models=[svm.SVC(kernel='linear'),svm.SVC(kernel='rbf'),LogisticRegression(),KNeighborsClassifier(n_neighbors=9),DecisionTreeClassifier(),GaussianNB(),RandomForestClassifier(n_estimators=100)]
for i in models:
    model = i
    cv_result = cross_val_score(model,X,Y, cv = kfold,scoring = "accuracy")
    cv_result=cv_result
    xyz.append(cv_result.mean())
    std.append(cv_result.std())
    accuracy.append(cv_result)
new_models_dataframe2=pd.DataFrame({'CV Mean':xyz,'Std':std},index=classifiers)       
new_models_dataframe2

plt.subplots(figsize=(12,6))
box=pd.DataFrame(accuracy,index=[classifiers])
box.T.boxplot()


new_models_dataframe2['CV Mean'].plot.barh(width=0.8)
plt.title('Average CV Mean Accuracy')
fig=plt.gcf()
fig.set_size_inches(8,5)
plt.show()

混淆矩阵

再介绍一个方法,混淆矩阵可以给出预测正确和预测错误的数量。

f,ax=plt.subplots(3,3,figsize=(12,10))
y_pred = cross_val_predict(svm.SVC(kernel='rbf'),X,Y,cv=10)
sns.heatmap(confusion_matrix(Y,y_pred),ax=ax[0,0],annot=True,fmt='2.0f')
ax[0,0].set_title('Matrix for rbf-SVM')
y_pred = cross_val_predict(svm.SVC(kernel='linear'),X,Y,cv=10)
sns.heatmap(confusion_matrix(Y,y_pred),ax=ax[0,1],annot=True,fmt='2.0f')
ax[0,1].set_title('Matrix for Linear-SVM')
y_pred = cross_val_predict(KNeighborsClassifier(n_neighbors=9),X,Y,cv=10)
sns.heatmap(confusion_matrix(Y,y_pred),ax=ax[0,2],annot=True,fmt='2.0f')
ax[0,2].set_title('Matrix for KNN')
y_pred = cross_val_predict(RandomForestClassifier(n_estimators=100),X,Y,cv=10)
sns.heatmap(confusion_matrix(Y,y_pred),ax=ax[1,0],annot=True,fmt='2.0f')
ax[1,0].set_title('Matrix for Random-Forests')
y_pred = cross_val_predict(LogisticRegression(),X,Y,cv=10)
sns.heatmap(confusion_matrix(Y,y_pred),ax=ax[1,1],annot=True,fmt='2.0f')
ax[1,1].set_title('Matrix for Logistic Regression')
y_pred = cross_val_predict(DecisionTreeClassifier(),X,Y,cv=10)
sns.heatmap(confusion_matrix(Y,y_pred),ax=ax[1,2],annot=True,fmt='2.0f')
ax[1,2].set_title('Matrix for Decision Tree')
y_pred = cross_val_predict(GaussianNB(),X,Y,cv=10)
sns.heatmap(confusion_matrix(Y,y_pred),ax=ax[2,0],annot=True,fmt='2.0f')
ax[2,0].set_title('Matrix for Naive Bayes')
plt.subplots_adjust(hspace=0.2,wspace=0.2)
plt.show()

结果如下:

模型调参

机器学习就像一个黑盒子,放一堆数据进去,哗的一下一个模型出来,但是我们可以对这个黑盒子的一些参数进行调整,来得到更好的模型,在这里我们对两个模型进行调参:支持向量机和随机森林

支持向量机

from sklearn.model_selection import GridSearchCV
C=[0.05,0.1,0.2,0.3,0.25,0.4,0.5,0.6,0.7,0.8,0.9,1]
gamma=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]
kernel=['rbf','linear']
hyper={'kernel':kernel,'C':C,'gamma':gamma}
gd=GridSearchCV(estimator=svm.SVC(),param_grid=hyper,verbose=True)
gd.fit(X,Y)
print(gd.best_score_)
print(gd.best_estimator_)


我们对支持向量机的C值和gamma值分别不断进行尝试,在其中获得效果最好的一组参数,至于这两个参数是什么意义,怎么影响模型的效率,这里就不赘述了,感兴趣的童鞋请自行研究 :-)

随机森林

n_estimators=range(100,1000,100)
hyper={'n_estimators':n_estimators}
gd=GridSearchCV(estimator=RandomForestClassifier(random_state=0),param_grid=hyper,verbose=True)
gd.fit(X,Y)
print(gd.best_score_)
print(gd.best_estimator_)

结束语

这个教程到这里就结束了,优秀的你能够克服大段文字看到这里,说明你很棒,怀揣着对数据挖掘的兴趣继续走下去吧,你一定是以后最叱咤风云的人才。
这个文章其实可以说是一篇翻译,因为思路和代码是用的 i,Coder 的notebook,但是我也花了很多时间亲自去实践,并将思路整理了一下,使得更加容易理解,其实我原本只是想在做完这个项目写一个笔记的,但是如果发布到博客上对其他人来说理解很难,就好像我之前的数据库笔记一样,现在我再回过头去看,感觉可读性一点也不高,如果仅仅是作为一个笔记,自己写成markdown什么时候用就找找就好了,我想当做博客发出来,就应该把它写的通俗易懂,可看性强,希望大家看完能学到知识。
在这里特别感谢我的朋友兼带我入坑的机器学习大佬@张森 ,感谢森哥的支持和鼓励

猜你喜欢

转载自blog.csdn.net/neverever01/article/details/78877256