2.老是值和标称值混合且不论缺失失数据集。//github.com/Wellat/MLaction

本文对裁定树算法进行简短的下结论与梳理,并对名牌的仲裁树算法ID3(Iterative
Dichotomiser
迭代二分器)进行落实,实现利用Python语言,一句老梗,“人生苦短,我为此Python”,Python确实会省多语言方面的从事,从而得以为咱注意于问题与缓解问题的逻辑。


因不同的多寡,我实现了三个版的ID3算法,复杂度逐步提升:

依照系列文章也《机器上实战》学习笔记,内容整理起书本,网络和和谐的掌握,如产生荒唐欢迎指正。

1.纯标称值无短缺失数据集

源码在Python3.5达标测试都经,代码和数量
–> https://github.com/Wellat/MLaction

2.接连值和标称值混合且不论短缺失数据集


3.连续值和标称值混合,有差失数据集

1、算法概述及落实

先是个算法参考了《机器上实战》的大多数代码,第二、三独算法基于前的贯彻进行模块的充实。

1.1 算法理论

决定树构建的伪代码如下:

公海赌船网址 1

明白,决策树的变化是一个递归过程,在仲裁树基本算法中,有3栽情导致递归返回:(1)当前结点包含的样本全属于同一型,无需划分;(2)属性集为空,或是所有样本在拥有属性上取值相同,无法分割;(3)当前结点包含的样书集合为空,不可知分。


 

分选择

决定树学的主要是第8履,即如何选择最好优划分属性,一般而言,随着划分过程不断拓展,我们意在决策树的分层节点所涵盖的范本尽可能属于同一档次,即结点的“纯度”越来越强。

①ID3算法

ID3算法通过对照选择不同特色下数据集的信增益香农熵来确定最了不起划分特征。

香农熵:

  公海赌船网址 2

from collections import Counter
import operator
import math

def calcEnt(dataSet):
    classCount = Counter(sample[-1] for sample in dataSet)
    prob = [float(v)/sum(classCount.values()) for v in classCount.values()]
    return reduce(operator.add, map(lambda x: -x*math.log(x, 2), prob))

 

纯度差,也称为信息增益,表示为

  公海赌船网址 3

点公式实际上就是当前节点的无纯度减去子节点不纯度的加权平均数,权重由子节点记录数与当前节点记录数的百分比控制。信息增益越怪,则意味着使用属性a来拓展划分所抱的“纯度提升”越怪,效果尤其好。

消息增益准则对可取值数目较多的性有所偏好。

 

②C4.5算法

C4.5决策树生成算法相对于ID3算法的重点改进是使用信息增益率来摘取节点性。它克服了ID3算法存在的供不应求:ID3算法只适用于离散的讲述属性,对于连续数需离散化,而C4.5则离散连续均会处理。

增益率定义为:

  公海赌船网址 4

其中

  公海赌船网址 5

内需小心的是,增益率准则对可取值数目较少的性质有所偏好,因此,C4.5算法并无是直选择增益率最酷之候选划分属性,而是用了一个启发式:先由候选划分属性被搜寻来信息增益高于平均水平的性质,再从中挑选增益率最高的。

 

③CART算法

CART决策树下“基尼指数”来选择分属性,数据集D的纯度可用基尼值来度量:

  公海赌船网址 6

它们体现了从数量集D中肆意抽取两只样本,其项目标记不平等的票房价值。为此Gini(D)越小,则数集D的纯度越强

from  collections import Counter
import operator

def calcGini(dataSet):
    labelCounts = Counter(sample[-1] for sample in dataSet)
    prob = [float(v)/sum(labelCounts.values()) for v in labelCounts.values()]
    return 1 - reduce(operator.add, map(lambda x: x**2, prob))

 

剪枝处理

呢避了拟合,需要对生成树剪枝。决策树剪枝的骨干方针有“预剪枝”和“后剪枝”。预剪枝是乘以仲裁树生成过程遭到,对每个结点划分前先进行估价,若当前结点的分开不克带来决策树泛化性能提升,则止划分并以目前结点标记为叶结点(有差拟合风险)。后剪枝则是先行从训练集生成一蔸完整的决策树,然后自底向上地指向非叶结点进行考察,若以欠结点对应的子树替换为叶结点能带决策树泛化性能提升,则拿该子树替换为叶节点。

 

核定树简介

仲裁树算法不用说大家应都知晓,是机器上的一个尽人皆知算法,由澳大利亚尽人皆知计算机科学家Rose
Quinlan发表。

决定树是同一种监督上之归类算法,目的是学来同颗决策树,该树中间节点是多少特征,叶子节点是项目,实际分类时根据树的布局,一步一步冲当下数据特征取值选择进入哪一样粒子树,直到走及叶子节点,叶子节点的类别就是是此决定树对这个数量的求学结果。下图虽是一致颗简单的决定树:

公海赌船网址 7这个决定树用来判定一个拥有纹理,触感,密度的西瓜是否是“好瓜”。

当起诸如此类一个西瓜,纹理清晰,密度也0.333,触感硬滑,那么要你判定是否是一个“好瓜”,这时要经过表决树来判断,显然可以直接本着纹理->清晰->密度<=0.382->否,即是瓜不是“好瓜”,一赖表决就如此成功了。正因决策树决策很有益,并且准确率为较高,所以常常让用来举行分类器,也是“机器上十颇算法”之一C4.5的主导思想。

读书产生同颗决策树要考虑一个题目,即 根据数量集构建当前培育应该选哪种属性作为树根,即分标准? 

设想最好的气象,一开始选有特征,就将多少集划分成,即当该特征及取某个值的均是千篇一律类似。

设想最充分之状,不断选择特征,划分后的数量集总是乱,就第二分拣任务以来,总是发出正类有负类,一直到特征全部据此了了,划分的数码集合还是时有发生凑巧发生赖,这时只能用投票法,正接近多便挑正类作为叶子,否则选负类。

故而得出了一般结论:
随着划分的进行,我们期望选择一个特点,使得子节点包含的范本尽可能属于同一品种,即“纯度”越强更好。

冲“纯度”的科班各异,有三栽算法:

1.ID3算法(Iterative
Dichotomiser
迭代二分器),也是本文要促成的算法,基于信息增益即信息熵来度量纯度

2.C4.5算法(Classifier
4.5),ID3 的晚算法,也是昆兰提出

3.CART算法(Classification
And Regression Tree),基于基尼指数度量纯度。

1.2 构造决策树

本书用ID3算法划分数据集,即透过对照选择不同风味下数据集的音增益和香农熵来规定最精彩划分特征。 

1.2.1 计算香农熵:

 1 from math import log
 2 import operator
 3 
 4 def createDataSet():
 5     '''
 6     产生测试数据
 7     '''
 8     dataSet = [[1, 1, 'yes'],
 9                [1, 1, 'yes'],
10                [1, 0, 'no'],
11                [0, 1, 'no'],
12                [0, 1, 'no']]
13     labels = ['no surfacing','flippers']    
14     return dataSet, labels
15 
16 def calcShannonEnt(dataSet):
17     '''
18     计算给定数据集的香农熵
19     '''
20     numEntries = len(dataSet)
21     labelCounts = {}
22     #统计每个类别出现的次数,保存在字典labelCounts中
23     for featVec in dataSet: 
24         currentLabel = featVec[-1]
25         if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0
26         labelCounts[currentLabel] += 1 #如果当前键值不存在,则扩展字典并将当前键值加入字典
27     shannonEnt = 0.0
28     for key in labelCounts:
29         #使用所有类标签的发生频率计算类别出现的概率
30         prob = float(labelCounts[key])/numEntries
31         #用这个概率计算香农熵
32         shannonEnt -= prob * log(prob,2) #取2为底的对数
33     return shannonEnt
34 
35 if __name__== "__main__":  
36     '''
37     计算给定数据集的香农熵
38     '''
39     dataSet,labels = createDataSet()
40     shannonEnt = calcShannonEnt(dataSet)

 

1.2.2 划分数据集

 1 def splitDataSet(dataSet, axis, value):
 2     '''
 3     按照给定特征划分数据集
 4     dataSet:待划分的数据集
 5     axis:   划分数据集的第axis个特征
 6     value:  特征的返回值(比较值)
 7     '''
 8     retDataSet = []
 9     #遍历数据集中的每个元素,一旦发现符合要求的值,则将其添加到新创建的列表中
10     for featVec in dataSet:
11         if featVec[axis] == value:
12             reducedFeatVec = featVec[:axis]
13             reducedFeatVec.extend(featVec[axis+1:])
14             retDataSet.append(reducedFeatVec)
15             #extend()和append()方法功能相似,但在处理列表时,处理结果完全不同
16             #a=[1,2,3]  b=[4,5,6]
17             #a.append(b) = [1,2,3,[4,5,6]]
18             #a.extend(b) = [1,2,3,4,5,6]
19     return retDataSet

划分数据集的结果如下所示:

公海赌船网址 8

 

慎选最好好之数集划分方式。接下来我们拿遍历整个数据集,循环计算香农熵和
splitDataSet() 函数,找到最好好之特性划分方式。  

 1 def chooseBestFeatureToSplit(dataSet):
 2     '''
 3     选择最好的数据集划分方式
 4     输入:数据集
 5     输出:最优分类的特征的index
 6     '''
 7     #计算特征数量
 8     numFeatures = len(dataSet[0]) - 1
 9     baseEntropy = calcShannonEnt(dataSet)
10     bestInfoGain = 0.0; bestFeature = -1
11     for i in range(numFeatures):
12         #创建唯一的分类标签列表
13         featList = [example[i] for example in dataSet]
14         uniqueVals = set(featList)
15         #计算每种划分方式的信息熵
16         newEntropy = 0.0
17         for value in uniqueVals:
18             subDataSet = splitDataSet(dataSet, i, value)
19             prob = len(subDataSet)/float(len(dataSet))
20             newEntropy += prob * calcShannonEnt(subDataSet)     
21         infoGain = baseEntropy - newEntropy
22         #计算最好的信息增益,即infoGain越大划分效果越好
23         if (infoGain > bestInfoGain):
24             bestInfoGain = infoGain
25             bestFeature = i
26     return bestFeature

 

1.2.3 递归构建决策树

当前咱们就修了起数量集构造决策树算法所待的支行功能模块,其行事原理如下:得到原始数据集,然后根据最好之属性值划分数据集,由于特征值可能多于两单,因此可能存在超出两只支行的数集划分。第一次私分之后,数据将于于下传递及培养分支的下一个节点,在是节点上,我们可重复划分数据。因此我们可以利用递归的原则处理数量集。递归结束的尺度是:程序遍历完所有划分数据集的性,或者每个分支下的具有实例都有所相同的归类。 

是因为特性数据并无是以每次划分数据分组时还缩减,因此这些算法在实际运用时可能惹一定的题材。目前咱们并不需要考虑这个题材,只需要在算法开始运行前算列的多少,查看算法是否采用了独具属性即可。如果数据集已经处理了有着属性,但是类标签还不是唯一的,此时咱们一般会采取多数表决的计决定该叶子节点的归类。

当 trees.py 中增如下投票表决代码:

 1 import operator
 2 def majorityCnt(classList):
 3     '''
 4     投票表决函数
 5     输入classList:标签集合,本例为:['yes', 'yes', 'no', 'no', 'no']
 6     输出:得票数最多的分类名称
 7     '''
 8     classCount={}
 9     for vote in classList:
10         if vote not in classCount.keys(): classCount[vote] = 0
11         classCount[vote] += 1
12     #把分类结果进行排序,然后返回得票数最多的分类结果
13     sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
14     return sortedClassCount[0][0]

创建树的函数代码(主函数):

 1 def createTree(dataSet,labels):
 2     '''
 3     创建树
 4     输入:数据集和标签列表
 5     输出:树的所有信息
 6     '''
 7     # classList为数据集的所有类标签
 8     classList = [example[-1] for example in dataSet]
 9     # 停止条件1:所有类标签完全相同,直接返回该类标签
10     if classList.count(classList[0]) == len(classList): 
11         return classList[0]
12     # 停止条件2:遍历完所有特征时仍不能将数据集划分成仅包含唯一类别的分组,则返回出现次数最多的类标签
13     #
14     if len(dataSet[0]) == 1:
15         return majorityCnt(classList)
16     # 选择最优分类特征
17     bestFeat = chooseBestFeatureToSplit(dataSet)
18     bestFeatLabel = labels[bestFeat]
19     # myTree存储树的所有信息
20     myTree = {bestFeatLabel:{}}
21     # 以下得到列表包含的所有属性值
22     del(labels[bestFeat])
23     featValues = [example[bestFeat] for example in dataSet]
24     uniqueVals = set(featValues)
25     # 遍历当前选择特征包含的所有属性值
26     for value in uniqueVals:
27         subLabels = labels[:]
28         myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
29     return myTree

 本例返回 myTree 为字典类型,如下:

{‘no surfacing’: {0: ‘no’, 1:
{‘flippers’: {0: ‘no’, 1: ‘yes’}}}}

ID3算法简介

消息熵是信息论中的一个至关重要概念,也于“香农熵”,香农先生之事迹相比多人数还听罢,一个人口创办了同家理论,牛的要命。香农理论遭遇一个不胜重大之特性就是是”熵“,即”信息内容的不确定性“,香农在拓展信息之定量测算的时候,明确地管信息量定义也擅自不定性程度之减少。这即表明了他对信息之敞亮:信息是用来压缩随意不定性的物。或者发表也香农逆定义:信息是众所周知的增。这吗说明了仲裁树为熵作为划分选择的心地标准的是,即我们想再迅速地起数量中落更多信息,我们就算应当很快下跌不确定性,即减少”熵“。

消息熵定义为:

公海赌船网址 9

D表示数据集,类别总数也|Y|,pk代表D中第k类样本所占的比例。根据该定义,Ent的值更小,信息纯度越强。Ent的范围是[0,log|Y|]

下面要选之一属性进行私分,要挨个考虑每个属性,假设当前设想属性a,a的取值有|V|种,那么我们希望取a作为划分属性,划分到|V|个子节点后,所有子节点的信息熵之同就分后的音熵能够生充分挺之削减,减小的绝多的不得了属性就是咱选择的特性。

分割后底信息熵定义为:

公海赌船网址 10 

用用属性a对样本集D进行划分的音信增益就是本来的音信熵减去划分后底音讯熵:

公海赌船网址 11

ID3算法就是这么每次选一个性质对样本集进行划分,知道少栽情形只要之历程停止:

(1)某个子节点样本全部属同一近乎

(2)属性都因此了了,这时候若果实节点样本或未同等,那么只能少数听多数了

公海赌船网址 12(图片源于网络)

2、测试分类和储存分类器

运决策树的归类函数:

 1 def classify(inputTree,featLabels,testVec):
 2     '''
 3     决策树的分类函数
 4     inputTree:训练好的树信息
 5     featLabels:标签列表
 6     testVec:测试向量
 7     '''
 8     # 在2.7中,找到key所对应的第一个元素为:firstStr = myTree.keys()[0],
 9     # 这在3.4中运行会报错:‘dict_keys‘ object does not support indexing,这是因为python3改变了dict.keys,
10     # 返回的是dict_keys对象,支持iterable 但不支持indexable,
11     # 我们可以将其明确的转化成list,则此项功能在3中应这样实现:
12     firstSides = list(inputTree.keys())
13     firstStr = firstSides[0]
14     secondDict = inputTree[firstStr]
15     # 将标签字符串转换成索引
16     featIndex = featLabels.index(firstStr)
17     key = testVec[featIndex]
18     valueOfFeat = secondDict[key]
19     # 递归遍历整棵树,比较testVec变量中的值与树节点的值,如果到达叶子节点,则返回当前节点的分类标签
20     if isinstance(valueOfFeat, dict): 
21         classLabel = classify(valueOfFeat, featLabels, testVec)
22     else: classLabel = valueOfFeat
23     return classLabel
24 
25 if __name__== "__main__":  
26     '''
27     测试分类效果
28     '''
29     dataSet,labels = createDataSet()
30     myTree = createTree(dataSet,labels)
31     ans = classify(myTree,labels,[1,0])

表决树模型的囤

 1 def storeTree(inputTree,filename):
 2     '''
 3     使用pickle模块存储决策树
 4     '''
 5     import pickle
 6     fw = open(filename,'wb+')
 7     pickle.dump(inputTree,fw)
 8     fw.close()
 9     
10 def grabTree(filename):
11     '''
12     导入决策树模型
13     '''
14     import pickle
15     fr = open(filename,'rb')
16     return pickle.load(fr)
17 
18 if __name__== "__main__":
19     '''
20     存取操作
21     '''
22     storeTree(myTree,'mt.txt')
23     myTree2 = grabTree('mt.txt')

  

ID3算法实现(纯标称值)

若样本全部凡标称值即距离散值的话语,会比较简单。

代码:

公海赌船网址 13公海赌船网址 14

from math import log
from operator import itemgetter
def createDataSet():            #创建数据集
    dataSet = [[1,1,'yes'],
               [1,1,'yes'],
               [1,0,'no'],
               [0,1,'no'],
               [0,1,'no']]
    featname = ['no surfacing', 'flippers']
    return dataSet,featname
def filetoDataSet(filename):
    fr = open(filename,'r')
    all_lines = fr.readlines()
    featname = all_lines[0].strip().split(',')[1:-1]
    print(featname)
    dataSet = []
    for line in all_lines[1:]:
        line = line.strip()
        lis = line.split(',')[1:]
        dataSet.append(lis)
    fr.close()
    return dataSet,featname
def calcEnt(dataSet):           #计算香农熵
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet:
        label = featVec[-1]
        if label not in labelCounts.keys():
            labelCounts[label] = 0
        labelCounts[label] += 1
    Ent = 0.0
    for key in labelCounts.keys():
        p_i = float(labelCounts[key]/numEntries)
        Ent -= p_i * log(p_i,2)
    return Ent
def splitDataSet(dataSet, axis, value):   #划分数据集,找出第axis个属性为value的数据
    returnSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
    return returnSet
def chooseBestFeat(dataSet):
    numFeat = len(dataSet[0])-1
    Entropy = calcEnt(dataSet)
    DataSetlen = float(len(dataSet))
    bestGain = 0.0
    bestFeat = -1
    for i in range(numFeat):
        allvalue = [featVec[i] for featVec in dataSet]
        specvalue = set(allvalue)
        nowEntropy = 0.0
        for v in specvalue:
            Dv = splitDataSet(dataSet,i,v)
            p = len(Dv)/DataSetlen
            nowEntropy += p * calcEnt(Dv)
        if Entropy - nowEntropy > bestGain:
            bestGain = Entropy - nowEntropy
            bestFeat = i
    return bestFeat
def Vote(classList):
    classdic = {}
    for vote in classList:
        if vote not in classdic.keys():
            classdic[vote] = 0
        classdic[vote] += 1
    sortedclassDic = sorted(classdic.items(),key=itemgetter(1),reverse=True)
    return sortedclassDic[0][0]
def createDecisionTree(dataSet,featnames):
    featname = featnames[:]              ################
    classlist = [featvec[-1] for featvec in dataSet]  #此节点的分类情况
    if classlist.count(classlist[0]) == len(classlist):  #全部属于一类
        return classlist[0]
    if len(dataSet[0]) == 1:         #分完了,没有属性了
        return Vote(classlist)       #少数服从多数
    # 选择一个最优特征进行划分
    bestFeat = chooseBestFeat(dataSet)
    bestFeatname = featname[bestFeat]
    del(featname[bestFeat])     #防止下标不准
    DecisionTree = {bestFeatname:{}}
    # 创建分支,先找出所有属性值,即分支数
    allvalue = [vec[bestFeat] for vec in dataSet]
    specvalue = sorted(list(set(allvalue)))  #使有一定顺序
    for v in specvalue:
        copyfeatname = featname[:]
        DecisionTree[bestFeatname][v] = createDecisionTree(splitDataSet(dataSet,bestFeat,v),copyfeatname)
    return DecisionTree
if __name__ == '__main__':
    filename = "D:\\MLinAction\\Data\\西瓜2.0.txt"
    DataSet,featname = filetoDataSet(filename)
    #print(DataSet)
    #print(featname)
    Tree = createDecisionTree(DataSet,featname)
    print(Tree)

View Code

解释一下几独函数:

filetoDataSet(filename)
 将文件被的数量整理成数据集

calcEnt(dataSet)    
计算香农熵

splitDataSet(dataSet, axis, value)    
划分数据集,选择有第axis只属性之取值为value的享有数据集,即D^v,并去丢第axis独特性,因为未待了

chooseBestFeat(dataSet)    
 根据信息增益,选择一个极端好之特性

Vote(classList)      
 如果属性用了,类别仍无等同,投票决定

createDecisionTree(dataSet,featnames)    
递归创建决策树


故西瓜数据集2.0针对性算法进行测试,西瓜数据集见 西瓜数据集2.0,输出如下:

['色泽', '根蒂', '敲声', '纹理', '脐部', '触感']
{'纹理': {'清晰': {'根蒂': {'蜷缩': '是', '硬挺': '否', '稍蜷': {'色泽': {'青绿': '是', '乌黑': {'触感': {'硬滑': '是', '软粘': '否'}}}}}}, '稍糊': {'触感': {'硬滑': '否', '软粘': '是'}}, '模糊': '否'}}

为了能反映决策树的优越性即决定方便,这里根据matplotlib模块编写可视化函数treePlot,对转移的决定树进行可视化,可视化结果如下:

公海赌船网址 15

 

由数量最少,没有装测试数据以说明其准确度,但是我后会冲乳腺癌的例子进行准确度的测试的,下面进入下有些:

3、使用 Matplotlib 绘制树形图

上节我们早就学习怎样从数汇总创建决策树,然而字典的象征形式很不错被了解,决策树的要害优点就是是直观易于理解,如果不克以那个直观显示出来,就无法表达其优势。本节使
Matplotlib 库编写代码绘制决策树。

始建名吧 treePlotter.py 的新文件:

生连续值的场面

有连续值的动静如果 西瓜数据集3.0 

一个特性有好多种取值,我们必将不能够每个取值都做一个分层,这时候要对连接属性进行离散化,有几乎栽办法供选择,其中有数种是:

1.针对各级一样近似别的数据集的连接值取平均值,再取各类的平均值的平均值作为划分点,将连属性化为片类似成为离散属性

2.C4.5应用的老二私分法,排序离散属性,取诸半单底中段作为划分点的候选点,计算以每个划分点划分数据集的信增益,取最好可怜之挺划分点将连属性化为简单类成为离散属性,用该属性进行分的音讯增益就是正计算的极其特别信增益。公式如下:

公海赌船网址 16

此处用第二栽,并于求学前对连属性进行离散化。增加拍卖的代码如下:

def splitDataSet_for_dec(dataSet, axis, value, small):
    returnSet = []
    for featVec in dataSet:
        if (small and featVec[axis] <= value) or ((not small) and featVec[axis] > value):
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
    return returnSet
def DataSetPredo(filename,decreteindex):
    dataSet,featname = filetoDataSet(filename)
    Entropy = calcEnt(dataSet)
    DataSetlen = len(dataSet)
    for index in decreteindex:     #对每一个是连续值的属性下标
        for i in range(DataSetlen):
            dataSet[i][index] = float(dataSet[i][index])
        allvalue = [vec[index] for vec in dataSet]
        sortedallvalue = sorted(allvalue)
        T = []
        for i in range(len(allvalue)-1):        #划分点集合
            T.append(float(sortedallvalue[i]+sortedallvalue[i+1])/2.0)
        bestGain = 0.0
        bestpt = -1.0
        for pt in T:          #对每个划分点
            nowent = 0.0
            for small in range(2):   #化为正类负类
                Dt = splitDataSet_for_dec(dataSet, index, pt, small)
                p = len(Dt) / float(DataSetlen)
                nowent += p * calcEnt(Dt)
            if Entropy - nowent > bestGain:
                bestGain = Entropy-nowent
                bestpt = pt
        featname[index] = str(featname[index]+"<="+"%.3f"%bestpt)
        for i in range(DataSetlen):
            dataSet[i][index] = "是" if dataSet[i][index] <= bestpt else "否"
    return dataSet,featname

重在是先行处理函数DataSetPredo,对数据集提前离散化,然后再度展开上,学习代码类似。输出的裁定树如下:

公海赌船网址 17

3.1 绘制树节点

 1 import matplotlib.pyplot as plt
 2 
 3 # 定义文本框和箭头格式
 4 decisionNode = dict(boxstyle="sawtooth", fc="0.8")
 5 leafNode = dict(boxstyle="round4", fc="0.8")
 6 arrow_args = dict(arrowstyle="<-")
 7 
 8 # 绘制带箭头的注释
 9 def plotNode(nodeTxt, centerPt, parentPt, nodeType):
10     createPlot.ax1.annotate(nodeTxt, xy=parentPt,  xycoords='axes fraction',
11              xytext=centerPt, textcoords='axes fraction',
12              va="center", ha="center", bbox=nodeType, arrowprops=arrow_args )
13 
14 def createPlot():
15     fig = plt.figure(1, facecolor='grey')
16     fig.clf()
17     # 定义绘图区
18     createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropses 
19     plotNode('a decision node', (0.5, 0.1), (0.1, 0.5), decisionNode)
20     plotNode('a leaf node', (0.8, 0.1), (0.3, 0.8), leafNode)
21     plt.show()
22 
23 if __name__== "__main__":  
24     '''
25     绘制树节点
26     '''
27     createPlot()

结果如下:

公海赌船网址 18

出缺失失值的情况

数发生欠失值是广大的情事,我们不好直接丢掉这些数据,因为这样见面损失大量数目,不划算,但是缺失失值我们呢束手无策判断它们的取值。怎么惩罚吧,办法或有的。

设想少独问题: 

1.起缺失失值时如何进行划分选择

2.就摘取分属性,有欠失值的样本划不分开,如何分割?

问题1:生差失值时怎么进展剪切选择**

中心思想是展开极端优属性选择时,先只考虑无缺失失值样本,然后再度乘以相应比例,得到在整个样本集上之约情况。连带考虑到第二独问题的话,考虑给各级一个样书一个权重,此时每个样本不再总是被当作一个独样本,这样好第二独问题的化解:即要样本在属性a上之值缺失,那么以那个看作是所有值都得,只不过取每个值的权重不平等,每个值的权重参考该值在无缺失值样本中的比例,简单地游说,比如以无缺失值样本集中,属性a取去两独值1和2,并且赢得1之权重和占全权重和1/3,而取2底权重和占有2/3,那么根据该属性对样本集进行剪切时,遇到该属性上有缺乏失值的样书,那么我们以为该样本取值2的可能性还充分,于是用该样本的权重乘以2/3由到取值为2的样本集中继续进行划分构造决策树,而随着1/3扛及取值为1的样本集中继续组织。不掌握自家说知道没有。

公式如下:

公海赌船网址 19

其中,D~表示数据集D在属性a上无短缺失值的样书,根据其来判定a属性的高低,rho(即‘lou’)表示属性a的无缺失值样本占所有样本的百分比,p~_k表示无短缺失值样本被第k类似所占用的百分比,r~_v表示管短缺失值样本在属性a上取值为v的样书所占有的比重。

于分样本时,如果生差失值,则将样本划分到所有子节点,在属于性a取值v的子节点上之权重为r~_v
* 原来的权重。

再度详尽的解读参考《机器上》P86-87。

基于权重法修改后底ID3算法实现如下:

公海赌船网址 20公海赌船网址 21

from math import log
from operator import itemgetter

def filetoDataSet(filename):
    fr = open(filename,'r')
    all_lines = fr.readlines()
    featname = all_lines[0].strip().split(',')[1:-1]
    dataSet = []
    for line in all_lines[1:]:
        line = line.strip()
        lis = line.split(',')[1:]
        if lis[-1] == '2':
            lis[-1] = '良'
        else:
            lis[-1] = '恶'
        dataSet.append(lis)
    fr.close()
    return dataSet,featname

def calcEnt(dataSet, weight):           #计算权重香农熵
    labelCounts = {}
    i = 0
    for featVec in dataSet:
        label = featVec[-1]
        if label not in labelCounts.keys():
            labelCounts[label] = 0
        labelCounts[label] += weight[i]
        i += 1
    Ent = 0.0
    for key in labelCounts.keys():
        p_i = float(labelCounts[key]/sum(weight))
        Ent -= p_i * log(p_i,2)
    return Ent

def splitDataSet(dataSet, weight, axis, value, countmissvalue):   #划分数据集,找出第axis个属性为value的数据
    returnSet = []
    returnweight = []
    i = 0
    for featVec in dataSet:
        if featVec[axis] == '?' and (not countmissvalue):
            continue
        if countmissvalue and featVec[axis] == '?':
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
        if featVec[axis] == value:
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
            returnweight.append(weight[i])
        i += 1
    return returnSet,returnweight

def splitDataSet_for_dec(dataSet, axis, value, small, countmissvalue):
    returnSet = []
    for featVec in dataSet:
        if featVec[axis] == '?' and (not countmissvalue):
            continue
        if countmissvalue and featVec[axis] == '?':
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
        if (small and featVec[axis] <= value) or ((not small) and featVec[axis] > value):
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
    return returnSet

def DataSetPredo(filename,decreteindex):     #首先运行,权重不变为1
    dataSet,featname = filetoDataSet(filename)
    DataSetlen = len(dataSet)
    Entropy = calcEnt(dataSet,[1 for i in range(DataSetlen)])
    for index in decreteindex:     #对每一个是连续值的属性下标
        UnmissDatalen = 0
        for i in range(DataSetlen):      #字符串转浮点数
            if dataSet[i][index] != '?':
                UnmissDatalen += 1
                dataSet[i][index] = int(dataSet[i][index])
        allvalue = [vec[index] for vec in dataSet if vec[index] != '?']
        sortedallvalue = sorted(allvalue)
        T = []
        for i in range(len(allvalue)-1):        #划分点集合
            T.append(int(sortedallvalue[i]+sortedallvalue[i+1])/2.0)
        bestGain = 0.0
        bestpt = -1.0
        for pt in T:          #对每个划分点
            nowent = 0.0
            for small in range(2):   #化为正类(1)负类(0)
                Dt = splitDataSet_for_dec(dataSet, index, pt, small, False)
                p = len(Dt) / float(UnmissDatalen)
                nowent += p * calcEnt(Dt,[1.0 for i in range(len(Dt))])
            if Entropy - nowent > bestGain:
                bestGain = Entropy-nowent
                bestpt = pt
        featname[index] = str(featname[index]+"<="+"%d"%bestpt)
        for i in range(DataSetlen):
            if dataSet[i][index] != '?':
                dataSet[i][index] = "是" if dataSet[i][index] <= bestpt else "否"
    return dataSet,featname

def getUnmissDataSet(dataSet, weight, axis):
    returnSet = []
    returnweight = []
    tag = []
    i = 0
    for featVec in dataSet:
        if featVec[axis] == '?':
            tag.append(i)
        else:
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
        i += 1
    for i in range(len(weight)):
        if i not in tag:
            returnweight.append(weight[i])
    return returnSet,returnweight

def printlis(lis):
    for li in lis:
        print(li)

def chooseBestFeat(dataSet,weight,featname):
    numFeat = len(dataSet[0])-1
    DataSetWeight = sum(weight)
    bestGain = 0.0
    bestFeat = -1
    for i in range(numFeat):
        UnmissDataSet,Unmissweight = getUnmissDataSet(dataSet, weight, i)   #无缺失值数据集及其权重
        Entropy = calcEnt(UnmissDataSet,Unmissweight)      #Ent(D~)
        allvalue = [featVec[i] for featVec in dataSet if featVec[i] != '?']
        UnmissSumWeight = sum(Unmissweight)
        lou = UnmissSumWeight / DataSetWeight        #lou
        specvalue = set(allvalue)
        nowEntropy = 0.0
        for v in specvalue:      #该属性的几种取值
            Dv,weightVec_v = splitDataSet(dataSet,Unmissweight,i,v,False)   #返回 此属性为v的所有样本 以及 每个样本的权重
            p = sum(weightVec_v) / UnmissSumWeight          #r~_v = D~_v / D~
            nowEntropy += p * calcEnt(Dv,weightVec_v)
        if lou*(Entropy - nowEntropy) > bestGain:
            bestGain = Entropy - nowEntropy
            bestFeat = i
    return bestFeat

def Vote(classList,weight):
    classdic = {}
    i = 0
    for vote in classList:
        if vote not in classdic.keys():
            classdic[vote] = 0
        classdic[vote] += weight[i]
        i += 1
    sortedclassDic = sorted(classdic.items(),key=itemgetter(1),reverse=True)
    return sortedclassDic[0][0]

def splitDataSet_adjustWeight(dataSet,weight,axis,value,r_v):
    returnSet = []
    returnweight = []
    i = 0
    for featVec in dataSet:
        if featVec[axis] == '?':
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
            returnweight.append(weight[i] * r_v)
        elif featVec[axis] == value:
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
            returnweight.append(weight[i])
        i += 1
    return returnSet,returnweight

def createDecisionTree(dataSet,weight,featnames):
    featname = featnames[:]              ################
    classlist = [featvec[-1] for featvec in dataSet]  #此节点的分类情况
    if classlist.count(classlist[0]) == len(classlist):  #全部属于一类
        return classlist[0]
    if len(dataSet[0]) == 1:         #分完了,没有属性了
        return Vote(classlist,weight)       #少数服从多数
    # 选择一个最优特征进行划分
    bestFeat = chooseBestFeat(dataSet,weight,featname)
    bestFeatname = featname[bestFeat]
    del(featname[bestFeat])     #防止下标不准
    DecisionTree = {bestFeatname:{}}
    # 创建分支,先找出所有属性值,即分支数
    allvalue = [vec[bestFeat] for vec in dataSet if vec[bestFeat] != '?']
    specvalue = sorted(list(set(allvalue)))  #使有一定顺序
    UnmissDataSet,Unmissweight = getUnmissDataSet(dataSet, weight, bestFeat)   #无缺失值数据集及其权重
    UnmissSumWeight = sum(Unmissweight)      # D~
    for v in specvalue:
        copyfeatname = featname[:]
        Dv,weightVec_v = splitDataSet(dataSet,Unmissweight,bestFeat,v,False)   #返回 此属性为v的所有样本 以及 每个样本的权重
        r_v = sum(weightVec_v) / UnmissSumWeight          #r~_v = D~_v / D~
        sondataSet,sonweight = splitDataSet_adjustWeight(dataSet,weight,bestFeat,v,r_v)
        DecisionTree[bestFeatname][v] = createDecisionTree(sondataSet,sonweight,copyfeatname)
    return DecisionTree

if __name__ == '__main__':
    filename = "D:\\MLinAction\\Data\\breastcancer.txt"
    DataSet,featname = DataSetPredo(filename,[0,1,2,3,4,5,6,7,8])
    Tree = createDecisionTree(DataSet,[1.0 for i in range(len(DataSet))],featname)
    print(Tree)

View Code

出缺乏失值的气象只要 西瓜数据集2.0alpha

试行结果:

公海赌船网址 22

3.2 构造注解树

制图一蔸完整的培育欲一些技。我们虽然发生 x,
y 坐标,但是怎么放有的树节点却是独问题。我们亟须了解出稍许个叶节点,以便可以正确确x轴的长度;我们尚需理解树起些许层,来规定y轴的惊人。这里外一两个新函数
getNumLeafs() 和 getTreeDepth()
,来抱叶节点的数额及塑造之层数,createPlot() 为主函数,完整代码如下:

  1 import matplotlib.pyplot as plt
  2 
  3 # 定义文本框和箭头格式
  4 decisionNode = dict(boxstyle="sawtooth", fc="0.8")
  5 leafNode = dict(boxstyle="round4", fc="0.8")
  6 arrow_args = dict(arrowstyle="<-")
  7 
  8 # 绘制带箭头的注释
  9 def plotNode(nodeTxt, centerPt, parentPt, nodeType):
 10     createPlot.ax1.annotate(nodeTxt, xy=parentPt,  xycoords='axes fraction',
 11              xytext=centerPt, textcoords='axes fraction',
 12              va="center", ha="center", bbox=nodeType, arrowprops=arrow_args )
 13 
 14 def createPlot(inTree):
 15     '''
 16     绘树主函数
 17     '''
 18     fig = plt.figure(1, facecolor='white')
 19     fig.clf()
 20     # 设置坐标轴数据
 21     axprops = dict(xticks=[], yticks=[])
 22     # 无坐标轴
 23     createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)
 24     # 带坐标轴
 25 #    createPlot.ax1 = plt.subplot(111, frameon=False)
 26     plotTree.totalW = float(getNumLeafs(inTree))
 27     plotTree.totalD = float(getTreeDepth(inTree))
 28     # 两个全局变量plotTree.xOff和plotTree.yOff追踪已经绘制的节点位置,
 29     # 以及放置下一个节点的恰当位置
 30     plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;
 31     plotTree(inTree, (0.5,1.0), '')
 32     plt.show()
 33 
 34 
 35 def getNumLeafs(myTree):
 36     '''
 37     获取叶节点的数目
 38     '''
 39     numLeafs = 0
 40     firstSides = list(myTree.keys())
 41     firstStr = firstSides[0]
 42     secondDict = myTree[firstStr]
 43     for key in secondDict.keys():
 44         # 判断节点是否为字典来以此判断是否为叶子节点
 45         if type(secondDict[key]).__name__=='dict':
 46             numLeafs += getNumLeafs(secondDict[key])
 47         else:   numLeafs +=1
 48     return numLeafs
 49 
 50 def getTreeDepth(myTree):
 51     '''
 52     获取树的层数
 53     '''
 54     maxDepth = 0
 55     firstSides = list(myTree.keys())
 56     firstStr = firstSides[0]
 57     secondDict = myTree[firstStr]
 58     for key in secondDict.keys():
 59         if type(secondDict[key]).__name__=='dict':
 60             thisDepth = 1 + getTreeDepth(secondDict[key])
 61         else:   thisDepth = 1
 62         if thisDepth > maxDepth: maxDepth = thisDepth
 63     return maxDepth
 64 
 65 
 66 def plotMidText(cntrPt, parentPt, txtString):
 67     '''
 68     计算父节点和子节点的中间位置,并在此处添加简单的文本标签信息
 69     '''
 70     xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]
 71     yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
 72     createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
 73 
 74 def plotTree(myTree, parentPt, nodeTxt):#if the first key tells you what feat was split on
 75     # 计算宽与高
 76     numLeafs = getNumLeafs(myTree)  #this determines the x width of this tree
 77     depth = getTreeDepth(myTree)
 78     firstSides = list(myTree.keys())
 79     firstStr = firstSides[0]
 80     cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)
 81     # 标记子节点属性值    
 82     plotMidText(cntrPt, parentPt, nodeTxt)
 83     plotNode(firstStr, cntrPt, parentPt, decisionNode)
 84     secondDict = myTree[firstStr]
 85     # 减少y偏移
 86     plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD
 87     for key in secondDict.keys():
 88         if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes   
 89             plotTree(secondDict[key],cntrPt,str(key))        #recursion
 90         else:   #it's a leaf node print the leaf node
 91             plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
 92             plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
 93             plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
 94     plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD
 95 
 96 
 97 def retrieveTree(i):
 98     '''
 99     保存了树的测试数据
100     '''
101     listOfTrees =[{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}},
102                   {'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}}
103                   ]
104     return listOfTrees[i]
105 
106 
107 
108 if __name__== "__main__":  
109     '''
110     绘制树
111     '''
112     createPlot(retrieveTree(1))

测试结果:

公海赌船网址 23

于乳腺癌数据集上的测试和表现

起了算法,我们本想做得之测试看无异扣押算法的呈现。这里自己选了威斯康辛女性乳腺癌的多少。

多少总共有9列,每一样排列分别表示,以公海赌船网址逗号分割

1 Sample
code number (病人ID)
2 Clump
Thickness 肿块厚度
3
Uniformity of Cell Size 细胞大小的均匀性
4
Uniformity of Cell Shape 细胞形状的均匀性
5
Marginal Adhesion 边缘粘
6 Single
Epithelial Cell Size 单上皮细胞的大小
7 Bare
Nuclei 裸核
8 Bland
Chromatin 乏味染色体
9 Normal
Nucleoli 正常核
10
Mitoses 有丝分裂
11 Class:
2 for benign, 4 formalignant(恶性或良性分类)

[from
Toby]

合计700漫漫左右的数码,选取最后80修作为测试集,前面作为训练集,进行上。

以分类器的代码如下:

import treesID3 as id3
import treePlot as tpl
import pickle

def classify(Tree, featnames, X):
    classLabel = "未知"
    root = list(Tree.keys())[0]
    firstGen = Tree[root]
    featindex = featnames.index(root)  #根节点的属性下标
    for key in firstGen.keys():   #根属性的取值,取哪个就走往哪颗子树
        if X[featindex] == key:
            if type(firstGen[key]) == type({}):
                classLabel = classify(firstGen[key],featnames,X)
            else:
                classLabel = firstGen[key]
    return classLabel

def StoreTree(Tree,filename):
    fw = open(filename,'wb')
    pickle.dump(Tree,fw)
    fw.close()

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

if __name__ == '__main__':
    filename = "D:\\MLinAction\\Data\\breastcancer.txt"
    dataSet,featnames = id3.DataSetPredo(filename,[0,1,2,3,4,5,6,7,8])
    Tree = id3.createDecisionTree(dataSet[:620],[1.0 for i in range(len(dataSet))],featnames)
    tpl.createPlot(Tree)
    storetree = "D:\\MLinAction\\Data\\decTree.dect"
    StoreTree(Tree,storetree)
    #Tree = ReadTree(storetree)
    i = 1
    cnt = 0
    for lis in dataSet[620:]:
        judge = classify(Tree,featnames,lis[:-1])
        shouldbe = lis[-1]
        if judge == shouldbe:
            cnt += 1
        print("Test %d was classified %s, it's class is %s %s" %(i,judge,shouldbe,"=====" if judge==shouldbe else ""))
        i += 1
    print("The Tree's Accuracy is %.3f" % (cnt / float(i)))

训练有的核定树如下:

公海赌船网址 24

末段之正确率可以看到:

公海赌船网址 25

正确率约为96%左右,算是不异的分类器了。

自身的乳腺癌数据见:http://7xt9qk.com2.z0.glb.clouddn.com/breastcancer.txt

从那之后,决策树算法ID3的兑现得了,下面考虑因基尼指数及消息增益率进行分割选择,以及考虑实现剪枝过程,因为咱们好看看上面训练有底核定树还设有正在无数冗余分支,是因实现过程中,由于数据量太死,每个分支都未完全纯净,所以会见创造为下之旁,但是分支投票的结果还要是同样的,而且数据量再好,特征数还多吧,决策树会非常深非常复杂,所以剪枝一般是必然做的一致步。剪枝分为先剪枝和晚剪枝,如果细说的口舌可以形容很多了。

此文亦可见:这里
参考资料:《机器上》《机器上实战》通过本次实战也发觉了马上点儿本书中之一对错的远在。

lz初学机器上不久,如发生错漏的处在请求多包涵指出要各位有什么想法或意见欢迎评论去报我:)

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

 4.1 处理流程

公海赌船网址 26

公海赌船网址 27

 

数据格式如下所示,其中最后一列意味类标签:

公海赌船网址 28 

4.2 Python实现代码 

1 import trees
2 import treePlotter
3 
4 fr = open('lenses.txt')
5 lenses = [inst.strip().split('\t') for inst in fr.readlines()]
6 lensesLabels=['age','prescript','astigmatic','tearRate']
7 lensesTree = trees.createTree(lenses,lensesLabels)
8 treePlotter.createPlot(lensesTree)

生的表决树:

公海赌船网址 29

 

本节应用的算法成为ID3,它是一个哀号的算法但无能为力直接处理数值型数据,尽管我们得透过量化的不二法门将数值型数据转发为标称型数值,但如存在不过多之表征划分,ID3算法仍然会面临其他题目。

相关文章