决策树

  • 一 、概述
  • 二、决策树的准备工作
  • 2 特征选择
  • 2.1香农熵
  • 2.2信息增益
  • 2.3数据集的最佳切分方式
  • 2.4按照给定列切分数据集
  • 三、递归构建决策树
  • 四、决策树的存储
  • 五、决策树分类效果


一 、概述

决策树:有监督学习的一种算法,并且是一种基本的分类与回归的方法。
决策树分为分类树和回归树,本章主要是分类树。

二、决策树的准备工作

决策树的构建分为三个过程:特征选择、决策树的生成、决策树的剪枝

1 原理: 由根节点到叶节点构成一条规则,中间结点的特征对应着规则的条件,叶结点的特征(类标签)对应着结论。决策树路径互斥且完备,即每一个实例被有且仅有一条路径或规则覆盖(实例满足规则的条件)。

2 特征选择

目的: 选取对训练数据具有分类能力的特征。
随着划分的进行,我们希望决策树的分支节点所包含的样本尽可能属于同一类别,也就是节点的纯度越来越高。

2.1香农熵

计算所有类别所有可能值包含的信息期望值:分类决策树 python实现 分类树和决策树_决策树

:Ent(D)值越高,D的不纯度越高,混合数据越多。

'''
计算香农熵
'''
def calEnt(dataSet):
    n = dataSet.shape[0]
    iset = dataSet.iloc[:,-1].value_counts()  #标记类别
    p = iset/n                   #标签类别比
    ent = (-p*np.log2(p)).sum()  #信息熵
    return ent
2.2信息增益

信息增益是父节点的信息熵与其下修正后的所有子节点总信息熵之差

假设属性a有V个可能的值{分类决策树 python实现 分类树和决策树_机器学习_02},若使用a对样本数据集D进行划分,则会产生V个分支节点,其中第V个分支节点包含了D中所有在属性上取值为分类决策树 python实现 分类树和决策树_分类决策树 python实现_03的样本,记为分类决策树 python实现 分类树和决策树_决策树_04.考虑到不同的分支节点包含的样本数不同,给分支节点赋予权重,即可修正。

分类决策树 python实现 分类树和决策树_数据集_05

分类决策树 python实现 分类树和决策树_python_06

例:

分类决策树 python实现 分类树和决策树_数据集_07

分类决策树 python实现 分类树和决策树_决策树_08

2.3数据集的最佳切分方式

划分数据集的最大准则是选择最大信息增益,也就是信息下降最快的方向。

'''
根据信息增益选择最佳数据集切分列
'''
def bestSplit(dataSet):
    baseEnt = calEnt(dataSet)                            # 计算原始熵
    bestGain = 0                                         # 初始化信息增益 
    axis = -1                                            # 初始化最佳切分列,此处是指标签列
    for i in range(dataSet.shape[1]-1):                  #对特征的每一列进行循环
        levels = dataSet.iloc[:,i].value_counts().index  #提取当前列的所有取值
        ents = 0                                         #初始化子节点的信息熵
        for j in levels:                                 #对当前列的每一个取值进行循环
            childSet = dataSet[dataSet.iloc[:,i]==j]     #某一个子节点的dataframe
            ent = calEnt(childSet)                       #计算某一个子节点的信息熵
            ents = ents+(childSet.shape[0]/dataSet.shape[0])*ent   #计算当前列的信息熵(修正的信息熵,权重不同)
            print(f'第{i}列的信息熵为{ents}')
            infoGain = baseEnt-ents                      #计算当前列的信息增益(原始信息熵 - 当前列的信息熵)
            print(f'第{i}列的信息增益为{infoGain}')
           
        if (infoGain > bestGain):                       #选择最大信息增益
                bestGain = infoGain
                axis=i
    return axis                                         #返回最佳切分列的索引
2.4按照给定列切分数据集

通过最佳切分列函数返回最佳切分列的索引,根据索引切分数据集。

'''
按照给定的列划分数据集
	dataSet:原始数据集
	axis:指定的列索引
	value:指定的属性值
	redataSet:按照指定列索引和属性值切分后的数据集
'''
def mySplit(dataSet, axis,value):
    col = dataSet.columns[axis]                    #把第axis列切分出来
    redataSet = dataSet.loc[dataSet[col]==value,:].drop(col,axis=1)  #切分完后,对col列进行删除
    return redataSet

三、递归构建决策树

1 工作原理:先得到数据集dataSet,基于最好的特征来划分数据集,由于特征值可能有多于两个,可能有多余两个的分支数据集来进行划分,第一次划分之后数据集被向下传递到树的分支的下一个节点,可以再次划分数据。
2 ID3算法
ID3算法的核心是在决策树各个节点上对应信息增益准则选择特征,递归的构建决策树。
具体原理: 从根节点开始,对节点计算所有可能的特征的信息增益,选择信息增益最大的特征作为节点的特征,由该特征的不同取值建立子节点,在对子节点递归的调用以上方法,构建决策树,直到所有特征的信息增益均很小或没有特征可以选择位置,得到最后一个决策树。
递归的结束条件: 遍历完所有特征列 或者 每个分支下的所有实例都具有相同的分类,也就是得到一个叶节点===》使其标签尽可能的纯,尽可能为一个类标签。

#建决策树
def createTree(dataSet):
    featlist = list(dataSet.columns)                 #提取出数据集的所有列
    classlist = dataSet.iloc[:,-1].value_counts()      #获取最后一列类标签(标签列)
    # 判断最多标签数目是否等于数据集行数(叶节点中只有一类标签),或者数据集是否只有一列(没有特征可选,只能选取这一列)
    if classlist[0]==dataSet.shape[0] or dataSet.shape[1]==1:
        return classlist.index[0]                      #若是,返回类标签
    axis = bestSplit(dataSet)                          #确定当前最佳切分列的索引
    bestfeat = featlist[axis]                          #获取该索引对应的特征
    myTree = {bestfeat:{}}                                #存储树的信息
    del featlist[axis]                              #用完删除当前特征
    valuelist = set(dataSet.iloc[:,axis])           #提取最佳切分列所有属性值
    for value in valuelist:                           #递归建树
        myTree[bestfeat][value] = createTree(mySplit(dataSet,axis,value))
    return myTree

四、决策树的存储

#树的存储
np.save('myTree.npy',myTree)
#树的获取
read_myTree = np.load('myTree.npy').item()
read_myTree

五、决策树分类效果

#对一条测试数据的函数进行分类
#inputTree: 已经建好的决策树
#labels:   存储选择的最优列标签 eg:labels=['no surfacing','flippers','fish]
#testvec: 测试数据列表,顺序对应原数据集(一个测试样本)
def classify(inputTree,labels,testVec):
    firstStr = next(iter(inputTree))            #获取决策树第一个节点(根节点)
    secondDict = inputTree[firstStr]            #提取下一个字典
    featIndex = labels.index(firstStr)          #找到第一个节点所在列的索引
    for key in secondDict.keys():               #对第二个字典的key进行循环
        if testVec[featIndex] == key:            #若测试集的第一列==key
            if type(secondDict[key]) == dict:    #判断是否为字典类型,若是字典类型,未划分到最后,继续进行递归划分;若不是字典类型,则达到了叶节点,直接返回分类的标签
                classlabel = classify(secondDict[key],labels,testVec)
            else:
                classlabel = secondDict[key]
    return classlabel
#对所有测试集的预测函数进行测试
def acc_classify(train,test):                           #输入测试集和训练集
    inputTree = createTree(train)                      # 根据训练集生成一棵树
    labels = list(train.columns)                       # 提取数据集所有列标签
    result = []
    for i in range(test.shape[0]):                   # 对测试集的每一条数据进行循环(分类返回类标签)
    	testVec = test.iloc[i,:-1]                     #测试集中的一个实例
        classLabel = classify(inputTree,labels,testVec)#预测该实例的分类
        result.append(classLabel)                      #将类标签追加到result中 
    test['predict'] = result                           #result的长度和测试集的长度应该一致,追加到测试集后面,命名为predict
    acc = (test.iloc[:,-1]==test.iloc[:,-2]).mean()    #预测标签==原来标签.mean()  计算准确率
    print(f'模型预测准确率为{acc}')
    return test