1、介绍

决策树(decision tree),每个分支都是需要通过条件判断进行划分的树,解决分类和回归问题的方法。

  • 策略
    正则化的极大似然函数。
    此外,从所有可能的决策树选取最优决策树是NP完全问题,实际中学习算法采用启发式方法,近似解决最优化问题。
  • 学习算法三要素:
    特征选择、决策树生成、剪枝。

决策树可看作 if-then 规则的集合。把决策树看作对特征空间的划分,那么它表示了给定特征条件下类的条件概率分布。

2、特征选择

特征选择的准则是信息增益或是信息增益比。

2.1 概念

几个术语:熵、条件熵、信息增益、信息增益比。
(entropy):随机变量不确定性度量,熵越大,不确定性越大。
随机变量概率分布:
决策树结果解读python 决策树算法 python_python
决策树结果解读python 决策树算法 python_算法_02
定义 决策树结果解读python 决策树算法 python_算法_03

补充:
交叉熵衡量两个分布的差异信息。当两个分布相同时,相对熵为0,交叉熵最小。这也是交叉熵用于分类模型损失函数的原理。

条件熵
已知一个随机变量下,另一个随机变量的不确定性。
决策树结果解读python 决策树算法 python_算法_04

注:如果熵和条件熵是由数据估计(特别是极大似然估计法)得到时,对应的熵和条件熵分别为经验熵(empirical entropy)和经验条件熵(empirical conditional entropy).

信息增益
特征 A 对数据集 D 的信息增益 g(D, A),定义为
决策树结果解读python 决策树算法 python_信息增益_05

其中,经验熵计算为:
决策树结果解读python 决策树算法 python_决策树结果解读python_06

其中,决策树结果解读python 决策树算法 python_决策树_07 代表类决策树结果解读python 决策树算法 python_算法_08.
条件熵计算为:
决策树结果解读python 决策树算法 python_python_09

其中,决策树结果解读python 决策树算法 python_python_10是根据特征A的取值划分的子集。
一般,类熵决策树结果解读python 决策树算法 python_决策树_11与特征下类条件熵决策树结果解读python 决策树算法 python_决策树_12之差称为互信息。决策树中的信息增益等价于类与特征的互信息。

先来说说优点:
1.信息增益考虑了特征出现与不出现的两种情况,比较全面,一般而言效果不错。
2.使用了所有样例的统计属性,减小了对噪声的敏感度。
3.容易理解,计算简单。

主要的缺陷:
1.信息增益考察的是特征对整个系统的贡献,没有到具体的类别上,所以一般只能用来做全局的特征选择,而没法针对单个类别做特征选择。
2.只能处理连续型的属性值,没法处理连续值的特征。
3.算法天生偏向选择分支多的属性,容易导致overfitting。

信息增益比

决策树结果解读python 决策树算法 python_算法_13


基尼指数

决策树结果解读python 决策树算法 python_决策树结果解读python_14


决策树结果解读python 决策树算法 python_算法_15

gini越大,数据D不确定性越大。

信息增益和基尼系数的区别:
信息增益是衡量样本纯度的指标;基尼系数是衡量不纯度的指标。

3、决策树生成

常用决策树生成算法:ID3、C4.5、CART。

3.1 ID3算法

使用信息增益进行决策树的生成。

ID3 算法

输入:训练集D,特征集A,阈值决策树结果解读python 决策树算法 python_python_16

(1)计算A中各特征对D的信息增益,选择最大的特征决策树结果解读python 决策树算法 python_决策树结果解读python_17

优点:
1.可能出现每个分支节点只包含一个样本;
缺点:
1.不具备泛化性;
2.容易产生过拟合可取值数据较多的属性(ID3只有生成,没有剪枝);
3.不能处理连续数值型特征;

3.2 C4.5算法

采用信息增益比作为决策树划分的判断,选择信息增益比最大的分裂点作为该特征的最佳分裂点。

C4.5处理连续数值型特征的方法:
先把连续属性转换成离散属性。把特征排序,按照大于、小于等于某个值进行划分到左树、右树,然后,计算这些情况下最大的信息增益率。
此外,C4.5算法倾向于选择连续特征作为最佳树分裂点,具体方法是,连续特征最佳分裂点的信息增益减去 决策树结果解读python 决策树算法 python_python_18

  • 优点:
    1.偏向取值较少的特征;
    2.可以处理连续数值型特征;
  • 缺点:
    1.只能分类;
3.3 CART算法

CART,classification and regression tree,采用基尼指数作为划分判断的二叉树

特点:
1.可以用来分类和回归;
2.生成的决策树为二叉树,而ID3,C4.5允许决策树为多分支的。

回归树的生成

在训练集的某个特征进行划分成两个数据集,分别计算两个数据集与相应输出平均值差的平方和(均方差),取使得数据集均方差最小的分割点,并且以分割后样本的平均目标值作为预测值;重复这一过程,完成回归树的生成。

分类树的生成

算法:
1)分别计算各个特征的基尼指数;
2)基尼指数最小的选为最佳特征,如min{a1,a2,a3,b1,b2}总选择a1,并且以该类特征中,如a类,最小的作为子节点min{a1,a2,a3},进行展开.
3)重复1、2步骤。

总而言,决策树生成步骤为:
生成步骤
1.将所有特征看成一个一个节点;
2.遍历每个特征的每一种分割方式,找出最好的分割点;将数据划成不同的子节点;计算划分后所有节点的信息不确定性;
3.根据2计算结果,选择最优的划分方式,得出最终的子节点N1,N2,…,Nm;
4.对子节点分别继续执行2-3步,直到最终的子节点满足要求。

5、决策树的剪枝

目的:防止过拟合;
实现:采用极小化决策树的代价函数;
计算每个节点及其父节点的损失函数,若该节点的损失函数比父节点的损失函数还大(包括等于),那么把这个节点剪掉。

CART除了生成决策树,也可以用于剪枝:
CART剪枝
算法:
1)初始值α=+∞,k=0,T=T0;
2)从底到顶,分别计算各个节点的g(t)(含义为损失函数)
3)损失函数较大的,剪掉,得到修建后的Tk;
4)重复2、3步骤;
5)使用交叉验证,从Tk,k=0,1,2,n,中选出最优子树Tα。

6.决策树细节

决策树如何处理缺失值?

决策树结果解读python 决策树算法 python_决策树_19


决策树结果解读python 决策树算法 python_决策树结果解读python_20

  • 1.当决策树开始决定选择哪个属性来进行分支时:
    比如这里,选择色泽,还是根蒂还是其他。
    决策时会先忽略这些缺失值,计算信息增益,然后给信息增益/信息增益率 乘于 决策树结果解读python 决策树算法 python_算法_21, 这里的决策树结果解读python 决策树算法 python_算法_21 是 非缺失值个数/总样本数。
    然后,我们计算出分支属性。
  • 2.当选择分支属性(比如色泽)后,如何确定分支属性缺失值样本处于哪个分支:
    把这些缺失值样本都按权值赋予各个分支,这里的权值是根据各分支样本所占的比例,比如色泽乌黑占6/14,那么缺失值1、5、13就给予6/14的权值划分到色泽乌黑分支。其他非缺失值样本的权值都是1。
  • 3.决策树生成后,如何推理待预测样本?
    这个比较复杂,C4.5的处理方法是,缺失值样本会按照一定权值进入各个分支,这个权值也是按照先验概率(该分支样本数/总样本数),然后计算样本属于各个类别的概率,选择概率最大的类别当做样本的类别。

当然,上面处理方法不是唯一的,决策数有很多种处理缺失值的方法,比如 ,忽略缺失值样本,填充缺失值(均值,众数等)等。

决策树最耗时的步骤:
确定最佳分割点,这个步骤对特征的值进行排序。
针对此,xgboost和lightgbm都进行改进。

决策树选择特征进行分类时,一个特征被选择过后,是否还会选择这个特征?
会,比如特征是离散特征,会对分类的子空间继续划分;若为连续特征,则可能在决策树多次被选择。

决策树的特点:

  • 优点:
    1.可解释性强;
  • 缺点:
    1.容易过拟合;
    2.不适合处理高维数据;
    3.对异常值敏感;
    4.泛化能力(generalization)差,没出现过的值几乎处理不了。

7、决策树可视化

#python实现决策树2
from math import log
import operator

def calShannonEnt(dataSet):
    numEntries = len(dataSet)
    labelCounts = {}
    for featVect in dataSet:
        currentLabel = featVect[-1]
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
    shannonEnt = 0.0
    for key in labelCounts:
        prob = labelCounts[key] / numEntries
        shannonEnt -= prob * log(prob, 2)
    return  shannonEnt
    
def splitDataSet(dataSet, axis, value):
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet
    
def createDataSet():
    dataSet = [['youth', 'no', 'no', 'just so-so', 'no'],
               ['youth', 'no', 'no', 'good', 'no'],
               ['youth', 'yes', 'no', 'good', 'yes'],
               ['youth', 'yes', 'yes', 'just so-so', 'yes'],
               ['youth', 'no', 'no', 'just so-so', 'no'],
               ['midlife', 'no', 'no', 'just so-so', 'no'],
               ['midlife', 'no', 'no', 'good', 'no'],
               ['midlife', 'yes', 'yes', 'good', 'yes'],
               ['midlife', 'no', 'yes', 'great', 'yes'],
               ['midlife', 'no', 'yes', 'great', 'yes'],
               ['geriatric', 'no', 'yes', 'great', 'yes'],
               ['geriatric', 'no', 'yes', 'good', 'yes'],
               ['geriatric', 'yes', 'no', 'good', 'yes'],
               ['geriatric', 'yes', 'no', 'great', 'yes'],
               ['geriatric', 'no', 'no', 'just so-so', 'no']]
    labels = ['age', 'work', 'house', 'credit']
    return dataSet, labels
    
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1
    baseEntropy = calShannonEnt(dataSet)
    bestInfoGain = 0.0
    bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueValue = set(featList)
        newEntropy = 0.0
        for value in uniqueValue:
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet) / len(dataSet)
            newEntropy += prob * calShannonEnt(subDataSet)
        infoGain = baseEntropy - newEntropy
        if infoGain > bestInfoGain:
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature
    
    
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]
    
    
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)
    bestFeat = chooseBestFeatureToSplit(dataSet)
    bestFeatLabel = labels[bestFeat]
    myTree = {bestFeatLabel: {}}
    del(labels[bestFeat])
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        subLabels = labels[:]
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
    return myTree
    
    
    
myDat, labels = createDataSet()
myTree = createTree(myDat, labels)
print(myTree)

结果:

{'house': {'yes': 'yes', 'no': 
{'work': {'yes': 'yes', 'no': 'no'}}}}

决策树结果解读python 决策树算法 python_决策树结果解读python_23


参考:

  1. 决策树算法的python实现
  2. GitHub_decision tree
  3. 决策树_码农场;
  4. 决策树可视化实现;
  5. zhihu 如何通俗的解释交叉熵与相对熵?;
  6. cart算法为什么选用gini指数?