艳鬼之结衣人间,cf西西辅助,黑心佛
原文链接:
上篇文章机器学习实战教程(二):决策树基础篇之让我们从相亲说起讲述了机器学习决策树的原理,以及如何选择最优特征作为分类特征。本篇文章将在此基础上进行介绍。主要包括:
上篇文章也粗略提到过,构建决策树的算法有很多。篇幅原因,本篇文章只使用id3算法构建决策树。
id3算法的核心是在决策树各个结点上对应信息增益准则选择特征,递归地构建决策树。具体方法是:从根结点(root node)开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子节点;再对子结点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止。最后得到一个决策树。id3相当于用极大似然法进行概率模型的选择。
在使用id3构造决策树之前,我们再分析下数据。
利用上篇文章求得的结果,由于特征a3(有自己的房子)的信息增益值最大,所以选择特征a3作为根结点的特征。它将训练集d划分为两个子集d1(a3取值为"是")和d2(a3取值为"否")。由于d1只有同一类的样本点,所以它成为一个叶结点,结点的类标记为“是”。
对d2则需要从特征a1(年龄),a2(有工作)和a4(信贷情况)中选择新的特征,计算各个特征的信息增益:
根据计算,选择信息增益最大的特征a2(有工作)作为结点的特征。由于a2有两个可能取值,从这一结点引出两个子结点:一个对应"是"(有工作)的子结点,包含3个样本,它们属于同一类,所以这是一个叶结点,类标记为"是";另一个是对应"否"(无工作)的子结点,包含6个样本,它们也属于同一类,所以这也是一个叶结点,类标记为"否"。
这样就生成了一个决策树,该决策树只用了两个特征(有两个内部结点),生成的决策树如下图所示。
这样我们就使用id3算法构建出来了决策树,接下来,让我们看看如何进行代实现。
我们使用字典存储决策树的结构,比如上小节我们分析出来的决策树,用字典可以表示为:
{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
创建函数majoritycnt统计classlist中出现此处最多的元素(类标签),创建函数createtree用来递归构建决策树。编写代码如下:
# -*- coding: utf-8 -*- from math import log import operator """ 函数说明:计算给定数据集的经验熵(香农熵) parameters: dataset - 数据集 returns: shannonent - 经验熵(香农熵) author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ def calcshannonent(dataset): numentires = len(dataset) #返回数据集的行数 labelcounts = {} #保存每个标签(label)出现次数的字典 for featvec in dataset: #对每组特征向量进行统计 currentlabel = featvec[-1] #提取标签(label)信息 if currentlabel not in labelcounts.keys(): #如果标签(label)没有放入统计次数的字典,添加进去 labelcounts[currentlabel] = 0 labelcounts[currentlabel] += 1 #label计数 shannonent = 0.0 #经验熵(香农熵) for key in labelcounts: #计算香农熵 prob = float(labelcounts[key]) / numentires #选择该标签(label)的概率 shannonent -= prob * log(prob, 2) #利用公式计算 return shannonent #返回经验熵(香农熵) """ 函数说明:创建测试数据集 parameters: 无 returns: dataset - 数据集 labels - 特征标签 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-20 """ def createdataset(): dataset = [[0, 0, 0, 0, 'no'], #数据集 [0, 0, 0, 1, 'no'], [0, 1, 0, 1, 'yes'], [0, 1, 1, 0, 'yes'], [0, 0, 0, 0, 'no'], [1, 0, 0, 0, 'no'], [1, 0, 0, 1, 'no'], [1, 1, 1, 1, 'yes'], [1, 0, 1, 2, 'yes'], [1, 0, 1, 2, 'yes'], [2, 0, 1, 2, 'yes'], [2, 0, 1, 1, 'yes'], [2, 1, 0, 1, 'yes'], [2, 1, 0, 2, 'yes'], [2, 0, 0, 0, 'no']] labels = ['年龄', '有工作', '有自己的房子', '信贷情况'] #特征标签 return dataset, labels #返回数据集和分类属性 """ 函数说明:按照给定特征划分数据集 parameters: dataset - 待划分的数据集 axis - 划分数据集的特征 value - 需要返回的特征的值 returns: 无 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ def splitdataset(dataset, axis, value): retdataset = [] #创建返回的数据集列表 for featvec in dataset: #遍历数据集 if featvec[axis] == value: reducedfeatvec = featvec[:axis] #去掉axis特征 reducedfeatvec.extend(featvec[axis+1:]) #将符合条件的添加到返回的数据集 retdataset.append(reducedfeatvec) return retdataset #返回划分后的数据集 """ 函数说明:选择最优特征 parameters: dataset - 数据集 returns: bestfeature - 信息增益最大的(最优)特征的索引值 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-20 """ def choosebestfeaturetosplit(dataset): numfeatures = len(dataset[0]) - 1 #特征数量 baseentropy = calcshannonent(dataset) #计算数据集的香农熵 bestinfogain = 0.0 #信息增益 bestfeature = -1 #最优特征的索引值 for i in range(numfeatures): #遍历所有特征 #获取dataset的第i个所有特征 featlist = [example[i] for example in dataset] uniquevals = set(featlist) #创建set集合{},元素不可重复 newentropy = 0.0 #经验条件熵 for value in uniquevals: #计算信息增益 subdataset = splitdataset(dataset, i, value) #subdataset划分后的子集 prob = len(subdataset) / float(len(dataset)) #计算子集的概率 newentropy += prob * calcshannonent(subdataset) #根据公式计算经验条件熵 infogain = baseentropy - newentropy #信息增益 # print("第%d个特征的增益为%.3f" % (i, infogain)) #打印每个特征的信息增益 if (infogain > bestinfogain): #计算信息增益 bestinfogain = infogain #更新信息增益,找到最大的信息增益 bestfeature = i #记录信息增益最大的特征的索引值 return bestfeature #返回信息增益最大的特征的索引值 """ 函数说明:统计classlist中出现此处最多的元素(类标签) parameters: classlist - 类标签列表 returns: sortedclasscount[0][0] - 出现此处最多的元素(类标签) author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ def majoritycnt(classlist): classcount = {} for vote in classlist: #统计classlist中每个元素出现的次数 if vote not in classcount.keys():classcount[vote] = 0 classcount[vote] += 1 sortedclasscount = sorted(classcount.items(), key = operator.itemgetter(1), reverse = true) #根据字典的值降序排序 return sortedclasscount[0][0] #返回classlist中出现次数最多的元素 """ 函数说明:创建决策树 parameters: dataset - 训练数据集 labels - 分类属性标签 featlabels - 存储选择的最优特征标签 returns: mytree - 决策树 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-25 """ def createtree(dataset, labels, featlabels): classlist = [example[-1] for example in dataset] #取分类标签(是否放贷:yes or no) if classlist.count(classlist[0]) == len(classlist): #如果类别完全相同则停止继续划分 return classlist[0] if len(dataset[0]) == 1 or len(labels) == 0: #遍历完所有特征时返回出现次数最多的类标签 return majoritycnt(classlist) bestfeat = choosebestfeaturetosplit(dataset) #选择最优特征 bestfeatlabel = labels[bestfeat] #最优特征的标签 featlabels.append(bestfeatlabel) mytree = {bestfeatlabel:{}} #根据最优特征的标签生成树 del(labels[bestfeat]) #删除已经使用特征标签 featvalues = [example[bestfeat] for example in dataset] #得到训练集中所有最优特征的属性值 uniquevals = set(featvalues) #去掉重复的属性值 for value in uniquevals: #遍历特征,创建决策树。 mytree[bestfeatlabel][value] = createtree(splitdataset(dataset, bestfeat, value), labels, featlabels) return mytree if __name__ == '__main__': dataset, labels = createdataset() featlabels = [] mytree = createtree(dataset, labels, featlabels) print(mytree)
递归创建决策树时,递归有两个终止条件:第一个停止条件是所有的类标签完全相同,则直接返回该类标签;第二个停止条件是使用完了所有特征,仍然不能将数据划分仅包含唯一类别的分组,即决策树构建失败,特征不够用。此时说明数据纬度不够,由于第二个停止条件无法简单地返回唯一的类标签,这里挑选出现数量最多的类别作为返回值。
运行上述代码,我们可以看到如下结果:
可见,我们的决策树已经构建完成了。这时候,有的朋友可能会说,这个决策树看着好别扭,虽然这个能看懂,但是如果多点的结点,就不好看了。能直观点吗?完全没有问题,我们可以使用强大的matplotlib绘制决策树。
这里代码都是关于matplotlib的,如果对于matplotlib不了解的,可以先学习下,matplotlib的内容这里就不再累述。可视化需要用到的函数:
我对可视化决策树的程序进行了详细的注释,直接看代码,调试查看即可。为了显示中文,需要设置fontproperties,代码编写如下:
# -*- coding: utf-8 -*- from matplotlib.font_manager import fontproperties import matplotlib.pyplot as plt from math import log import operator """ 函数说明:计算给定数据集的经验熵(香农熵) parameters: dataset - 数据集 returns: shannonent - 经验熵(香农熵) author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ def calcshannonent(dataset): numentires = len(dataset) #返回数据集的行数 labelcounts = {} #保存每个标签(label)出现次数的字典 for featvec in dataset: #对每组特征向量进行统计 currentlabel = featvec[-1] #提取标签(label)信息 if currentlabel not in labelcounts.keys(): #如果标签(label)没有放入统计次数的字典,添加进去 labelcounts[currentlabel] = 0 labelcounts[currentlabel] += 1 #label计数 shannonent = 0.0 #经验熵(香农熵) for key in labelcounts: #计算香农熵 prob = float(labelcounts[key]) / numentires #选择该标签(label)的概率 shannonent -= prob * log(prob, 2) #利用公式计算 return shannonent #返回经验熵(香农熵) """ 函数说明:创建测试数据集 parameters: 无 returns: dataset - 数据集 labels - 特征标签 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-20 """ def createdataset(): dataset = [[0, 0, 0, 0, 'no'], #数据集 [0, 0, 0, 1, 'no'], [0, 1, 0, 1, 'yes'], [0, 1, 1, 0, 'yes'], [0, 0, 0, 0, 'no'], [1, 0, 0, 0, 'no'], [1, 0, 0, 1, 'no'], [1, 1, 1, 1, 'yes'], [1, 0, 1, 2, 'yes'], [1, 0, 1, 2, 'yes'], [2, 0, 1, 2, 'yes'], [2, 0, 1, 1, 'yes'], [2, 1, 0, 1, 'yes'], [2, 1, 0, 2, 'yes'], [2, 0, 0, 0, 'no']] labels = ['年龄', '有工作', '有自己的房子', '信贷情况'] #特征标签 return dataset, labels #返回数据集和分类属性 """ 函数说明:按照给定特征划分数据集 parameters: dataset - 待划分的数据集 axis - 划分数据集的特征 value - 需要返回的特征的值 returns: 无 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ def splitdataset(dataset, axis, value): retdataset = [] #创建返回的数据集列表 for featvec in dataset: #遍历数据集 if featvec[axis] == value: reducedfeatvec = featvec[:axis] #去掉axis特征 reducedfeatvec.extend(featvec[axis+1:]) #将符合条件的添加到返回的数据集 retdataset.append(reducedfeatvec) return retdataset #返回划分后的数据集 """ 函数说明:选择最优特征 parameters: dataset - 数据集 returns: bestfeature - 信息增益最大的(最优)特征的索引值 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-20 """ def choosebestfeaturetosplit(dataset): numfeatures = len(dataset[0]) - 1 #特征数量 baseentropy = calcshannonent(dataset) #计算数据集的香农熵 bestinfogain = 0.0 #信息增益 bestfeature = -1 #最优特征的索引值 for i in range(numfeatures): #遍历所有特征 #获取dataset的第i个所有特征 featlist = [example[i] for example in dataset] uniquevals = set(featlist) #创建set集合{},元素不可重复 newentropy = 0.0 #经验条件熵 for value in uniquevals: #计算信息增益 subdataset = splitdataset(dataset, i, value) #subdataset划分后的子集 prob = len(subdataset) / float(len(dataset)) #计算子集的概率 newentropy += prob * calcshannonent(subdataset) #根据公式计算经验条件熵 infogain = baseentropy - newentropy #信息增益 # print("第%d个特征的增益为%.3f" % (i, infogain)) #打印每个特征的信息增益 if (infogain > bestinfogain): #计算信息增益 bestinfogain = infogain #更新信息增益,找到最大的信息增益 bestfeature = i #记录信息增益最大的特征的索引值 return bestfeature #返回信息增益最大的特征的索引值 """ 函数说明:统计classlist中出现此处最多的元素(类标签) parameters: classlist - 类标签列表 returns: sortedclasscount[0][0] - 出现此处最多的元素(类标签) author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ def majoritycnt(classlist): classcount = {} for vote in classlist: #统计classlist中每个元素出现的次数 if vote not in classcount.keys():classcount[vote] = 0 classcount[vote] += 1 sortedclasscount = sorted(classcount.items(), key = operator.itemgetter(1), reverse = true) #根据字典的值降序排序 return sortedclasscount[0][0] #返回classlist中出现次数最多的元素 """ 函数说明:创建决策树 parameters: dataset - 训练数据集 labels - 分类属性标签 featlabels - 存储选择的最优特征标签 returns: mytree - 决策树 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-25 """ def createtree(dataset, labels, featlabels): classlist = [example[-1] for example in dataset] #取分类标签(是否放贷:yes or no) if classlist.count(classlist[0]) == len(classlist): #如果类别完全相同则停止继续划分 return classlist[0] if len(dataset[0]) == 1 or len(labels) == 0: #遍历完所有特征时返回出现次数最多的类标签 return majoritycnt(classlist) bestfeat = choosebestfeaturetosplit(dataset) #选择最优特征 bestfeatlabel = labels[bestfeat] #最优特征的标签 featlabels.append(bestfeatlabel) mytree = {bestfeatlabel:{}} #根据最优特征的标签生成树 del(labels[bestfeat]) #删除已经使用特征标签 featvalues = [example[bestfeat] for example in dataset] #得到训练集中所有最优特征的属性值 uniquevals = set(featvalues) #去掉重复的属性值 for value in uniquevals: #遍历特征,创建决策树。 mytree[bestfeatlabel][value] = createtree(splitdataset(dataset, bestfeat, value), labels, featlabels) return mytree """ 函数说明:获取决策树叶子结点的数目 parameters: mytree - 决策树 returns: numleafs - 决策树的叶子结点的数目 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ 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 - 决策树的层数 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ 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: 无 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ 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: 无 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ 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: 无 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ 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 """ 函数说明:创建绘制面板 parameters: intree - 决策树(字典) returns: 无 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ 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() #显示绘制结果 if __name__ == '__main__': dataset, labels = createdataset() featlabels = [] mytree = createtree(dataset, labels, featlabels) print(mytree) createplot(mytree)
不出意外的话,我们就可以得到如下结果,可以看到决策树绘制完成。plotnode函数的工作就是绘制各个结点,比如有自己的房子
、有工作
、yes
、no
,包括内结点和叶子结点。plotmidtext函数的工作就是绘制各个有向边的属性,例如各个有向边的0
和1
。这部分内容呢,个人感觉可以选择性掌握,能掌握最好,不能掌握可以放一放,因为后面会介绍一个更简单的决策树可视化方法。看到这句话,是不是想偷懒不仔细看这部分的代码了?(눈_눈)
依靠训练数据构造了决策树之后,我们可以将它用于实际数据的分类。在执行数据分类时,需要决策树以及用于构造树的标签向量。然后,程序比较测试数据与决策树上的数值,递归执行该过程直到进入叶子结点;最后将测试数据定义为叶子结点所属的类型。在构建决策树的代码,可以看到,有个featlabels参数。它是用来干什么的?它就是用来记录各个分类结点的,在用决策树做预测的时候,我们按顺序输入需要的分类结点的属性值即可。举个例子,比如我用上述已经训练好的决策树做分类,那么我只需要提供这个人是否有房子,是否有工作这两个信息即可,无需提供冗余的信息。
用决策树做分类的代码很简单,编写代码如下:
# -*- coding: utf-8 -*- from math import log import operator """ 函数说明:计算给定数据集的经验熵(香农熵) parameters: dataset - 数据集 returns: shannonent - 经验熵(香农熵) author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ def calcshannonent(dataset): numentires = len(dataset) #返回数据集的行数 labelcounts = {} #保存每个标签(label)出现次数的字典 for featvec in dataset: #对每组特征向量进行统计 currentlabel = featvec[-1] #提取标签(label)信息 if currentlabel not in labelcounts.keys(): #如果标签(label)没有放入统计次数的字典,添加进去 labelcounts[currentlabel] = 0 labelcounts[currentlabel] += 1 #label计数 shannonent = 0.0 #经验熵(香农熵) for key in labelcounts: #计算香农熵 prob = float(labelcounts[key]) / numentires #选择该标签(label)的概率 shannonent -= prob * log(prob, 2) #利用公式计算 return shannonent #返回经验熵(香农熵) """ 函数说明:创建测试数据集 parameters: 无 returns: dataset - 数据集 labels - 特征标签 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-20 """ def createdataset(): dataset = [[0, 0, 0, 0, 'no'], #数据集 [0, 0, 0, 1, 'no'], [0, 1, 0, 1, 'yes'], [0, 1, 1, 0, 'yes'], [0, 0, 0, 0, 'no'], [1, 0, 0, 0, 'no'], [1, 0, 0, 1, 'no'], [1, 1, 1, 1, 'yes'], [1, 0, 1, 2, 'yes'], [1, 0, 1, 2, 'yes'], [2, 0, 1, 2, 'yes'], [2, 0, 1, 1, 'yes'], [2, 1, 0, 1, 'yes'], [2, 1, 0, 2, 'yes'], [2, 0, 0, 0, 'no']] labels = ['年龄', '有工作', '有自己的房子', '信贷情况'] #特征标签 return dataset, labels #返回数据集和分类属性 """ 函数说明:按照给定特征划分数据集 parameters: dataset - 待划分的数据集 axis - 划分数据集的特征 value - 需要返回的特征的值 returns: 无 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ def splitdataset(dataset, axis, value): retdataset = [] #创建返回的数据集列表 for featvec in dataset: #遍历数据集 if featvec[axis] == value: reducedfeatvec = featvec[:axis] #去掉axis特征 reducedfeatvec.extend(featvec[axis+1:]) #将符合条件的添加到返回的数据集 retdataset.append(reducedfeatvec) return retdataset #返回划分后的数据集 """ 函数说明:选择最优特征 parameters: dataset - 数据集 returns: bestfeature - 信息增益最大的(最优)特征的索引值 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-20 """ def choosebestfeaturetosplit(dataset): numfeatures = len(dataset[0]) - 1 #特征数量 baseentropy = calcshannonent(dataset) #计算数据集的香农熵 bestinfogain = 0.0 #信息增益 bestfeature = -1 #最优特征的索引值 for i in range(numfeatures): #遍历所有特征 #获取dataset的第i个所有特征 featlist = [example[i] for example in dataset] uniquevals = set(featlist) #创建set集合{},元素不可重复 newentropy = 0.0 #经验条件熵 for value in uniquevals: #计算信息增益 subdataset = splitdataset(dataset, i, value) #subdataset划分后的子集 prob = len(subdataset) / float(len(dataset)) #计算子集的概率 newentropy += prob * calcshannonent(subdataset) #根据公式计算经验条件熵 infogain = baseentropy - newentropy #信息增益 # print("第%d个特征的增益为%.3f" % (i, infogain)) #打印每个特征的信息增益 if (infogain > bestinfogain): #计算信息增益 bestinfogain = infogain #更新信息增益,找到最大的信息增益 bestfeature = i #记录信息增益最大的特征的索引值 return bestfeature #返回信息增益最大的特征的索引值 """ 函数说明:统计classlist中出现此处最多的元素(类标签) parameters: classlist - 类标签列表 returns: sortedclasscount[0][0] - 出现此处最多的元素(类标签) author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-24 """ def majoritycnt(classlist): classcount = {} for vote in classlist: #统计classlist中每个元素出现的次数 if vote not in classcount.keys():classcount[vote] = 0 classcount[vote] += 1 sortedclasscount = sorted(classcount.items(), key = operator.itemgetter(1), reverse = true) #根据字典的值降序排序 return sortedclasscount[0][0] #返回classlist中出现次数最多的元素 """ 函数说明:创建决策树 parameters: dataset - 训练数据集 labels - 分类属性标签 featlabels - 存储选择的最优特征标签 returns: mytree - 决策树 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-25 """ def createtree(dataset, labels, featlabels): classlist = [example[-1] for example in dataset] #取分类标签(是否放贷:yes or no) if classlist.count(classlist[0]) == len(classlist): #如果类别完全相同则停止继续划分 return classlist[0] if len(dataset[0]) == 1 or len(labels) == 0: #遍历完所有特征时返回出现次数最多的类标签 return majoritycnt(classlist) bestfeat = choosebestfeaturetosplit(dataset) #选择最优特征 bestfeatlabel = labels[bestfeat] #最优特征的标签 featlabels.append(bestfeatlabel) mytree = {bestfeatlabel:{}} #根据最优特征的标签生成树 del(labels[bestfeat]) #删除已经使用特征标签 featvalues = [example[bestfeat] for example in dataset] #得到训练集中所有最优特征的属性值 uniquevals = set(featvalues) #去掉重复的属性值 for value in uniquevals: #遍历特征,创建决策树。 mytree[bestfeatlabel][value] = createtree(splitdataset(dataset, bestfeat, value), labels, featlabels) return mytree """ 函数说明:使用决策树分类 parameters: inputtree - 已经生成的决策树 featlabels - 存储选择的最优特征标签 testvec - 测试数据列表,顺序对应最优特征标签 returns: classlabel - 分类结果 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-25 """ 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 if __name__ == '__main__': dataset, labels = createdataset() featlabels = [] mytree = createtree(dataset, labels, featlabels) testvec = [0,1] #测试数据 result = classify(mytree, featlabels, testvec) if result == 'yes': print('放贷') if result == 'no': print('不放贷')
这里只增加了classify函数,用于决策树分类。输入测试数据[0,1],它代表没有房子,但是有工作,分类结果如下所示:
看到这里,细心的朋友可能就会问了,每次做预测都要训练一次决策树?这也太麻烦了吧?有什么好的解决吗?
构造决策树是很耗时的任务,即使处理很小的数据集,如前面的样本数据,也要花费几秒的时间,如果数据集很大,将会耗费很多计算时间。然而用创建好的决策树解决分类问题,则可以很快完成。因此,为了节省计算时间,最好能够在每次执行分类时调用已经构造好的决策树。为了解决这个问题,需要使用python模块pickle序列化对象。序列化对象可以在磁盘上保存对象,并在需要的时候读取出来。
假设我们已经得到决策树{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}},使用pickle.dump存储决策树。
# -*- coding: utf-8 -*- import pickle """ 函数说明:存储决策树 parameters: inputtree - 已经生成的决策树 filename - 决策树的存储文件名 returns: 无 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-25 """ def storetree(inputtree, filename): with open(filename, 'wb') as fw: pickle.dump(inputtree, fw) if __name__ == '__main__': mytree = {'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}} storetree(mytree, 'classifierstorage.txt')
运行代码,在该python文件的相同目录下,会生成一个名为classifierstorage.txt的txt文件,这个文件二进制存储着我们的决策树。我们可以使用sublime txt打开看下存储结果。
看不懂?没错,因为这个是个二进制存储的文件,我们也无需看懂里面的内容,会存储,会用即可。那么问题来了。将决策树存储完这个二进制文件,然后下次使用的话,怎么用呢?
很简单使用pickle.load进行载入即可,编写代码如下:
# -*- coding: utf-8 -*- import pickle """ 函数说明:读取决策树 parameters: filename - 决策树的存储文件名 returns: pickle.load(fr) - 决策树字典 author: jack cui blog: http://blog.csdn.net/c406495762 modify: 2017-07-25 """ def grabtree(filename): fr = open(filename, 'rb') return pickle.load(fr) if __name__ == '__main__': mytree = grabtree('classifierstorage.txt') print(mytree)
如果在该python文件的相同目录下,有一个名为classifierstorage.txt的文件,那么我们就可以运行上述代码,运行结果如下图所示:
从上述结果中,我们可以看到,我们顺利加载了存储决策树的二进制文件。
进入本文的正题:眼科医生是如何判断患者需要佩戴隐形眼镜的类型的?一旦理解了决策树的工作原理,我们甚至也可以帮助人们判断需要佩戴的镜片类型。
隐形眼镜数据集是非常著名的数据集,它包含很多换着眼部状态的观察条件以及医生推荐的隐形眼镜类型。隐形眼镜类型包括硬材质(hard)、软材质(soft)以及不适合佩戴隐形眼镜(no lenses)。数据来源与uci数据库,数据集下载地址:https://github.com/jack-cherish/machine-learning/blob/master/decision%20tree/classifierstorage.txt
一共有24组数据,数据的labels依次是age、prescript、astigmatic、tearrate、class,也就是第一列是年龄,第二列是症状,第三列是是否散光,第四列是眼泪数量,第五列是最终的分类标签。数据如下图所示:
可以使用已经写好的python程序构建决策树,不过出于继续学习的目的,本文使用sklearn实现。
官方英文文档地址:
sklearn.tree模块提供了决策树模型,用于解决分类问题和回归问题。方法如下图所示:
本次实战内容使用的是decisiontreeclassifier和export_graphviz,前者用于决策树构建,后者用于决策树可视化。
decisiontreeclassifier构建决策树:
让我们先看下decisiontreeclassifier这个函数,一共有12个参数:
参数说明如下:
除了这些参数要注意以外,其他在调参时的注意点有:
sklearn.tree.decisiontreeclassifier()提供了一些方法供我们使用,如下图所示:
了解到这些,我们就可以编写代码了。
# -*- coding: utf-8 -*- from sklearn import tree if __name__ == '__main__': fr = open('lenses.txt') 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)
运行代码,会得到如下结果:
我们可以看到程序报错了,这是为什么?因为在fit()函数不能接收string类型的数据,通过打印的信息可以看到,数据都是string类型的。在使用fit()函数之前,我们需要对数据集进行编码,这里可以使用两种方法:
为了对string类型的数据序列化,需要先生成pandas数据,这样方便我们的序列化工作。这里我使用的方法是,原始数据->字典->pandas数据,编写代码如下:
# -*- coding: utf-8 -*- 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]) 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)
从运行结果可以看出,顺利生成pandas数据。
接下来,将数据序列化,编写代码如下:
# -*- coding: utf-8 -*- import pandas as pd from sklearn.preprocessing import labelencoder import pydotplus from sklearn.externals.six import stringio 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]) 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) #打印pandas.dataframe le = labelencoder() #创建labelencoder()对象,用于序列化 for col in lenses_pd.columns: #为每一列序列化 lenses_pd[col] = le.fit_transform(lenses_pd[col]) print(lenses_pd)
从打印结果可以看到,我们已经将数据顺利序列化,接下来。我们就可以fit()数据,构建决策树了。
graphviz的是at&t labs research开发的图形绘制工具,他可以很方便的用来绘制结构化的图形网络,支持多种格式输出,生成图片的质量和速度都不错。它的输入是一个用dot语言编写的绘图脚本,通过对输入脚本的解析,分析出其中的点,边以及子图,然后根据属性进行绘制。是使用sklearn生成的决策树就是dot格式的,因此我们可以直接利用graphviz将决策树可视化。
在讲解编写代码之前,我们需要安装两样东西,即pydotplus和grphviz。
pydotplus可以在cmd窗口中,直接使用指令安装:
pip3 install pydotplus
graphviz不能使用pip进行安装,我们需要手动安装,下载地址:
下载好安装包,进行安装,安装完毕之后,需要设置graphviz的环境变量。
首先,按快捷键win+r,在出现的运行对话框中输入sysdm.cpl,点击确定,出现如下对话框:
选择高级->环境变量。在系统变量的path变量中,添加graphviz的环境变量,比如graphviz安装在了d盘的根目录,则添加:d:\graphviz\bin;
添加好环境变量之后,我们就可以正常使用graphviz了。
talk is cheap, show me the code.(废话少说,放码过来)。可视化部分的代码不难,都是有套路的,直接填参数就好,详细内容可以查看官方教程:
# -*- coding: utf-8 -*- from sklearn.preprocessing import labelencoder, onehotencoder from sklearn.externals.six import stringio from sklearn import tree import pandas as pd 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) #打印pandas.dataframe 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("tree.pdf")
运行代码,在该python文件保存的相同目录下,会生成一个名为tree的pdf文件,打开文件,我们就可以看到决策树的可视化效果图了。
确定好决策树之后,我们就可以做预测了。可以根据自己的眼睛情况和年龄等特征,看一看自己适合何种材质的隐形眼镜。使用如下代码就可以看到预测结果:
print(clf.predict([[1,1,1,0]])) #预测
代码简单,官方手册都有,就不全贴出来了。
本来是想继续讨论决策树的过拟合问题,但是看到《机器学习实战》将此部分内容放到了第九章,那我也放在后面好了。
决策树的一些优点:
决策树的一些缺点:
其他:
圆方圆学院汇集 python + ai 名师,打造精品的 python + ai 技术课程。 在各大平台都长期有优质免费公开课及录像,欢迎报名收看。
公开课地址:
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
Python爬虫:Request Payload和Form Data的简单区别说明
浅谈Python中threading join和setDaemon用法及区别说明
Python3-异步进程回调函数(callback())介绍
python继承threading.Thread实现有返回值的子类实例
Python中使用threading.Event协调线程的运行详解
网友评论