MLIA_决策树

#-*-coding:utf-8-*-
from math import log
import operator

# 3.1 计算给定数据集的香农熵calcShannonEnt()
def calcShannonEnt(dataSet):
    # 首先计算数据集中实例的总数
    numEntries = len(dataSet)
    # 创建一个数据字典,
    labelCounts = { }
    for featVec in dataSet:
        # 它的键值是最后一列的数值
        currentLabel = featVec[-1]
        # 如果当前键值不存在,则扩展字典并将当前键值加入字典
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
            # 每个键值都记录了当前类别出现的次数
            labelCounts[currentLabel] += 1
        shannonEnt = 0.0
    # 使用所有类别标签的发生频率计算类别出现的概率
    for key in labelCounts:
            prob = float(labelCounts[key])/numEntries
            shannonEnt -= prob*log(prob,2)
    return shannonEnt

def createDataSet():
    dataSet = [[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']]
    labels = ['no surfacing','flippers']
    return dataSet, labels 

# 3.2 按照给定特征划分数据集(带划分的数据集,划分数据集的特征,特征的返回值)
# python语言不同考虑内存分配问题,python语言在函数中的传递的是列表的引用,在函数内部对列表兑现的修改,将会影响该列表对象的整个生存周期。为了消除这种不良影响,我们需要在函数的开始生命一个新的列表对象
def splitDataSet(dataSet, axis, value):
    # 因为该函数代码在同一数据集上被调用多次, 为了不修改原始数据集,创建一个新的列表对象。
    retDataSet = []
    # 数据集这个列表中的各个元素也是列表,我们要变美丽数据集中的每个元素,符合要求的值添加到新创建的列表中
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            # 当我们按照某个特征划分数据集时,就需要将所有符合要求的元素抽取出来。代码中使用了自带的exten()和append()方法
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet


# 接下来遍历整个数据集,循环计算香农熵和splitDataSet()函数,找到最好的特征划分方法。熵计算将告诉我们如何划分数据集是最好的数据组织方式
# 选择最好的数据集划分方式,选取特征,划分数据集,计算得出最好的划分数据集的特征
# 函数chooseBestFeatureToSplit()使用了calcShannonEnt(dataSet)和 splitDataSet(dataSet, axis, value),在函数中调用的数据需要满足一定的要求:
# 一是数据必须是一种由列表元素组成的列表,而且所有的列表元素都要具有相同的数据长度;二是,数据的最后一列或每个实例的最后一个元素是当前实例的类别标签
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1      #the last column is used for the labels
    # 计算整个数据集的原始香农熵
    baseEntropy = calcShannonEnt(dataSet)
    # 保存最初的无序度量值
    bestInfoGain = 0.0; bestFeature = -1
    for i in range(numFeatures):        #iterate over all the features
        # 使用推导列表来创建新的列表,将数据集中所有第i个特征值或者所有可能存在的值写入这个新的list中
        featList = [example[i] for example in dataSet]#create a list of all the examples of this feature
        # 使用python语言原生的集合(set)数据类型。set数据类型和list数据类型相似,不同之处仅在于set类型中的每个值互不相同,从列表中创建集合时python语言得到列表中唯一元素值得最快方式
        uniqueVals = set(featList)       #get a set of unique values
        newEntropy = 0.0
        # 遍历当前特征中的唯一属性值,对每个特征划分一次数据集
        for value in uniqueVals:
            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                      #returns an integer


# 与classify0部分的投票表决代码非常类似,该函数使用分类名称的列表,然后创建键值为classList中唯一值得数据字典,字典对象村出了classList中每个类标签出现的频率,最后利用operator操作键值排序字典,并返回出现次数最多的分类名称
def majorityCnt(classList):
    classCount = { }
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 0
            classCount[vote] += 1
    sortedClassCount = sorted(classCount.iteritems(), key = operator.itemgetter(1), reverse = True)
    return sortedClassCount[0][0]


# 3.4 创建树的函数代码
# 两个输入参数:数据集和标签列表。标签列表包含了数据集中所有特征的标签(算法本身并不需要这个变量,但是为了给出数据明确的含义)
def createTree(dataSet, labels):
    classList = [example[-1] for example in dataSet]
    # 递归函数的第一个停止条件是所有的类标签完全相同,则直接返回该标签
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    # 递归函数的第二个停止条件是使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。
    if len(dataSet[0])==1:
        return majorityCnt(classList)
    # 由于第二个条件无法简单地返回唯一的类标签,这里使3.3函数挑选出现次数最多的列别作为返回值
    bestFeat = chooseBestFeatureToSplit(dataSet)
    bestFeatLabel = labels[bestFeat]
    # 下一步程序开始创建树,使用python语言的字典类型存储树的信息,也可以声明特殊的数据类型存储树。字典变量myTree存储了树的所有信息,对其后绘图十分重要
    # 当前数据集选取的最好特征值存储在bestFeat中,
    myTree = {bestFeatLabel:{}}
    # 得到列表包含的所有属性值
    del(labels[bestFeat])
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    # 最后遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree(),得到的返回值将被插入到字典变量myTree中,因此函数终止执行时,字典中将会嵌套很好代表叶子节点信息的字典数据。
    for value in uniqueVals:
        # 这行代码复制了类标签,并将其存储在新列表变量subLabels中。
        # 之所有这样做,是因为在python语言中函数参数是列表类型时,参数是按照引用方式传递的。为了保证每次调用createTree()函数时不改变原始列表的内容,使用新变量subLabels代替原始列表
        subLabels = labels[:]
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
    return myTree



def classify(inputTree, featLabels, testVec):
    firstStr = inputTree.keys()[0]
    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


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)

猜你喜欢

转载自blog.csdn.net/weixin_42836351/article/details/81300884