Python的scikit-learn(简称sklearn)模块可以非常好地支持决策树分类,尽管它使用的是更加复杂的决策树算法,但依然是在上一节讲述的原理基础上的优化算法。本节将使用scikit-learn的决策树分类来识别“蘑菇数据”中的毒蘑菇。
首先介绍本案例使用的数据。原始数据来源于加州大学欧文分校用于机器学习的数据库(UCI数据库),本书使用的是经过必要处理的数据,此数据可从教材资源平台下载。
接下来在命令行界面安装scikit-learn。
pip install -u scikit-learn
安装scikit-learn之前需要确保模块numpy和scipy已经安装,否则可以使用pip install安装。
pip install -u numpy
pip install -u scipy
进入Python之后,使用如下命令调用scikit-learn的决策树方法。
from sklearn import tree
模块scikit-learn的决策树是基于DecisionTreeClassifier对象,它能够解决二分类问题(如蘑菇是否可食用),也可以解决多分类问题。在使用中,对输入训练数据的维度要求如下。
输入X:样本数量×特征属性数量;
输入Y:样本类别标签,与X要一一对应。
下面先用一个简单的例子来熟悉使用方法。对四个点X=[[0,0],[0,1],[1,0],[1,1]]使用决策树进行分类,四个点分别属于两个类别0和1,它们对应的类别标签是Y=[0,0,1,1]。这可以通过如下简单代码实现。
In[1]:from sklearn import tree
X=[[0,0],[0,1],[1,0],[1,1]]
Y=[0,0,1,1]
clf=tree.DecisionTreeClassifier()
clf=clf.fit(X,Y)
clf命令用来构造决策树,通过clf.fit(X,Y)实现了决策树的构建,此时clf已经是能够进行分类的决策树了,可以用它来进行新数据的分类。例如,输出[[0.3,0],[0.8,1],[1.2,1]]的类别,代码如下。
In[2]:test=[[0.3,0],[0.8,1],[1.2,1]]
In[3]:clf.predict(test)
Out[3]:array([0,1,1])
通过上述输出可以看到决策树给出的分类结果是[0.3,0]类别为0;[0.8,1]类别为1;[1.2,1]类别为1。读者可以在平面直角坐标系上画出这些点,看看分类是否合理。
读者还可以尝试下列与决策树相关的函数,看看它们具有什么功能。
In[4]:dir(clf)
Out[4]:
[……
'apply',
'class_weight',
'classes_',
'criterion',
'decision_path',
'feature_importances_',
'fit',
'fit_transform',
'get_params',
'max_depth',
'max_features',
'max_features_;,
'max_leaf_nodes',
'min_impurity_split',
'min_samples_leaf',
'min_samples_split',
'min_weight_fraction_leaf',
'n_classes_',
'n_features_',
'n_outputs_',
'predict',
'predict_log_proba',
'predict_proba',
'presort',
'random_state',
'score',
'set_params',
'splitter',
'transform',
'tree_']
下面开始进行蘑菇分类。本书提供的蘑菇数据如表3-5所示,包含样本编号和特征属性,其中第一行是类别标记及属性名称,第一列为样本编号。
表3-5
该表共包含8 123条数据,特征属性共有22个,这些属性及其对应的取值含义如下所示。
类别标记:
毒蘑菇,poisonous,p
可食用,edible,e
特征属性及取值
1.cap-shape:bell=b,conical=c,convex=x,flat=f,knobbed=k,sunken=s
2.cap-surface:fibrous=f,grooves=g,scaly=y,smooth=s
3.cap-color:brown=n,buff=b,cinnamon=c,gray=g,green=r,pink=p,purple=u,red=e,white=w,yellow=y
4.bruises:bruises=t,no=f
5.odor:almond=a,anise=l,creosote=c,fishy=y,foul=f,musty=m,none=n,pungent=p,spicy=s
6.gill-attachment:attached=a,descending=d,free=f,notched=n
7.gill-spacing:close=c,crowded=w,distant=d
8.gill-size:broad=b,narrow=n
9.gill-color:black=k,brown=n,buff=b,chocolate=h,gray=g,green=r,orange=o,pink=p,purple=u,red=e,white=w,yellow=y
10.stalk-shape:enlarging=e,tapering=t
11.stalk-root:bulbous=b,club=c,cup=u,equal=e,rhizomorphs=z,rooted=r,missing=?
12.stalk-surface-above-ring:fibrous=f,scaly=y,silky=k,smooth=s
13.stalk-surface-below-ring:fibrous=f,scaly=y,silky=k,smooth=s
14.stalk-color-above-ring:brown=n,buff=b,cinnamon=c,gray=g,orange=o,pink=p,red=e,white=w,yellow=y
15.stalk-color-below-ring:brown=n,buff=b,cinnamon=c,gray=g,orange=o,pink=p,red=e,white=w,yellow=y
16.veil-type:partial=p,universal=u
17.veil-color:brown=n,orange=o,white=w,yellow=y
18.ring-number:none=n,one=o,two=t
19.ring-type:cobwebby=c,evanescent=e,flaring=f,large=l,none=n,pendant=p,sheathing=s,zone=z
20.spore-print-color:black=k,brown=n,buff=b,chocolate=h,green=r,orange=o,purple=u,white=w,yellow=y
21.population:abundant=a,clustered=c,numerous=n,scattered=s,several=v,solitary=y
22.habitat:grasses=g,leaves=l,meadows=m,paths=p,urban=u,waste=w,woods=d
首先使用pandas读取数据,这是一个强大的数据处理工具。通过显示数据形状可以看到共有8 124行、24列。
In[5]:import pandas as pd
import numpy as np
In[6]:data=pd.read_excel('mushroom.xlsx',header=0)
In[7]:data.shape
Out[7]:(8124,24)
使用如下命令观察前5行数据。
In[8]:data.head(5)
Out[8]:
样本编号标记属性1 属性2 属性3 属性4 属性5 属性6 属性7 属性8 ...
属性13 属性14 属性15 属性16 属性17 属性18\
0 1.0 p x s n t p f c n ...s w w p w o
1 2.0 e x s y t a f c b ...s w w p w o
2 3.0 e b s w t l f c b ...s w w p w o
3 4.0 p x y w t p f c n ...s w w p w o
4 5.0 e x s g f n f w b ...s w w p w o
属性19 属性20 属性21 属性22
0 p k s u
1 p n n g
2 p n n m
3 p k s u
4 e n a g
[5 rows x 24 columns]
进行数据拆分,获得输入数据X和对应的类别标记Y,这个过程是为了准备训练数据。用以下代码获取类别标记。
In[9]:label=data['标记']
#读取标记列
In[10]:label=np.array(label)
#转化成数组,这是Python最常使用的数据格式
In[11]:label.shape
Out[11]:(8124,)
#获得标记的个数。实际标记是8123个,需要剔除最后一个' '标记
In[12]:label=label[0:-1]
#获得8123个标记
因为用来表示类别的“e”和“p”是英文字母,所以需要转化成1和0以便计算机使用,其中1表示可食用,0表示有毒。使用以下代码完成转化,这是一个循环程序。
In[13]:for i in range(0,8123):
if label[i]=='e':
label[i]=1
else:
label[i]=0
接下来就可以设置Y数据了。
In[14]:Y=label
In[15]:Y=Y.astype(Y)
接下来处理训练样本。
In[16]:data=data.drop(['样本编号','标记'],axis=1)
#训练样本需要将两列去掉(样本编号和标记)
In[17]:data.shape
Out[17]:(8124,22)
#训练样本的行列数量
In[18]:newdata=np.array(data)
In[19]:newdata=newdata[0:-1,:]
#去掉最后一行
In[20]:newdata.shape
Out[20]:(8123,22)
#新的数据
特征属性的值同样是用字符表示的,而Python的决策树需要对数值进行处理。所以接下来使用下面的编码将字符直接转化成ASCII编码的整数。表3-6中的最后一列是通常的字符,前两列分别是这个字符的十进制编码和十六进制编码。
表3-6
使用下面的代码将字符转化成它对应的整数。如g转化为103,O转化为79等。这是一个循环程序,其中的ord( )函数负责转化功能。
In[21]:for i in range(0,8123):
for j in range(0,22):
newdata[i,j]=ord(newdata[i,j])
新的训练数据为
In[22]:X=newdata
接下来就可以构造决策树并用于分类了,输出的是描述所生成的决策树的参数。
In[23]:clf=tree.DecisionTreeClassifier()
In[24]:clf.fit(X,Y)
Out[24]:
DecisionTreeClassifier(class_weight=None,criterion='gini',
max_depth=None,
max_features=None,max_leaf_nodes=None,
min_impurity_split=1e-07,min_samples_leaf=1,
min_samples_split=2,min_weight_fraction_leaf=0.0,
presort=False,random_state=None,splitter='best')
使用决策树用于新数据的分类,可以看到对下列4个新的蘑菇数据进行分类后,输出结果表明其中有3种是可食用的,1种是有毒的。
In[25]:test=np.anay([[…],[…],[…],[…]])
np.anay([[120,115,121,...,110,110,103],
[98,115,119,...,110,110,109],
[120,121,119,...,107,115,117],
[120,115,103,...,110,97,103]])
In[26]:clf.predict(test)
Out[26]:array([1,1,0,1])
这个决策树的准确率如何呢?一种简单的方法是使用训练数据中的X,用决策树获得对应的类别标记,也就是预测分类结果(这里用Y_predict表示),然后把它和实际的类别标记Y进行比较。scikit-learn支持这样的比较,使用的方法是accuracy_score。
In[27]:from sklearn.metrics import accuracy_score
In[28]:Y_predict=clf.predict(X)
#这里使用前面训练好的决策树,输入训练样本的X,给出对应的predict
In[29]:accuracy_score(Y,Y_predict)
#这里利用accuracy_score来比较预测值和真实值。
Out[29]:1.0
可以看到这个决策树在训练数据上的准确率是100%(1.0)。这表明使用上述算法构建的决策树完全捕捉了训练数据的分类信息。这也许是因为训练出的决策树确实具有很好的分类效果,但也有可能并不是真正的分类准确率,而是发生了过拟合现象。
为了更好地评估分类准确率,需要使用与训练数据不同的测试数据。可以抽取全部数据中的一部分作为训练数据,另一部分作为测试数据,使用抽取出的训练数据来构建决策树,然后使用测试数据评估准确率。这可以通过如下方式来实现。
In[30] from sklearn.model_selection import train_test_split
In[31]:X_train,X_test,Y_train,Y_test=train_test_split
(X,Y,test_size=0.2)
#这里将全部样本分成两部分,其中训练样本占80%,测试样本占20%,这
#里采用了随机分割样本的方法,所以读者的输出可能与下面的结果不同
In[32]:newclf=tree.DecisionTreeClassifier()
In[33]:newclf.fit(X_train,Y_train)
#利用训练数据来构建决策树
Out[33]:
DecisionTreeClassifier(class_weight=None,criterion='gini',
max_depth=None,
max_features=None,max_leaf_nodes=None,
min_impurity_split=1e-07,min_samples_leaf=1,
min_samples_split=2,min_weight_fraction_leaf=0.0,
presort=False,random_state=None,splitter='best')
#返回新的决策树的参数
In[34]:Y_predict=newclf.predict(X_test)
#注意,这里是将新训练的决策树用在测试样本的X上,并预测出对应Y
In[35]:accuracy_score(Y_test,Y_predict)
Out[35]:1.0
可以看到在测试集上的准确率仍然是100%,这说明这个分类器确实具有很高的准确率。因为测试数据和训练数据是采用随机划分的方式获得的,读者最后获得的输出结果可能会与此不同。
从上述内容可以看出,决策树实现起来简单并且效果很好。本书采用最简单的形式实现这个算法,scikit-learn的决策树有很多参数可以调整,读者可以尝试调整决策树的参数,观察分类性能会发生什么改变。