机器学习实战第三章决策树算法照葫芦画瓢算法实践

来源:互联网 发布:斗鱼诸葛网络吃蛇视频 编辑:程序博客网 时间:2024/06/05 00:35

分别实现了决策树的构造和隐形眼镜的分类模型以及决策树的可视化,在此主要贴决策树算法的具体实现过程和一个必要的手动模拟子函数的过程。

并且贴出隐形眼镜决策树的可视化效果。

# -*- coding: utf-8 -*-"""照葫芦画瓢完成于2017.4.18 19:49算法名称 : 决策树算法算法整体思路:   1.基于给定的数据,建立一棵决策树   2.根据给定的决策树,输入数据得到分类结果   建树过程:   1.根据可以分类的特征,对数据集进行划分。   2.根据熵的大小进行递归划分,每次划分都选择能使得熵最小的特征进行划分,递归直到叶子结点上的分类结果一致   3.当然用完了所有特征如果叶子结点里的分类结果还多种多样,则进行一次投票,选择出现次数最多的分类作为那个叶子结点的最终分类作者:    zzt941006"""from math import logimport operator#import sys#sys.path.append('c:\Python27\trees.py') # 这个例子针对 windows 用户来说的  def createDataSet():    dataSet = [[1,1,'yes'],               [1,1,'yes'],              [1,0,'no'],              [0,1,'no'],              [0,1,'no']]#这是一个二维矩阵,第一维表示它是第几行,每一行里面有三个元素,第二维表示列    labels = ['no surfacing','flippers'] #浮出水面是否能够生存 和是否有脚蹼,对应着dataSet里的前两列    return dataSet,labels#计算香农熵def calcShannonEnt(dataSet):    numEntries = len(dataSet) #实例总数    labelCounts = {}    for featVec in dataSet:        currentLabel = featVec[-1] #取矩阵的最后一列,其值代表具体的分类是啥。        if currentLabel not in labelCounts:            labelCounts[currentLabel] = 0 #如果当前键值不存在,则扩展字典并将当前键值加入其中        labelCounts[currentLabel] += 1 #这句话之所以不用elif是因为如果键值不存在,将键值加入其中后,值为1,而如果已经出现则用作累加。    shannonEnt = 0.0    for key in labelCounts:        prob = float(labelCounts[key]) / numEntries        shannonEnt -= prob * log(prob,2)#根据 H = - Σ p(xi) * log2(xi) 来计算信息熵    #print 2333    return shannonEnt#按照给定特征划分数据集,参数三个分别代表:待划分的数据集,划分数据集的特征,需要返回的特征的值  #大概过程是,遍历dataSet的第axis列,看值是否等于value,等于的把这一列之前与这一列之后的数据存起来即可#注意list.extend(x),如果x是一个表,则会把单独的表元素都取出来然后加进list中#而list.append(x),如果x是一个表,则x将会整体被放进list中。#eg: list = [1,2,3] x = [4,5,6] list.extend(x)之后list为 [1,2,3,4,5,6] 而list.append(x) 之后list 为[1,2,3,[4,5,6]]表里套了一个表#具体到这里的样例:myDat = [[1,1,yes],[1,1,yes],[1,0,no],[0,1,no],[0,1,no]]#调用spiltData(myDat,0,1)则代表,分出数据集中的第0行,所对应的特征为1的相关序列,不过已经不包含这一列的东西了,也即取featVec[:0]那一列#加入到reduceFeatVec中,接着再取featVec[1:end]里的那一列的元素加进reduceFeatVec中,显然featVec[:0]咱们取不出东西,因此加进来的是原数据集#中的第一和第二列的元素,显然可以得到原数据集的前三行是满足的,因为这三行的第0列都是1,然后得到[1,yes],[1,yes],[0,no]这三个表构成一个新表#再比如调用spiltData(myDat,1,1)则代表,分出数据集第1行,所对应特征为1的相关序列,显然加的是featVec[:1]和featVec[2:end]的这些列的元素进去#先构成子表,再构成总表,对应可以得到[1,yes],[1,yes],[0,no],[0,no]这4个子表,构成一个大表维度4 × 2def splitDataSet(dataSet,axis,value):    retDataSet = []    for featVec in dataSet:        if featVec[axis] == value:            reducedFeatVec = featVec[:axis]#取与特征对应的这列之前的所有元素存进表中 0 -> axis -1            reducedFeatVec.extend(featVec[axis+1:])#取与特征对应的这列之后的所有元素存进表中 axis + 1 -> end            retDataSet.append(reducedFeatVec)    return retDataSet#选择最好的数据集划分方式#其主要思想就是利用splitDataSet函数将原有的数据集进行划分,用划分出来的新数据集,计算新的香农熵,并跟当前的香农熵做比较#一旦香农熵变小,则说明这样分类更加稳定,并记录下当前的最优分类所对应的特征,遍历完所有特征之后,获取最优的分类特征def chooseBestFeatureToSplit(dataSet):    numFeatures = len(dataSet[0]) - 1#除去标签可以分为几类,比如这里的特征有两个,不浮出水面是否可以生存和是否有脚蹼    baseEntropy = calcShannonEnt(dataSet)#计算原始的香农熵,没有进行分类    bestInfoGain = 0.0 #最大信息熵初始化    bestFeature = -1#最优特征初始化    for i in range(numFeatures): #遍历数据集中的所有特征        featList = [example[i] for example in dataSet]#分别按每行的第i个元素得到对应的表        #print i, featList        uniqueVals = set(featList)#用Set 去重得到实际有效的不重复特征        #print uniqueVals        newEntropy = 0.0        for value in uniqueVals :#基于某个特征i,遍历i下面可能存在的不同的取值,比如样例里可以取0和1 代表有何没有            subDataSet = splitDataSet(dataSet,i,value) #对数据集进行划分得到子数据集           # print i,value,subDataSet            prob = len(subDataSet) / float(len(dataSet))#计算按照这个特征进行分类的信息个数 除去总数,则为出现概率                                                         #显然基于某个特征下的不同取值对应不同的概率,但是其和肯定为1            newEntropy += prob * calcShannonEnt(subDataSet)#计算子数据的香农熵,乘上自己的概率,累加即为新的熵,类似于求期望           # print i,value,subDataSet,baseEntropy,calcShannonEnt(subDataSet),newEntropy        infoGain = baseEntropy - newEntropy #计算原始香农熵和新香农熵的差作为衡量标准,差距越大说明这种分类越稳定(合理)        if(infoGain > bestInfoGain):            bestInfoGain = infoGain            bestFeature = i           return bestFeature#递归处理这棵决策树,使得决策树的叶子节点上的分类只有一种可能。#比如按照第0列来分,可以得到0 NO 0 NO  1 YES 1 YES 0 NO 第二列的Value为 0 的类别都是NO了所以是叶子节点,而Value = 1的分出来有2个YES#1个NO,因此还得对[1,yes] [1,yes] [0,no] 再进行一次递归,分的更细才可能有新的叶子节点出现,后面的代码里面有模拟的过程注释#majorityCnt是用于用完了所有特征之后,可能还有YES NO的混合,这个时候我们进行一次投票,取Max为当前的分类结果,强行让其变为叶子节点def majorityCnt(classList):    classCount = {}    for vote in classList:        if vote not in classCount.keys():            classCount[vote] = 0        classCount[vote] +=1    sortedClassCount = sorted(classCount.iteritems(),kye = operator.itemgetter(1), reverse = True)    return sortedClassCount[0][0]def createTree(dataSet,labels):    classList = [example[-1] for example in dataSet]    #print classList,classList.count(classList[0]),len(classList)    if classList.count(classList[0]) == len(classList): #所有类的标签完全相同则返回停止递归        return classList[0]    if len(dataSet[0]) == 1: #使用完了所有特征(因为每次对其进行了split操作,所以特征数应该会减少)                             # 仍然不能将数据集划分出来,于是返回出现次数最多的类别       # print 'come in', majorityCnt(classList)        return majorityCnt(classList)    bestFeat = chooseBestFeatureToSplit(dataSet)    bestFeatLabel = labels[bestFeat]        myTree = {bestFeatLabel:{}}        del(labels[bestFeat])    featValues = [example[bestFeat] for example in dataSet] #得到第i列的表    #print bestFeat,bestFeatLabel,featValues    uniqueVals = set(featValues)    for value in uniqueVals:        subLabels = labels[:]        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels)#通过这里的分割特征来进行赋值        #比如得到bestFeatLabel = 'nosufacing',value = 0,bestFeat = 0 则先split [1,1,yes],[1,1,yes] [1,0,no] [0,1,no],[0,1,no]        #得到[1,no] [1,no]再去递归,发现no的个数就是2,与类的长度相等,则返回no,即myTree['nosurfacing'][0] = 'no'        #然后再对bestFeatLabel = 'nosufacing',value = 1,bestFeat = 0 进行split,得到[1,yes],[1,yes],[0,no]发现终止不了,则继续递归        #此时bestFeatLable = 'flippers',通过value = 0 和 1分成了[yes] [yes](1对应)和[no](0对应),于是就可以分别停止递归,得到这棵决策树        # print myTree    return myTree#使用决策树的分类函数def classify(inputTree,featLabels,testVec):    firstStr = inputTree.keys()[0] #取出当前输入树的第1个特征    #print firstStr    secondDict = inputTree[firstStr]#其对应的子树    print firstStr,secondDict    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#树的数据的存取def storeTree(inputTree,filename):    import pickle    fw = open(filename,'w')    pickle.dump(inputTree,fw)    fw.close()def grabTree(filename):    import pickle    fr = open(filename)    return pickle.load(fr)


0 0