李航《统计学习方法》----决策树--例题代码+ 机器学习实战

决策树=树+决策 也就是先构造一棵树,在这棵树上进行一系列的决策 (是分类算法但也可以做回归)
那他做了什么事呢?请看下图:
在这里插入图片描述
我们的目标是分出图中谁爱篮球。 图中对应有五个样本以及右图中的两个特征(age、male)。这个分类的过程是:对五个样本从上往下走得出来最终的结果(输入样本数等于输出样本数)。因此决策树的分类过程比较简单。但是,我们要怎么构造一棵决策树呢?
再举一个例子:
在这里插入图片描述
假如现在要去相亲,但是相亲之前有一些指标。首先来看,年龄小于30去见,大于30不见;其次长相帅去见,不帅不见;最后收入高见,低不见;那么你为什么要年龄当成第一个节点,长相第二节点,收入第三个节点呢?所以我们在构造一棵树的时候,首先要做的就是决定每个节点的顺序。
在说决策树之前,先解释一个概念——熵
听起来好像很难理解,但是通俗的讲就好理解了。熵在我们高中的化学里面表示的是分子内部的混乱程度,其实也就是表示的当前这个东西纯不纯。
定义:X和Y两个事件相互独立,P(X,Y) = P(X)*P(Y)
H(X),H(Y) 表示事件发生的不确定性。
P(事情发生的概率越小)------> H(x)值越大 如:今天买的彩票中了。
P(事情发生的概率越大)------> H(Y)值越小 如:我们今天要上班。
在这里插入图片描述
在这里插入图片描述
在明白了上面的概念只有,我们就来讲决策树。
构造决策树的基本想法是:随着树深度的增加,节点的熵迅速地降低。熵降低的速度越快越好,这样我们就可以得到一棵高度最矮的决策树。这个怎么理解呢?比如公司让你做一件事,我们如果三步能做完为什么要用五步呢,当然是希望越快做完越好啊。
接下来给出一个例子说明如何构造决策树:
在这里插入图片描述
(图中有14行数据,每个数据4个特征 :天气,温度,湿度,风)
上面我们说过我们可以基于特征划分,现在有四个候选条件,那选择那个节点呢?
首先:我们要先计算在没有给定任何天气信息时,根据历史数据,我们知道新的一天出去约会的概率是9/14,不出去约会的概率是5/14,此时的熵为:
在这里插入图片描述
其次,从候选的划分节点中选择根节点。
对每项指标分别统计:在不同的取值下出去约会和不出去约会的次数。例如我们计算已知变量天气时,信息熵是多少:

  • 天气 = 晴 :2/5的概率出去约会,3/5的概率不出去约会。entropy = 0.971
  • 天气 = 多云 :1的概率去约会,所以 entropy = 0
  • 天气 = 下雨 :3/5的概率出去约会,2/5的概率不出去约会,entropy = 0.971

天气取值为晴、多云、下雨的概率分别是5/14、4/14、5/14
信息熵为:5/14 x 0.971 + 4/14 x 0 + 5/14 x 0.971 = 0.693
这样的话系统的信息熵就从0.940下降到了0.693,信息增益gain(天气) = 0.940 - 0.693 = 0.247
同理可以计算出gain(温度) = 0.029,gain(湿度) = 0.152, gain(风) = 0.048.
gain(天气)最大(即在第一步中是系统的信息熵下降的最快),所以决策树的根节点就取 天气。
那么问题来了,刚刚计算里面提到的信息增益是什么鬼?在定义信息增益之前,还要明确一个概念,条件熵。
上面讲了熵我们知道是什么,那条件熵是个什么东西???条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性,随机变量X给定的条件下随机变量Y的条件熵(conditional entropy) H(Y|X),定义X给定条件下Y的条件概率分布的熵对X的数学期望:
在这里插入图片描述
这里:
在这里插入图片描述
接下来,让我们说说信息增益。其实,相信聪明的你从上面的计算就可以看出,信息增益什么了。它定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即
在这里插入图片描述
信息增益越大越好。越大表示选择当前特征我们信息熵下降的越快,使得样本划分的越好。
根节点选完了,第二个节点选谁呢?跟之前选择根节点的过程是一样的,直到全部选完。
以上的算法就是ID3算法(信息增益)。
但是ID3算法有缺点:
举例:在上面的特征中加入ID特征,根据尝试我们知道,去不去约会,跟id值无关,但是我们在计算的时候也会把它当成一个候选特征。而且通过计算发现,他会使我们的信息增益值最大。
因此,如果当我们的集合中特征属性很多的时候,一些无关的特征会影响我们最后的划分,因此引入了信息增益比(C4.5)
定义:特征A对集合D的信息增益比gr(D,A)定义为其信息增益g(D,A)与训练集D关于特征A的值得熵Ha(D)之比,即:
在这里插入图片描述
其中,
在这里插入图片描述
CART:Gini 系数
在这里插入图片描述
课本例题5.1实战代码(ID3,C45,CART):

"""
函数说明:创建数据集
dataSet:数据集
labels : 标签
"""
from math import log
import operator
def loadDataSet():
    dataSet = [[0, 0, 0, 0, 'no'],   #数据集
            [0, 0, 0, 1, 'no'],
            [0, 1, 0, 1, 'yes'],
            [0, 1, 1, 0, 'yes'],
            [0, 0, 0, 0, 'no'],
            [1, 0, 0, 0, 'no'],
            [1, 0, 0, 1, 'no'],
            [1, 1, 1, 1, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [2, 0, 1, 2, 'yes'],
            [2, 0, 1, 1, 'yes'],
            [2, 1, 0, 1, 'yes'],
            [2, 1, 0, 2, 'yes'],
            [2, 0, 0, 0, 'no']]
    labels = ['年龄', '有工作', '有自己的房子', '信贷情况']	#分类属性
    return dataSet, labels                #返回数据集和分类属性
"""
计算数据集的熵
dataSet:数据集
shannonEnt :熵
"""

def calcShannonEnt(dataSet):
    numEntries = len(dataSet)
    labelCounts = {}
    for feat in dataSet:
        curlabel = feat[-1]
        if curlabel not in labelCounts.keys():
            labelCounts[curlabel] = 0
        labelCounts[curlabel] += 1
    shannonEnt = 0.0
    for key in labelCounts:
        prob = float(labelCounts[key])/numEntries
        shannonEnt -= prob * log(prob,2)
    return shannonEnt


"""
函数说明:按照给定特征划分数据集
dataSet:待划分数据集
axis : 划分数据集的特征
value : 需要返回的特征的值

"""
def splitDataSet(dataSet,axis,value):
    retDataSet = []
    for feat in dataSet:
        if feat[axis] == value:
           reducedFeatVec = feat[:axis] 
           reducedFeatVec.extend(feat[axis+1:])  #这两步合起来相当于去掉了axis特征
           retDataSet.append(reducedFeatVec)
    return retDataSet 

"""
函数说明:ID3、C4.5、CART算法选择最优特征
dataSet:数据集
bestFeature:信息增益最大的特征的索引值
"""

def ID3_chooseBestFeatureToSplit(dataSet):
    numFeature = len(dataSet[0])-1   #特征的数量
    Entropy = calcShannonEnt(dataSet) #数据集的熵
    bestInfoGain = 0.0
    bestFeature = -1
    for i in range(numFeature):
        featList = [example[i] for example in dataSet]   #获取第i个特征的所有取值
        uniqueVals = set(featList) 
        newEntropy = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet,i,value)    
            prob = len(subDataSet)/float(len(dataSet))    #计算子集的概率
            newEntropy += prob*calcShannonEnt(subDataSet)  #根据公式计算经验熵
        infoGain = Entropy - newEntropy                   #计算信息增益
        print(u"ID3中第%d个特征的信息增益率为:%.3f"%(i,infoGain))
        if (infoGain > bestInfoGain):                             #计算信息增益
            bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益
            bestFeature = i                                     #记录信息增益最大的特征的索引值
    return bestFeature 

def C45_chooseBestFeatureToSplit(dataSet):
    numFeature = len(dataSet[0])-1   #特征的数量
    Entropy = calcShannonEnt(dataSet) #数据集的熵
    bestInfoGain_ratio = 0.0
    bestFeature = -1
    for i in range(numFeature):
        featList = [example[i] for example in dataSet]   #获取第i个特征的所有取值
        uniqueVals = set(featList) 
        newEntropy = 0.0
        iv = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet,i,value)    
            prob = len(subDataSet)/float(len(dataSet))    #计算子集的概率
            newEntropy += prob*calcShannonEnt(subDataSet)  #根据公式计算经验熵
            iv = iv - prob*log(prob,2)
        infoGain = Entropy - newEntropy                   #计算信息增益
        if iv == 0:
            continue       
        infoGain_ratio = infoGain / iv        #计算信息增益率
        print(u"C4.5中第%d个特征的信息增益率为:%.3f"%(i,infoGain_ratio))
        if (infoGain_ratio > bestInfoGain_ratio):                             
            bestInfoGain_ratio = infoGain                             #更新信息增益率,找到最大的信息增益率
            bestFeature = i                                     #记录信息增益率最大的特征的索引值
    return bestFeature

def CART_chooseBestFeatureToSplit(dataSet):
    numFeature = len(dataSet[0])-1   #特征的数量
    bestGini = 999999.0
    bestFeature = -1
    for i in range(numFeature):
        featList = [example[i] for example in dataSet]   #获取第i个特征的所有取值
        uniqueVals = set(featList)
        gini = 0.0
        for value in uniqueVals:
            subdataset = splitDataSet(dataSet,i,value)
            p = len(subdataset)/float(len(dataSet))
            subp = len(splitDataSet(subdataset,-1,'no'))/float(len(subdataset))
        gini += p*(1.0-pow(subp,2)-pow(1-subp,2))  #G(D,A)
        print(u"CART中第%d个特征的基尼值为:%.3f"%(i,gini))
        if gini < bestGini:
            bestGini = gini
            bestFeature = i
    return bestFeature

"""
函数说明:统计出现次数最多的类标签
classList : 类标签列表
"""
def majorityCnt(classList):
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 0
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True) #根据字典的值降序排序
    return sortedClassCount[0][0]
    
"""
函数说明:创建决策树
dataSet:训练数据集
labels :标签
featLabels :最优的特征标签

"""

  
def ID3_createTree(dataSet,labels,featLabels):
    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)
    bestFeat = ID3_chooseBestFeatureToSplit(dataSet) 
    bestFeatLabel = labels[bestFeat]   #最优特征对应的标签
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel:{}}
    del(labels[bestFeat])   #加入树中的特征删除
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        myTree[bestFeatLabel][value] = ID3_createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree
def C45_createTree(dataSet,labels,featLabels):
    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)
    bestFeat = C45_chooseBestFeatureToSplit(dataSet) 
    bestFeatLabel = labels[bestFeat]   #最优特征对应的标签
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel:{}}
    del(labels[bestFeat])   #加入树中的特征删除
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        myTree[bestFeatLabel][value] = C45_createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree

def CART_createTree(dataSet,labels,featLabels):
    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)
    bestFeat = CART_chooseBestFeatureToSplit(dataSet) 
    bestFeatLabel = labels[bestFeat]   #最优特征对应的标签
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel:{}}
    del(labels[bestFeat])   #加入树中的特征删除
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        myTree[bestFeatLabel][value] = CART_createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree
  """
函数说明:对每一个测试数据分类
inputTree:决策树
featLabels:分类标签
testVec:每一个测试数据
 返回    :决策结果
    """
def classify(inputTree,featLabels,testVec):
  
    firstStr = list(inputTree.keys())[0]   #决策树的根节点
    secondDict = inputTree[firstStr]       #根节点下面的树
    featIndex = featLabels.index(firstStr)  #根节点对应数据的下标
    classLabel = 'no'
    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
"""
函数说明:对测试数据分类
inputTree:决策树
featLabels:分类标签
testDataSet:测试数据集
 返回    :决策结果
  
    """
def classifytest(inputTree, featLabels, testDataSet):
    classLabelAll = []
    for testVec in testDataSet:
        classLabelAll.append(classify(inputTree, featLabels, testVec))
    return classLabelAll
    

if __name__=="__main__":
    dataSet,features = loadDataSet()
    #print(dataSet)
    #print(calcShannonEnt(dataSet))
    test_data = [[0, 0, 0, 1],   #测试数据集
            [0, 1, 0, 1],
            [1, 0, 1, 2],
            [1, 0, 0, 1],
            [2, 1, 0, 2],
            [2, 0, 0, 0],
            [2, 0, 0, 2]]
   
    
        
    while(True):
        dec_Tree = str(input("请选择决策树:->(1:ID3; 2:C4.5; 3:CART)|('enter q to quit!')|:"))
        #ID3
        if dec_Tree == '1':
           featLabels = [] 
           labels_tmp = features[:] # 拷贝,createTree会改变labels
           ID3desicionTree = ID3_createTree(dataSet,labels_tmp,featLabels)
           print('ID3desicionTree:\n', ID3desicionTree) 
           testSet = test_data
           print("下面为测试数据集结果:")
           print('ID3_TestSet_classifyResult:\n', classifytest(ID3desicionTree, features, testSet))
           print("---------------------------------------------")
        #C4.5决策树
        if dec_Tree =='2':
            featLabels = []
            labels_tmp = features[:] # 拷贝,createTree会改变labels
            C45desicionTree =C45_createTree(dataSet,labels_tmp,featLabels)
            print('C45desicionTree:\n', C45desicionTree)
            
            testSet = test_data
            print("下面为测试数据集结果:")
            print('C4.5_TestSet_classifyResult:\n', classifytest(C45desicionTree, features, testSet))
            print("---------------------------------------------")
        
        #CART决策树
        if dec_Tree =='3':
            featLabels = [] 
            labels_tmp = features[:] # 拷贝,createTree会改变labels        
            CARTdesicionTree = CART_createTree(dataSet,labels_tmp,featLabels)
            print('CARTdesicionTree:\n', CARTdesicionTree)
            
            testSet = test_data
            print("下面为测试数据集结果:")
            print('CART_TestSet_classifyResult:\n', classifytest(CARTdesicionTree, features, testSet))
        if dec_Tree=='q':
            break

结果:

ID3desicionTree:
 {'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
ID3_TestSet_classifyResult:
 ['no', 'yes', 'yes', 'no', 'yes', 'no', 'no']
C45desicionTree:
 {'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
下面为测试数据集结果:
C4.5_TestSet_classifyResult:
 ['no', 'yes', 'yes', 'no', 'yes', 'no', 'no']
CARTdesicionTree:
 {'有工作': {0: {'有自己的房子': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
下面为测试数据集结果:
CART_TestSet_classifyResult:
 ['no', 'yes', 'yes', 'no', 'yes', 'no', 'no'] 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值