您的位置:首页 > 其它

[置顶] 《机器学习实战》学习笔记(二)之决策树(下)决策树可视化、存储分类器以及隐形眼镜实例

2017-10-23 17:33 579 查看
转载请注明作者和出处:http://blog.csdn.net/john_bh/

运行平台: Windows

Python版本: Python2.7

IDE: Sublime text3

一 决策树可视化
1 Matplotlib注解

2 构造注解树

二 测试和存储分类器
1 测试算法使用决策树进行分类

2 决策树的存储

三实例使用决策树预测隐形眼镜类型
1 实战背景

2 使用Sklearn构建决策树

21 DecisionTreeClassifier构建决策树

22 代码实现DecisionTreeClassifier构建决策树

3 使用Graphviz可视化决策树
31 Graphviz安装配置

32 代码实现

一、 决策树可视化

1.1 Matplotlib注解

Matplotlib提供了一个非常有用的注解工具annotations,他可以在数据图形上添加文本注解,注解通常用于解释数据的内容,由于数据上面直接存在文本描述非常丑陋,因此工具内嵌支持带箭头的画线工具,使得我们可以在其他恰当的地方指向数据位置,并在此添加描述信息,解释数据内容。

代码实例如下:

# -*- coding: UTF-8 -*-
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

decisionNode=dict(boxstyle="saw
4000
tooth",fc="0.8")
leafNode=dict(boxstyle="round4",fc="0.8")

def plotNode(nodeTxt,centerPt,parentPt,nodeType):
arrow_args=dict(arrowstyle="<-")#定义箭头格式
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)#设置中文字体
#绘制结点
createPlot.ax1.annotate(nodeTxt,xy=parentPt,xycoords='axes fraction',xytext=centerPt,textcoords='axes fraction',va="center",ha="center",bbox=nodeType, arrowprops=arrow_args, FontProperties=font)

def createPlot():
fig=plt.figure(1,facecolor='white')#创建fig
fig.clf()#清空fig
createPlot.ax1=plt.subplot(111, frameon=False)#去掉x、y轴
plotNode('决策节点',(0.5,0.1),(0.1,0.5),decisionNode)
plotNode('叶子节点',(0.8,0.1),(0.3,0.8),leafNode)
plt.show()

if __name__ == '__main__':
createPlot()


输出结果如图1.1 函数plotNode的例子所示:



图1.1 函数plotNode的例子

1.2 构造注解树

用到的函数如下:

getNumLeafs:获取决策树叶子结点的数目

getTreeDepth:获取决策树的层数

plotNode:绘制结点

plotMidText:标注有向边属性值

plotTree:绘制决策树

createPlot:创建绘制面板

具体代码如下:

"""
函数说明:获取决策树叶子结点的数目

Parameters:
myTree - 决策树
Returns:
numLeafs - 决策树的叶子结点的数目
"""
def getNumLeafs(myTree):
numLeafs = 0#初始化叶子
firstStr = next(iter(myTree))  #python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法获取结点属性,可以使用list(myTree.keys())[0]
secondDict = myTree[firstStr]  #获取下一组字典
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':  #测试该结点是否为字典,如果不是字典,代表此结点为叶子结点
numLeafs += getNumLeafs(secondDict[key])
else:   numLeafs +=1
return numLeafs

"""
函数说明:获取决策树的层数

Parameters:
myTree - 决策树
Returns:
maxDepth - 决策树的层数
"""
def getTreeDepth(myTree):
maxDepth = 0#初始化决策树深度
firstStr = next(iter(myTree))   #python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法获取结点属性,可以使用list(myTree.keys())[0]
secondDict = myTree[firstStr]  #获取下一个字典
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':  #测试该结点是否为字典,如果不是字典,代表此结点为叶子结点
thisDepth = 1 + getTreeDepth(secondDict[key])
else:   thisDepth = 1
if thisDepth > maxDepth: maxDepth = thisDepth   #更新层数
return maxDepth

"""
函数说明:绘制结点

Parameters:
nodeTxt - 结点名
centerPt - 文本位置
parentPt - 标注的箭头位置
nodeType - 结点格式
Returns:
无
"""
def plotNode(nodeTxt,centerPt,parentPt,nodeType):
arrow_args=dict(arrowstyle="<-")#定义箭头格式
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)#设置中文字体
#绘制结点
createPlot.ax1.annotate(nodeTxt,xy=parentPt,xycoords='axes fraction',xytext=centerPt,textcoords='axes fraction',va="center",ha="center",bbox=nodeType, arrowprops=arrow_args, FontProperties=font)

"""
函数说明:标注有向边属性值

Parameters:
cntrPt、parentPt - 用于计算标注位置
txtString - 标注的内容
Returns:
无
"""
def plotMidText(cntrPt, parentPt, txtString):
xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]  #计算标注位置
yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)

"""
函数说明:绘制决策树

Parameters:
myTree - 决策树(字典)
parentPt - 标注的内容
nodeTxt - 结点名
Returns:
无
"""
def plotTree(myTree, parentPt, nodeTxt):
decisionNode = dict(boxstyle="sawtooth", fc="0.8")#设置结点格式
leafNode = dict(boxstyle="round4", fc="0.8")#设置叶结点格式
numLeafs = getNumLeafs(myTree)  #获取决策树叶结点数目,决定了树的宽度
depth = getTreeDepth(myTree)#获取决策树层数
firstStr = next(iter(myTree))#下个字典
cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)#中心位置
plotMidText(cntrPt, parentPt, nodeTxt)#标注有向边属性值
plotNode(firstStr, cntrPt, parentPt, decisionNode)#绘制结点
secondDict = myTree[firstStr]#下一个字典,也就是继续绘制子结点
plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD#y偏移
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':#测试该结点是否为字典,如果不是字典,代表此结点为叶子结点
plotTree(secondDict[key],cntrPt,str(key))#不是叶结点,递归调用继续绘制
else:#如果是叶结点,绘制叶结点,并标注有向边属性值
plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD

def createPlot(inTree):
fig=plt.figure(1,facecolor='white')#创建fig
fig.clf()#清空fig
axprops = dict(xticks=[], yticks=[])
createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)  #去掉x、y轴
plotTree.totalW = float(getNumLeafs(inTree))  #获取决策树叶结点数目
plotTree.totalD = float(getTreeDepth(inTree))  #获取决策树层数
plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;  #x偏移
plotTree(inTree, (0.5,1.0), '')  #绘制决策树
plt.show()  #显示绘制结果


运行结果如图1.2 绘制决策树结果 所示:



图1.2 绘制决策树结果

二 、测试和存储分类器

2.1 测试算法:使用决策树进行分类

依靠训练数据构造了决策树之后,我们可以将它用于实际数据的分类。在执行数据分类时,需要决策树以及用于构造树的标签向量。然后,程序比较测试数据与决策树上的数值,递归执行该过程直到进入叶子结点;最后将测试数据定义为叶子结点所属的类型。在构建决策树的代码,可以看到,有个featLabels参数。它是用来干什么的?它就是用来记录各个分类结点的,在用决策树做预测的时候,我们按顺序输入需要的分类结点的属性值即可。举个例子,比如我用上述已经训练好的决策树做分类,那么我只需要提供这个人是否有房子,是否有工作这两个信息即可,无需提供冗余的信息。

在DecisionTree.py文件中添加classify()方法,编写代码如下:

"""
函数说明:使用决策树分类

Parameters:
inputTree - 已经生成的决策树
featLabels - 存储选择的最优特征标签
testVec - 测试数据列表,顺序对应最优特征标签
Returns:
classLabel - 分类结果
"""
def classify(inputTree, featLabels, testVec):
firstStr = next(iter(inputTree))    #获取决策树结点
secondDict = inputTree[firstStr]    #下一个字典
featIndex = featLabels.index(firstStr)
for key in secondDict.keys():
if testVec[featIndex] == key:
if type(secondDict[key]).__name__ == 'dict':
classLabel = classify(secondDict[key], featLabels, testVec)
else: classLabel = secondDict[key]
return classLabel


分类结果如图1.3 分类结果所示:



图1.3 分类结果

2.2 决策树的存储

构造决策树是很耗时的任务,即使处理很小的数据集,如前面的样本数据,也要花费几秒的时间,如果数据集很大,将会耗费很多计算时间。然而用创建好的决策树解决分类问题,则可以很快完成。因此,为了节省计算时间,最好能够在每次执行分类时调用已经构造好的决策树。为了解决这个问题,需要使用Python模块pickle序列化对象。序列化对象可以在磁盘上保存对象,并在需要的时候读取出来。

假设我们已经得到决策树{‘有自己的房子’: {0: {‘有工作’: {0: ‘no’, 1: ‘yes’}}, 1: ‘yes’}},使用pickle.dump存储决策树。

新建storeTree.py文件,在文件中添加方法storeTree()存储决策树和方法grabTree()读取决策树,编写代码如下:

# -*- coding: UTF-8 -*-
import pickle

"""
函数说明:存储决策树

Parameters:
inputTree - 已经生成的决策树
filename - 决策树的存储文件名
Returns:
无
"""
def storeTree(inputTree, filename):
with open(filename, 'wb') as fw:
pickle.dump(inputTree, fw)

"""
函数说明:读取决策树

Parameters:
10134

filename - 决策树的存储文件名
Returns:
pickle.load(fr) - 决策树字典

"""
def grabTree(filename):
fr = open(filename, 'rb')
return pickle.load(fr)

if __name__ == '__main__':
#myTree = {'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
#storeTree(myTree, 'classifierStorage.txt')
myTree = grabTree('classifierStorage.txt')
print(myTree)


读取结果如图1.4 读取结果所示:



图1.4 读取结果

三、实例:使用决策树预测隐形眼镜类型

3.1 实战背景

眼科医生是如何判断患者需要佩戴隐形眼镜的类型的?一旦理解了决策树的工作原理,我们甚至也可以帮助人们判断需要佩戴的镜片类型。

隐形眼镜数据集是非常著名的数据集,它包含很多换着眼部状态的观察条件以及医生推荐的隐形眼镜类型。隐形眼镜类型包括硬材质(hard)、软材质(soft)以及不适合佩戴隐形眼镜(no lenses)。数据来源与UCI数据库,

数据集下载地址:

一共有24组数据,数据的Labels依次是age、prescript、astigmatic、tearRate、class,也就是第一列是年龄,第二列是症状,第三列是是否散光,第四列是眼泪数量,第五列是最终的分类标签。数据如下图所示:



图1.5 隐形眼镜数据

3.2 使用Sklearn构建决策树

官方英文文档地址



图1.6 sklearn.tree: Decision Trees

本次实战内容使用的是DecisionTreeClassifier和export_graphviz,前者用于决策树构建,后者用于决策树可视化。

3.2.1 DecisionTreeClassifier构建决策树



图1.7 DecisionTreeClassifier

参数说明如下:

criterion:特征选择标准,可选参数,默认是gini,可以设置为entropy。gini是基尼不纯度,是将来自集合的某种结果随机应用于某一数据项的预期误差率,是一种基于统计的思想。entropy是香农熵是一种基于信息论的思想。Sklearn把gini设为默认参数,应该也是做了相应的斟酌的,精度也许更高些?ID3算法使用的是entropy,CART算法使用的则是gini。

splitter:特征划分点选择标准,可选参数,默认是best,可以设置为random。每个结点的选择策略。best参数是根据算法选择最佳的切分特征,例如gini、entropy。random随机的在部分划分点中找局部最优的划分点。默认的”best”适合样本量不大的时候,而如果样本数据量非常大,此时决策树构建推荐”random”。

max_features:划分时考虑的最大特征数,可选参数,默认是None。寻找最佳切分时考虑的最大特征数(n_features为总共的特征数),有如下6种情况: -

如果max_features是整型的数,则考虑max_features个特征;

如果max_features是浮点型的数,则考虑int(max_features * n_features)个特征;

如果max_features设为auto,那么max_features = sqrt(n_features);

如果max_features设为sqrt,那么max_featrues = sqrt(n_features),跟auto一样;

如果max_features设为log2,那么max_features = log2(n_features);

如果max_features设为None,那么max_features = n_features,也就是所有特征都用。

一般来说,如果样本特征数不多,比如小于50,我们用默认的”None”就可以了,如果特征数非常多,我们可以灵活使用刚才描述的其他取值来控制划分时考虑的最大特征数,以控制决策树的生成时间

max_depth:决策树最大深,可选参数,默认是None。这个参数是这是树的层数的。层数的概念就是,比如在贷款的例子中,决策树的层数是2层。如果这个参数设置为None,那么决策树在建立子树的时候不会限制子树的深度。一般来说,数据少或者特征少的时候可以不管这个值。或者如果设置了min_samples_slipt参数,那么直到少于min_smaples_split个样本为止。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间。

min_samples_split:内部节点再划分所需最小样本数,可选参数,默认是2。这个值限制了子树继续划分的条件。如果min_samples_split为整数,那么在切分内部结点的时候,min_samples_split作为最小的样本数,也就是说,如果样本已经少于min_samples_split个样本,则停止继续切分。如果min_samples_split为浮点数,那么min_samples_split就是一个百分比,ceil(min_samples_split * n_samples),数是向上取整的。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。

min_weight_fraction_leaf:叶子节点最小的样本权重和,可选参数,默认是0。这个值限制了叶子节点所有样本权重和的最小值,如果小于这个值,则会和兄弟节点一起被剪枝。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。

max_leaf_nodes:最大叶子节点数,可选参数,默认是None。通过限制最大叶子节点数,可以防止过拟合。如果加了限制,算法会建立在最大叶子节点数内最优的决策树。如果特征不多,可以不考虑这个值,但是如果特征分成多的话,可以加以限制,具体的值可以通过交叉验证得到。

class_weight:类别权重,可选参数,默认是None,也可以字典、字典列表、balanced。指定样本各类别的的权重,主要是为了防止训练集某些类别的样本过多,导致训练的决策树过于偏向这些类别。类别的权重可以通过{class_label:weight}这样的格式给出,这里可以自己指定各个样本的权重,或者用balanced,如果使用balanced,则算法会自己计算权重,样本量少的类别所对应的样本权重会高。当然,如果你的样本类别分布没有明显的偏倚,则可以不管这个参数,选择默认的None。

random_state:可选参数,默认是None。随机数种子。如果是证书,那么random_state会作为随机数生成器的随机数种子。随机数种子,如果没有设置随机数,随机出来的数与当前系统时间有关,每个时刻都是不同的。如果设置了随机数种子,那么相同随机数种子,不同时刻产生的随机数也是相同的。如果是RandomState instance,那么random_state是随机数生成器。如果为None,则随机数生成器使用np.random。

min_impurity_split:节点划分最小不纯度,可选参数,默认是1e-7。这是个阈值,这个值限制了决策树的增长,如果某节点的不纯度(基尼系数,信息增益,均方差,绝对差)小于这个阈值,则该节点不再生成子节点。即为叶子节点。

presort:数据是否预排序,可选参数,默认为False,这个值是布尔值,默认是False不排序。一般来说,如果样本量少或者限制了一个深度很小的决策树,设置为true可以让划分点选择更加快,决策树建立的更加快。如果样本量太大的话,反而没有什么好处。问题是样本量少的时候,我速度本来就不慢。所以这个值一般懒得理它就可以了。

除了这些参数要注意以外,其他在调参时的注意点有:

当样本数量少但是样本特征非常多的时候,决策树很容易过拟合,一般来说,样本数比特征数多一些会比较容易建立健壮的模型

如果样本数量少但是样本特征非常多,在拟合决策树模型前,推荐先做维度规约,比如主成分分析(PCA),特征选择(Losso)或者独立成分分析(ICA)。这样特征的维度会大大减小。再来拟合决策树模型效果会好。

推荐多用决策树的可视化,同时先限制决策树的深度,这样可以先观察下生成的决策树里数据的初步拟合情况,然后再决定是否要增加深度。

在训练模型时,注意观察样本的类别情况(主要指分类树),如果类别分布非常不均匀,就要考虑用class_weight来限制模型过于偏向样本多的类别。

决策树的数组使用的是numpy的float32类型,如果训练数据不是这样的格式,算法会先做copy再运行。

如果输入的样本矩阵是稀疏的,推荐在拟合前调用csc_matrix稀疏化,在预测前调用csr_matrix稀疏化。

sklearn.tree.DecisionTreeClassifier()提供了一些方法供我们使用,如下图所示:



图1.8 sklearn.tree.DecisionTreeClassifier()提供的方法

3.2.2 代码实现DecisionTreeClassifier构建决策树

新建文件DT_Sklearn_lenses.py,代码实现如下,

# -*- coding: UTF-8 -*-
from sklearn import tree

if __name__ == '__main__':
with open('lenses.txt', 'r') as fr:   #加载文件
lenses = [inst.strip().split('\t') for inst in fr.readlines()]        #处理文件
print(lenses)
lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']
clf = tree.DecisionTreeClassifier()
lenses = clf.fit(lenses, lensesLabels)


运行代码报错:



图1.9

程序报错了,这是因为在fit()函数不能接收string类型的数据,通过打印的信息可以看到,数据都是string类型的。在使用fit()函数之前,我们需要对数据集进行编码,这里可以使用两种方法:

LabelEncoder:将字符串转换为增量值

OneHotEncoder:使用One-of-K算法将字符串转换为整数

对string类型的数据序列化,需要先生成pandas数据,这样方便我们的序列化工作。原始数据->字典->pandas数据,编写代码如下:

# -*- coding: UTF-8 -*-
from sklearn import tree
import pandas as pd

if __name__ == '__main__':
with open('lenses.txt', 'r') as fr:   #加载文件
lenses = [inst.strip().split('\t') for inst in fr.readlines()]        #处理文件

lenses_target = []     #提取每组数据的类别,保存在列表里
for each in lenses:
lenses_target.append(each[-1])
print(lenses_target)

lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']            #特征标签
lenses_list = []    #保存lenses数据的临时列表
lenses_dict = {}   #保存lenses数据的字典,用于生成pandas
for each_label in lensesLabels:   #提取信息,生成字典
for each in lenses:
lenses_list.append(each[lensesLabels.index(each_label)])
lenses_dict[each_label] = lenses_list
lenses_list = []
print(lenses_dict)
lenses_pd = pd.DataFrame(lenses_dict)    #生成pandas.DataFrame
print(lenses_pd)


运行结果如下图 2.1 数据集编码运行结果:



图2.1 数据集编码运行结果

将数据序列化,添加代码:

le = LabelEncoder()      #创建LabelEncoder()对象,用于序列化
for col in lenses_pd.columns:      #为每一列序列化
lenses_pd[col] = le.fit_transform(lenses_pd[col])
print(lenses_pd)


运行结果如下图 2.2 数据序列化运行结果:



图2.2 数据序列化运行结果

3.3 使用Graphviz可视化决策树

3.3.1 Graphviz安装配置

具体的安装配置请查看我的另一篇文章:graphviz安装配置

3.3.2 代码实现

官方教程:http://scikit-learn.org/stable/modules/tree.html#tree

# -*- coding: UTF-8 -*-
from sklearn import tree
import pandas as pd
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.externals.six import StringIO
import numpy as np
import pydotplus

if __name__ == '__main__':
with open('lenses.txt', 'r') as fr:   #加载文件
lenses = [inst.strip().split('\t') for inst in fr.readlines()]        #处理文件

lenses_target = []     #提取每组数据的类别,保存在列表里
for each in lenses:
lenses_target.append(each[-1])
#print(lenses_target)

lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']            #特征标签
lenses_list = []    #保存lenses数据的临时列表
lenses_dict = {}   #保存lenses数据的字典,用于生成pandas
for each_label in lensesLabels:   #提取信息,生成字典
for each in lenses:
lenses_list.append(each[lensesLabels.index(each_label)])
lenses_dict[each_label] = lenses_list
lenses_list = []
#print(lenses_dict)    #打印字典信息
lenses_pd = pd.DataFrame(lenses_dict)    #生成pandas.DataFrame
#print(lenses_pd)
le = LabelEncoder()      #创建LabelEncoder()对象,用于序列化
for col in lenses_pd.columns:      #为每一列序列化
lenses_pd[col] = le.fit_transform(lenses_pd[col])
#print(lenses_pd)   #打印编码信息

#可视化
clf = tree.DecisionTreeClassifier(max_depth = 4)   #创建DecisionTreeClassifier()类
clf = clf.fit(lenses_pd.values.tolist(), lenses_target)     #使用数据,构建决策树
dot_data = StringIO()
tree.export_graphviz(clf, out_file = dot_data,    #绘制决策树
feature_names = lenses_pd.keys(),
class_names = clf.classes_,
filled=True, rounded=True,
special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_pdf("lensesTree.pdf")      #保存绘制好的决策树,以PDF的形式存储。


运行代码,在该python文件保存的相同目录下,会生成一个名为lensesTree的PDF文件,打开文件,我们就可以看到决策树的可视化效果图,如下图 2.3决策树的可视化效果图:



图2.3 决策树的可视化效果图

使用决策树预测结果,如下图 2.4 决策树预测结果:



图2.4 决策树预测结果
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: