机器学习实战_决策树

来源:互联网 发布:kk聊天软件 编辑:程序博客网 时间:2024/09/21 08:14
# -*- encoding:utf-8 -*-from math import logimport operatorimport matplotlib as pltdef calcShannonEnt(dataSet):    '''    1.首先计算数据集种实例的个数    2.然后创建一个词典,键值是最后一列的数值,如果键值不存在,那么扩展字典保存当前键值,每个键值记录当前类别出现的次数    3.最后,使用所在类别的发生频率计算类别出现的频率    我们将用这个概率计算香浓熵,统计所有类标发生的次数    熵越高,则混合的数据越多,可以在数据集中添加更多的分类,    例如观测熵的变化    :param dataSet:    :return:    '''    numEntries=len(dataSet)    labelsCounts={}    # 遍历数据记录    for fect in dataSet:        # 获得当前类标签        currentLabels=fect[-1]        if currentLabels not in labelsCounts.keys(): # 如果在标签-个数的字典中不存在,那么将添加一个值            labelsCounts[currentLabels]=0 # 添加一个标签字典        labelsCounts[currentLabels]+=1 # 统计+1    shannonEnt=0.0    # 获得类别(遍历所有类别)    for key in labelsCounts:        # 计算各个类别的频率        # p=label-count/total dataset        prob=float(labelsCounts[key])/numEntries        # IG-=(i to n)log2P(xi)        shannonEnt-=prob*log(prob,2)    return shannonEntdef createDatsSet():    dataset=[        [1,1,'yes'],        [1,1,'yes'],        [1,0,'no'],        [0,1,'no'],        [0,1,'no']    ]    labels=['no surfacing','flippers']    return dataset,labelsdef splitDataSet(dataSet,axis,value):    '''    1.输入参数:待划分的数据集;划分数据集的特征;需要返回的特征值    2.新建一个list列表,遍历数据集中的每个元素,发现符合的元素则将其添加到新建的列表中    :param dataSet:数据集    :param axis: 拆分的属性特征(认为是最好的特征)    :param value:需要返回的特征值    :return:    '''    retDataSet=[] # 创建空的列表保存计算后的值    for fect in dataSet: # 遍历数据集        # 如果取出的特征是最好(需要返回的值)        # 那么将重组数据,提取出特征值,然后对剩下的数据进行重组,(特征值后面的值前移)        if fect[axis]==value:            reduceFectVec=fect[:axis] # 每条记录的前axis个值            reduceFectVec.extend(fect[axis+1:])            # 重组数据            retDataSet.append(reduceFectVec)    return retDataSetdef chooseBestFeatureToSplit(dataSet):    '''    选择最好的数据集划分形式    1.首先获得数据记录的特征个数    2.计算数据集的熵    3.遍历每个特征i:        将数据集中的所有第i个特征进行抽取,然后唯一化        对每个唯一化的值value进行遍历:            数据子集=拆分数据集(数据集,第i个特征,唯一化遍历的值value)            prob=计算子集长度/数据集的长度            对唯一值的熵进行求和    4.比较所有特征中的信息增益,返回最好特征划分的索引值    :param dataSet:    :return:    '''    numFeatures=len(dataSet[0])-1 # 数据集的特征长度,这里需要为后面遍历每个特征提供数据    baseEntropy=calcShannonEnt(dataSet) # 计算熵(数据集)    beatInfoGain=0.0    bestFeature=-1    for i in range(numFeatures): # 遍历每个特征进行数据拆分        # 取出所有数据记录的第i个特征,组装成填充到列表featList        featList=[example[i] for example in dataSet]        # 对提取到的特征值进行唯一化        uniqueVals=set(featList)        newEntropy=0.0        for value in uniqueVals: # 遍历每个唯一化后的值            # 对第i个特征的值进行拆分出的数据子集            # 1.遍历当前特征中的所有唯一属性值,对每个特征划分一次数据集            # 2.然后计算数据集的新熵值,并对所有唯一特征值得到的熵求和,信息增益是熵的减少或数据无序度的减少            # 3.比较所有特征中的信息增益,返回最好特征划分的索引值            subDataSet =splitDataSet(dataSet,i,value)            prob=len(subDataSet)/float(len(dataSet))            newEntropy+=prob*calcShannonEnt(subDataSet)        infoGain=baseEntropy-newEntropy        if (infoGain > beatInfoGain):            beatInfoGain=infoGain            bestFeature=i    return bestFeature# 原理:# 1.得到原始数据集# 2.然后基于最好的属性值划分数据集,由于特征值可能多于2个,# 因此,可能存在大于两个分支的数据集划分# (1)第一次划分后,数据将被向下传递到树分支的下一个节点# (2)然后在这个节点上,我们可以再次划分数据# 3.# 递归的终止条件是:程序遍历完所有划分数据集的属性;# 或者每个分支上的所有实例都具有相同的类# 如果所有实例具有相同的分类,则得到一个叶子节点或终止块# 任何达到叶子节点的数据必然属于叶子节点的分类def majorityCnt(classList):    '''    多数表决的方法决定该叶子节点的分类    同时,查看是不是已经使用所有的属性(在还没找出最好的分类结论之前)    :param classList: 数据集的类别标签    :return:    '''    classCount={}    # 对类别数据集进行遍历    for vote in classList:        # 如果类别不在统计字典中,则添加一个        if vote not in classCount.keys():            # 计为0            classCount[vote]=0        # 类别+1        classCount[vote]+=1    #  对类别进行排序,    print classCount,'不排序前'    sortedClassCount=sorted(classCount.iteritems(),key=operator.itemgetter(1),reverse=True)    print sortedClassCount,'排序后'    return sortedClassCount[0][0]def createTree(dataSet,labels):    '''    1. 输入两个参数:数据集和标签列表。        标签列表包含了数据集中所有特征的标签        算法本身并不需要这个变量,但是为了给出数据明确的含义,我们将它作为一个输人参数提供。        此外,前面提到的对数据集的要求这里依然需要满足。上述代码首先创建了名为。133^ 1 化的列表变量,        其中包含了数据集的所有类标签。递归函数的第一个停止条件是所有的类标签完全相同,则直接返回该类标签0 。        递归函数的第二个停止条件是使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组©。        由于第二个条件无法简单地返回唯一的类标签,这里使用程序清单3-3的函数挑选出现次数最多的类别作为返回值。    2.下一步程序开始创建树,这里使用Python语言的字典类型存储树的信息,当然也可以声明特    殊的数据类型存储树,但是这里完全没有必要。字典变量bestFeat存储了树的所有信息,这对于    其后绘制树形图非常重要。当前数据集选取的最好特征存储在变量beStFeat 中,得到列表包含    的所有属性值© 。这部分代码与程序清单3-3中的部分代码类似,这里就不再进一步解释了。    最后代码遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数    createTree ( ) ,得到的返回值将被插人到字典变量myTree中,因此函数终止执行时,宇典中将    会嵌套很多代表叶子节点信息的字典数据。在解释这个嵌套数据之前,我们先看一下循环的第一行    subLabels = labels[:],这行代码复制了类标签,并将其存储在新列表变量即证让訂沖。之    所以这样做,是因为在Python语言中函数参数是列表类型时,参数是按照引用方式传递的。为了保    证每次调用函数createTree时不改变原始列表的内容,使用新变量subLabels代替原始列表。    现在我们可以测试上面代码的实际输出结果,首先将程序清单3-4的内容输人到文件1^队?7    中’ 然后在Python命令提示符下输入下列命令:    :param dataSet:    :param labels:    :return:    '''    classList=[example[-1] for example in dataSet] # 获得所有标签    if classList.count(classList[0])==len(classList): #如果标签值的个数和数据长度一致,那么返回一个值就可以了        return classList[0]    if len(dataSet[0])==1: # 如果数据长度为1,(只有一个值)        return majorityCnt(classList)    bestFeat=chooseBestFeatureToSplit(dataSet) # 选择最好的特征    bestFeatLabels=labels[bestFeat] #取出最好标签的    # mytree={'flipper:{0,'no',1,'yes'}'}    myTree={bestFeatLabels:{}}    del(labels[bestFeat])    featValues=[example[bestFeat] for example in dataSet]    uniqueVals=set(featValues)    for value in uniqueVals:        subLabels=labels[:]        myTree[bestFeatLabels][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLabels)    return myTreedef storeTree(inputTree,filename):    import pickle    fw=open(filename,'w')    pickle.dump(inputTree,fw)    fw.close()def grabTree(filename):    import pickle    fr=open(filename,'r')    return pickle.load(fr)class Mat:    def __init__(self):        self.decisionNode=dict(boxstyle='sawtooth',fc='0.8')        self.leafNode=dict(boxstyle='round4',fc='0.8')        self.arrow_args=dict(arrowstyle='<-')    def createPlot(self):        fig=plt.figure(1,facecolor='white')        fig.clf()        plt.ax1=plt.subplot(111,frameon=False)if __name__=='__main__':    myDat,lables=createDatsSet()    # 观察熵的变化    # myDat[0][-1]='maybe'    bestFeature=chooseBestFeatureToSplit(myDat)    # bestFeature=i 表示第i个特征是最好的用于划分数据集的特    print(myDat)    myTree=createTree(myDat,labels=lables)    # 变量myTree包含了很多代表树结构信息的嵌套字典,从左边开始,    # 第一个关键字 no surfacing 是第一个划分数据集的特征名称,该关键字的值也是另一个数据字典。    # 第二个关键字是 no surfacing 特征划分的数据集,这些关键字的值是no surfacing 节点的子节点。    # 这些值可能是类标签,也可能是另一个数据字典。    # 如果值是类标签,则该子节点是叶子节点;    # 如果值是另一个数据字典,则子节点是一个判断节点,这种格式结构不断重复就构成了整棵树。    # 本节的例子中,这棵树包含了3个叶子节点以及2个判断节点。    # {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}    print myTree
0 0