ID3决策树(python3)

1.基本概念(香农熵,信息增益)

2.决策树代码步骤


一.基本概念

1.香农熵(又名信息熵)(Entropy)

一件事情的发生几率越高,它携带的信息熵越低(信息熵为非负数)

香农熵的公式:


其中 U 事件有 n 种可能结果,Pi 为[ 可能结果i ]的发生几率 (i<=n)

例如输入一种数据,判断这个人的性别,即 n = 2,因为性别只有 男 或 女 两种可能结果。


再举一个例子:对一个游戏的活跃用户进行分层,分为很活跃,较活跃,不活跃,用户比例分别为:20%,30%,50%,则以这种方式划分的熵为:




2.信息增益(Information Gain)

某个条件对某个事件的决定因素越大,此条件的信息增益越高。如今天阴天这个条件对今天会否下雨这个事件影响很大,因为阴天就意味着有很大机会下雨,所以阴天的信息增益会比较高。


H为原始数据集信息熵。

T为划分之后的分支集合

p(t)为该分支集合在原本的父集合中出现的概率

H(t)为该子集合的信息熵

某个特征类别X的[信息增益] 为:父数据集的香农熵 减去 各分支集合的香浓熵*该分支在父数据集中的出现的几率 之和


3.决策树作用

通过调查所得的一个数据集,形成决策树。形成决策树后,决策树可以通过一组新数据来判定这组数据属于哪个范畴

如有:

其中:“不浮出水面是否可以生存” 和 “是否有脚蹼” 为数据集的特征类别。

而“是否属于鱼类” 是 通过 “不浮出水面是否可以生存” 和 “是否有脚蹼”  而判断的得出的[结论]


形成决策树后,若输入一组数据,如 [否,否],则可以判断这组数据是否鱼类


形成的决策树:


二.决策树代码步骤

1.构造数据集(当作调查数据)

如有上面一组调查数据,把是写成1,否写成0.,并把[特征类别]用一列表(labels)装载。

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

2.计算信息熵

def calcShannonEnt(dataSet):
    numEntries = len(dataSet)               #计算出数据集有多少组数据
    labelCount ={}                          #用于记录当前数据集最后一列各个取值的出现次数
    for featVec in dataSet:                 #依次取出数据集的每组数据
        CurrentLabel = featVec[-1]          #获取每组数据最后一列数据(即数据集最后一列的数据)
        if CurrentLabel not in labelCount.keys():   #记录最后一列数据各个取值的出现次数
            labelCount[CurrentLabel]=0              #若某取值第一次出现,则加入到labelCount中,并设置次数为0
        labelCount[CurrentLabel]+=1                 #若某取值已经出现过,则次数加1
    #计算香农熵
    shannonEnt = 0.0                                #香农熵初始设置为0
    for key in labelCount:                          #取出数据集最后一列的各个取值
        prob = float(labelCount[key])/numEntries    #某个取值在数据集中的出现概率
        shannonEnt -= prob*log(prob,2)              #通过公式计算香农熵
        print(shannonEnt)
    return shannonEnt

如一数据集为:

[1,1,'YES']

[1,1,'YES']

[1,0,'NO'  ]   

[0,1,'NO  ']

[0,1,'NO  ']

取出最后一列数据,并统计最后一列的取值出现次数(YES 和 NO 的出现次数):

labelCount={‘YES’:2,'NO':3}

YES出现的概率 Py = 2/5 = 0.4

NO出现的概率Pn = 3/5 = 0.6

通过公式,香农熵 E = -(0.4*log(0.4,2) + 06*log(0.6,2) )=0.9709505944546686


3. 数据集 按照某个特征类别进行划分 成子数据集

def splitDataSet(dataSet,axis,value):   #3个参数分别是[要划分的数据集],[特定特征类别],[特定特征类别的某个取值]
    retDataSet=[]                           #要返回的子数据集
    for featVec in dataSet:                 #依此取出要划分数据集的每组数据
        if featVec[axis]==value:            #若某组数据的[特定特征类别]的取值与 value相等
            reduceFeatVec = featVec[:axis]              #则该组数据舍弃[特定特征类别] 那一列数据
            reduceFeatVec.extend(featVec[axis+1:])      #
            retDataSet.append(reduceFeatVec)            #
    return retDataSet
如要划分的数据集dataSet为:

[1,1,'YES']

[1,1,'YES']

[1,0,'NO'  ]   

[0,1,'NO  ']

[0,1,'NO  ']

而 axi参数 为“是否有脚蹼”(即第二列数据),value参数 为 1,则返回的数据集为:

[1,'YES']

[1,'YES']

[0,'NO']

[0,'NO']


4.找出最大[信息增益] 的特征类别

作用:找出最大[信息增益] 的特征类别后,可以按照该特征类别来调用[上一步的函数splitDataSet] 来对数据集进行划分

def chooseBestFeatureToSplit(dataSet):
    numFeature = len(dataSet[0])-1          #得出数据集的[特征类别个数],-1是因为最后一列是数据的结论
    baseEntropy = calcShannonEnt(dataSet)   #计算当前数据集的信息熵
    bestInfoGain = 0                        #初始信息增益 设为0
    bestFeature = -1                        #信息增益最大的 [特征类别] 初始设为 -1
    for i in range(numFeature):             #遍历各个[特征类别]
        featList=[number[i] for number in dataSet]      #取出序号为i列 的[特征类别]的值
        uniqueVals = set(featList)                      #集合set中的值不能相同
        newEntropy = 0
        for value in uniqueVals:                      #依次用序号为i的[特征类别]的值(value)来划分数据集
            subDataSet = splitDataSet(dataSet,i,value)#返回{用序号为i的[特征类别] 的取值=value}这条件来划分的子数据集
            prob = len(subDataSet)/float(len(dataSet))#{序号为i的[特征类别] 的取值=value}的子数据集的数据占父数据集的比例
            newEntropy +=prob*calcShannonEnt(subDataSet)#各个子数据集的[香农熵*子集合在父集合中的出现几率]之和
        InfoGain = baseEntropy - newEntropy #序号为i的特征类别的[信息增益]
                                            # 为父数据集的香农熵 减去 (以序号为i的特征类别划分的#
                                            #各个子数据集的香农熵之和)

        #最大信息增益
        if(InfoGain > bestInfoGain):    #选出拥有最大[信息增益]的特征类别
            bestInfoGain = InfoGain
            bestFeature = i
    return bestFeature                  #返回拥有最大[信息增益]的特征类别的序号

如要划分的数据集dataSet为:

[1,1,'YES']

[1,1,'YES']

[1,0,'NO'  ]   

[0,1,'NO  ']

[0,1,'NO  ']

该数据的特征类别为 “不浮出水面是否能生存”(第一列) 和 “是否有脚蹼”(第二列),下面分别计算两个特征类别的 [信息增益]:

dataSet的香农熵 E = -(0.4*log(0.4,2) + 06*log(0.6,2) )=0.9709505944546686


第一列特征类别(即 axi = 0):
1.取出第一列的所有值。featList=[1,1,1,0,0]

2.把 1 的数据形成一个set集合,名为uniqueVals = ([1,0])

     2.1.遍历uniqueVals,取出 1(即value = 1)

     2.2.以 dataSet,axi = 0,value = 1 为参数,划分dataSet数据集,返回 subDataSet

           得 subDataSet =  [1,'YES']

                                        [1.'YES']

                                        [0,‘NO’] 

     2.3计算 axi = 0,value=1,在dataSet 中出现的几率

           prop = 3/5 = 0.6

     2.4. axi = 0,value = 1 这个子数据集的香农熵 乘 prop1 =  N1

     2.5.同理,当 value = 0,计算出axi = 0,value=0 这子数据集的香农熵 乘 其prop2 = N2

     2.6.最后 axi = 0的[信息增益] 为  E - (N1+N2)

3.同理也可以计算出第二列特征类别(即axi = 1) 的信息增益

4.比较哪个特征类别的信息增益大,信息增益较大的特征类别意味着对 数据判定起的作用越大。(即该特征类别对 ”是否鱼类“这结论 起到的预测作用越强)



5.创建决策树的树形算法

构造决策树时,会对 [类别]进行遍历,(类别=所有特征类别+最后一列的结论),用到递归的方法,停止递归的条件有两个:

1.输入数据集的最后一列类别 的 值完全相同

         例如有数据:             

                    [1,1,'YES']

                    [1,0,'YES']

                    [0,0,'YES']

          其最后一列都是YES,所以可以返回,返回YES


2.遍历完所有类别

          例如初始有数据集:


注意:剩下的最后一项,一定是 [结论](即 ”是否鱼类“)

代码:

#计算classList中出现最多的特征类别
def majorityCnt(classList):
    classCount={}
    for vote in classList:
        if vote not in classCount.keys():classCount[vote]=0
        classCount[vote]+=1
    sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    return classCount[0][0]

def createTree(dataSet,labels):                     #传入的数据为 dataSet 数据集 和 特征类别 labels
    labels_copy = labels[:]                         #拷贝一份 labels
    classList=[example[-1] for example in dataSet]  #记录dataSet最后一列的数据
    #类别完全相同则停止划分
    if classList.count(classList[-1]) == len(classList):
        return classList[-1]
    #剩余类别个数为1,即遍历完所有类别,返回classList中出现次数最多的结果
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)
    #按照信息增益最高选取分类特征属性
    bestFeat = chooseBestFeatureToSplit(dataSet) #返回[信息增益]最高的特征类别序号
    bestFeatLable = labels_copy[bestFeat] #该特征类别
    myTree = {bestFeatLable:{}}
    del(labels_copy[bestFeat])              #用某个特征类别划分数据集后,把这个特征类别从labels中删除
    featValues = [example[bestFeat] for example in dataSet] #返回dataSet中bestFeat特征的那一列数据
    uniqueVals = set(featValues)
    for value in uniqueVals:
        subLables = labels_copy[:]
        newDataSet = splitDataSet(dataSet,bestFeat,value)   #用[信息增益]最高的特征类别来划分数据集
        myTree[bestFeatLable][value] = createTree(newDataSet,subLables) #递归创建决策树
    return myTree


6.测试代码:

def classify(inputTree,featLables,testVec): #三个参数:决策树,特征类别列表,测试数据
    firstStr = list(inputTree.keys())[0]    #获取树的第一个判断块(即根节点)
    secondDict = inputTree[firstStr]        #第一个判断块的子树
    featIndex = featLables.index(firstStr)
    for key in secondDict.keys():           #key 是数据里的 是或否
        if testVec[featIndex] == key:
            if type(secondDict[key]).__name__== 'dict':                     #若还有dict字典变量(即若还有子树)
                classLable = classify(secondDict[key],featLables,testVec)   #递归,从上往下遍历决策树
            else:classLable = secondDict[key]
    return classLable


dataSet, labels = creatDataSet()    #先获取数据集(实际应用中,可以导入样本数据作为数据集)
tree = createTree(dataSet, labels)  #形成决策树
print(tree)                         #打印决策树
ret = classify(tree,labels,[1,1])   #输入新数据 [1,1] ,看它是否“鱼类”
print(ret)                          #打印数据[1,1]的结果,为YES,即是鱼类
打印出的决策树为:

{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}

即:

与预期一样。











猜你喜欢

转载自blog.csdn.net/u014453898/article/details/79521427