zoukankan      html  css  js  c++  java
  • 机器学习-决策树

    本文代码均来自《机器学习实战》

    '''
    Created on Oct 12, 2010
    Decision Tree Source Code for Machine Learning in Action Ch. 3
    @author: Peter Harrington
    '''
    import pdb
    from math import log
    import operator
    #书中源代码和目前机器学习主流的表示方法是有出入的!原文中的代码中的labels指的其实是feature的名称,这个不应该和labels混为一谈。一开始没看懂这个,理解源代码的时候绕了很大的弯路
    def createDataSet():#生成测试集
        #这种方法对于小规模样本挺好的,省的每次都要输入测试集或者读文件,直接将数据写在程序中当成函数就可以了
        dataSet = [[1, 1, 'yes'],
                   [1, 1, 'yes'],
                   [1, 0, 'no'],
                   [0, 1, 'no'],
                   [0, 1, 'no']]
        features = ['no surfacing','flippers']
        #change to discrete values
        return dataSet,features
    
    def calcShannonEnt(dataSet):#计算信息熵
        #计算公式是H=-∑P(xi)log2(P(xi)),i=1:n,其中P(Xi)是选择该分类的概率
        numEntries = len(dataSet)
        featuresCounts = {}
        for featVec in dataSet: #the the number of unique elements and their occurance,说白了就是计算有几种features和这几种features对应的出现频率,概率等会算
            currentfeatures = featVec[-1]
            if currentfeatures not in featuresCounts.keys(): featuresCounts[currentfeatures] = 0#如果features集合中没有该features的话就加进去,并且设置它的出现频率为0
            featuresCounts[currentfeatures] += 1#该features出现了
        shannonEnt = 0.0#信息熵
        for key in featuresCounts:
            prob = float(featuresCounts[key])/numEntries#把频率算成概率
            shannonEnt -= prob * log(prob,2) #log base 2
        return shannonEnt
        
    def splitDataSet(dataSet, axis, value):#划分数据集以减少熵
        #axis是划分数据集的特征,axis是对应的要分割数据的列,value是要分割的列按哪个值分割,即找到含有该值的数据
        #想象数据分布在一个二维平面,我们要用一条直线对这些数据进行划分,那么应该在哪个轴划线呢?又应该在哪个位置划线呢?
        #这里axis就确定了在哪个维度进行划线,而value就是要在这个维度上划线的位置
        #python函数传递的都是参数的引用,为了不对原有的数据集造成破坏,我们新建一个对象:
        retDataSet = []#划分后的数据集集合
        for featVec in dataSet:#取出数据集合中的一个样本
            if featVec[axis] == value:
                #如果featVec[axis] != value,根本就不放入返回的数组中
                #把一个样本的特征裂开,前面的0-axis个数据,不包括axis,直接放到reducedFeatVec中 
                reducedFeatVec = featVec[:axis]     #chop out axis used for splitting
                #将axis之后的特征平铺,消除数据的层数,都放在一个数组中
                reducedFeatVec.extend(featVec[axis+1:])
                retDataSet.append(reducedFeatVec)#这是一个处理好的样本
        return retDataSet
        
    def chooseBestFeatureToSplit(dataSet):
        #这只是找一次分所需的特征,要构建一棵树需要不停地分,直至每一类中label唯一了
        numFeatures = len(dataSet[0]) - 1      #the last column is used for the featuress,获取特征的数量
        baseEntropy = calcShannonEnt(dataSet)#计算不划分的话熵是多少
        bestInfoGain = 0.0  # 定义最大的信息增益 
        bestFeature = -1# 定义分割后信息增益最大的特征 
        #遍历特征,即所有的列,计算每一列分割后的信息增益,找出信息增益最大的列
        for i in range(numFeatures):        #iterate over all the features
            #选择样本总体的一个维度
            featList = [example[i] for example in dataSet]#create a list of all the examples of this feature
            #取出所选择的这个维度
            uniqueVals = set(featList)       #get a set of unique values
            newEntropy = 0.0
            for value in uniqueVals:
                #选择这个特征中的某个值分开,得到的subDataSet只包括部分数据,由于如果我们要使用这个特征分开,那么就需要彻底的分开,一个值分一个subset
                subDataSet = splitDataSet(dataSet, i, value)
                prob = len(subDataSet)/float(len(dataSet))
                newEntropy += prob * calcShannonEnt(subDataSet) #计算这部分特征的信息增益    
            infoGain = baseEntropy - newEntropy     #calculate the info gain; ie reduction in entropy
            if (infoGain > bestInfoGain):       #compare this to the best gain so far
                bestInfoGain = infoGain         #if better than current best, set to best
                bestFeature = i
        return bestFeature                      #返回分开效果最好的特征
    # 对类标签进行投票 ,找标签数目最多的标签
    def majorityCnt(classList):
        classCount={}
        for vote in classList:
            #如果这个features之前没见过,就设置value为0
            if vote not in classCount.keys(): classCount[vote] = 0
            classCount[vote] += 1
        sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
        # 返回数目最多的features
        return sortedClassCount[0][0]
    """
    它的输入长这个样子:
    dataSet = [[1, 1, 'yes'],
                   [1, 1, 'yes'],
                   [1, 0, 'no'],
                   [0, 1, 'no'],
                   [0, 1, 'no']]
        featuress = ['no surfacing','flippers']
    
    """
    def createTree(dataSet,featuress):#建立决策树的主要部分
        #数据的features的值是存放在dataSet中的,featuress里面是这些features的全写,dataSet里面的只是简略表示
        #tm的python还有这种倒着寻址的,几乎都要忘掉了
        #将dataSet的最后一列数据(标签)取出赋给classList,classList存储的是labels列
        classList = [example[-1] for example in dataSet]
        #这里处理的是一个特殊的case,也是算法停机性的所在:如果一个set里面分类都一样了就停机了
        if classList.count(classList[0]) == len(classList): #计算是不是所有的labels都是第1个那种
            return classList[0]#stop splitting when all of the classes are equal,返回了一个labels
        #这里是另外一个特殊case,就是set里面只有一个样本了
        if len(dataSet[0]) == 1: #stop splitting when there are no more features in dataSet
            return majorityCnt(classList)
        bestFeat = chooseBestFeatureToSplit(dataSet)
        bestFeatfeatures = featuress[bestFeat]#选出要分开的features的全称
        # 定义决策树,key为bestFeatfeatures,value为空
        #树结构是利用dict完成的,注意key对应的value也是个dict,所以这里其实是个二维dict
        myTree = {bestFeatfeatures:{}}
        del(featuress[bestFeat])#删除featuress[bestFeat]对应的元素,这种情况在树根已经被解决了
        featValues = [example[bestFeat] for example in dataSet]
        uniqueVals = set(featValues)#取出dataSet中所有数据中要分类的feature的所有值
        for value in uniqueVals:
            subfeaturess = featuress[:]       #copy all of featuress, so trees don't mess up existing featuress
            #妙啊,这嵌套的dict用起来太妙了
            myTree[bestFeatfeatures][value] = createTree(splitDataSet(dataSet, bestFeat, value),subfeaturess)
        return myTree #最后生成的树是dict的嵌套,最后可以像上面一样用连续的下标进行访问                           
    #生成基于决策树的分类函数
    #使用步骤:先调用createTree生成树,再调用该方法进行classify
    def classify(inputTree,featfeaturess,testVec):
        #featfeaturess是树中所用的features的名称的集合,testVec是测试集
         # 得到树中的第一个特征
        firstStr = list(inputTree.keys())[0]
         # 得到第一个对应的值或者子dict,也就是下一层
        secondDict = inputTree[firstStr]
        # 得到树中第一个特征对应的索引,也就是在data中是第几维的
        featIndex = featfeaturess.index(firstStr)
        for key in secondDict.keys():#遍历树的各个分支  
            # 如果在secondDict[key]中找到testVec[featIndex]  
            if testVec[featIndex] == key:#如果一致,就从这个入口进去
                # 判断secondDict[key]是否为字典  
                if type(secondDict[key]).__name__ == 'dict':  
                    # 若为字典,递归的寻找testVec  
                    classfeatures = classify(secondDict[key], featfeaturess, testVec)  
                else:  
                    # 若secondDict[key]为标签值,则将secondDict[key]对应的value,也就是features赋给classfeatures  
                    classfeatures = secondDict[key]  
        return classfeatures
    #将树进行序列化保存
    """
    序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。
    在序列化期间,对象将其当前状态写入到临时或持久性存储区。 
    以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
    """
    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)
    #下面是使用决策树进行分类的一个例子
    dataSet,featuress=createDataSet()
    featuressSave=featuress[:]#建立树时会对features有修改,所以要额外保存一份
    #注意直接写featuressSave=featuress是不行的,这样子传递的只是引用,必须加上[:]才是真正的拷贝
    thisTree=createTree(dataSet,featuress)
    classify(thisTree,featuressSave,[1,0])
    #这里要是输入classify(thisTree,featuressSave,[2,0])就会报错,因为我们的树建立起来的时候对于第一个feature的分类只有0和1,不存在2
    
  • 相关阅读:
    使用Eolinker拓展API设计
    如何记录API
    API设计
    【翻译】通过API主导的集成来证明您的业务未来
    从状态库迁移分布系统建设方式
    PostgreSql 查询某个模式下面的所有表
    迁移状态库的地市区县信息
    测开和开发的难度对比
    yum源的三种搭建方式
    Harbor实现容器镜像仓库的管理和运维
  • 原文地址:https://www.cnblogs.com/jiading/p/11624072.html
Copyright © 2011-2022 走看看