机器学习实战_决策树

# -*- encoding:utf-8 -*-
from math import log
import operator
import matplotlib as plt

def 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 shannonEnt

def createDatsSet():
    dataset=[
        [1,1,'yes'],
        [1,1,'yes'],
        [1,0,'no'],
        [0,1,'no'],
        [0,1,'no']
    ]
    labels=['no surfacing','flippers']
    return dataset,labels
def 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 retDataSet

def 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 myTree
def 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值