https://www.ibm.com/developerworks/cn/analytics/library/machine-learning-hands-on5-cart-tree/index.html?ca=drs-

CART 树简介

在上一篇文章中,主要介绍了 ID3 和 C4.5 决策树。它们利用信息增益和信息增益比划分数据集。但是这两种决策树是有缺陷的,即按某特征划分后,该特征将不会在后面的划分中出现。这就导致了划分过于迅速,从而影响分类结果。在这篇文章中将要介绍的 CART(Classification And Regression Tree)树,即分类回归树利用二分策略,有效地避免了划分过于迅速这一问题。而且二分策略可以直接处理连续型属性值。

CART 树(分类回归树)分为分类树和回归树。顾名思义,分类树用于处理分类问题;回归树用来处理回归问题。我们知道分类和回归是机器学习领域两个重要的方向。分类问题输出特征向量对应的分类结果,回归问题输出特征向量对应的预测值。

分类树和 ID3、C4.5 决策树相似,都用来处理分类问题。不同之处是划分方法。分类树利用基尼指数进行二分。如图 1 所示就是一个分类树。

图 1. 分类树示例

回归树用来处理回归问题。回归将已知数据进行拟合,对于目标变量未知的数据可以预测目标变量的值。如图 2 所示就是一个回归树,其中 s 是切分点,x 是特征,y 是目标变量。可以看出图 2 利用切分点 s 将特征空间进行划分,y 是在划分单元上的输出值。回归树的关键是如何选择切分点、如何利用切分点划分数据集、如何预测 y 的取值。

图 2. 回归树示例

CART 树原理

分类树

二分

分类树利用二分划分数据。将特征值等于切分点值的数据划分为左子树,将特征值不等于切分点值的数据划分为右子树。

基尼指数:同信息增益、信息增益比作用类似,不过基尼指数相对更快

假设有 N 个类,样本属于第 n 类的概率为 Pn,则基尼指数为:

若数据集按特征 A 取值是否等于切分点值划分为 D1 和 D2 两部分,则在特征 A 下,集合 D 的基尼指数为:

回归树

二分

回归树也利用二分划分数据。与分类树不同的是,回归树将特征值大于切分点值的数据划分为左子树,将特征值小于等于切分点值的数据划分为右子树。

平方误差

不同于分类树,回归树用平方误差选择切分点。若数据集按特征取值是否大于切分点值划分为两部分,则在特征 A 下,集合 D 的平方误差为:

用 CART 树进行分类和回归

本节主要用示例数据详细说明如何用 CART 树进行分类和回归。

分类树

表 1. 示例数据集
圆的 红的 分类
1 1 1
1 0 0
0 1 0
0 0 0
1 0 0

选择最优特征

  1. 按特征"圆的" = 1 划分数据集,则 Gini 为:

3/5 * Gini(D1) + 2/5 * Gini(D0)

= 3/5 * [1/3 * 2/3 + 2/3 * 1/3] + 2/5 * [0]

= 0.266

  1. 按特征"红的" = 1 划分数据集,则 Gini 为:

2/5 * Gini(D1) + 3/5 * Gini(D0)

= 2/5 * [1/2 * 1/2 + 1/2 * 1/2] + 3/5 * [0]

= 0.2

综上所述,由于按特征"红的"比特征"圆的"划分的基尼指数小,所以特征"红的" = 1 为切分点。

按最优特征划分数据集

按特征"红的"划分数据集后,有两种情况,第一种为如果是红的:0,则分类:0; 第二种为如果是红的:1, 则有如下数据子集 {圆的:1,分类:1; 圆的:0, 分类:0}

接下来需要对数据子集{圆的:1,分类:1; 圆的:0, 分类:0}继续划分。由于剩下一个特征,故按特征"圆的"划分数据集。划分后,如果是圆的:1,则分类:1;如果是圆的:0, 则分类:0。

返回的决策树为:

{'红的': {0: '类别 0', 1: {'圆的': {0: '类别 0', 1: '类别 1'}}}}

回归树

表 2. 示例数据集
面积/平米 价格/万
20 40.1
21 40.3
35 70.4
36 70.2

选择最优特征

1.按特征"面积" = 20 划分数据集,

y1 均值为 40.1,

y2 均值为(40.3 + 70.4 + 70.2) / 3 = 60.3,

则平方误差为:

0 + (40.3 – 60.3)2 + (70.4 – 60.3)2 +(70.2 – 60.3)2

= 600.02

2.按特征"面积" = 21 划分数据集,则平方误差为:

y1 均值为(40.1 + 40.3)/ 2 = 40.2,

y2 均值为(70.4 + 70.2) / 2 = 70.3,

则平方误差为:

(40.1 –40.2)2 + (40.3 –40.2)2 + (70.4 –70.3)2 +(70.2 –70.3)2

= 0.04

3.按特征"面积" = 35 划分数据集,则平方误差为:

y1 均值为(40.1 + 40.3 + 70.4) / 3 = 50.27,

y2 均值为 70.2,

则平方误差为:

(40.1 –50.27)2 + (40.3 –50.27)2 +(70.4 –50.27)2 + 0

= 608.05

综上所述,由于按特征"面积" = 21 比特征"面积" = 20、"面积" = 35 划分的平方误差小,所以特征"面积" = 21 为切分点。

按最优特征划分数据集

以特征"面积" = 21 为切分点,将数据切分为{面积 = 20,价格 = 40.1; 面积 = 21, 价格 = 40.3}, {面积 = 35,价格 = 70.4; 面积 = 36, 价格 = 70.2}两个子集。

其中子集{面积 = 20,价格 = 40.1; 面积 = 21, 价格 = 40.3}的目标变量非常接近,故不继续划分,得叶节点值(40.1 + 40.3) / 2 = 40.2; 同理得子集{面积 = 35,价格 = 70.4; 面积 = 36, 价格 = 70.2}的叶节点值为 (70.4 + 70.2) / 2 = 70.3。

实现步骤: 自己动手实现 CART 树

本节将介绍分类树和回归树的代码实现细节。两者都是二分类划分数据。分类树利用基尼指数选择最优特征和最优特征对应的划分属性值。回归树利用平方误差选择最优特征和最优特征对应的划分属性值。

分类树

读者应该还记得上篇介绍的 ID3 和 C4.5 决策树,它们利用信息增益和信息增益比划分数据,且创建出来的树可以有任意多的分支数,分支数取决于特征值的多少。本节介绍的分类树与前两种决策树不同之处在于,它是二分类,即创建出来的树最多只有两个分支,而且是利用基尼指数划分数据。分类回归树分为两种树,分类树和回归树。本节先介绍分类树,回归树会在下一小节继续介绍。

清单 1. 计算基尼指数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def calcGini(dataSet):
    totalNum = shape(dataSet)[0]
    labelNum = {}
    gini = 0
    for data in dataSet:
        label = data[-1]
        if label in labelNum:
            labelNum[label] += 1
        else:
            labelNum[label] = 1
    for key in labelNum:
        p = labelNum[key] / totalNum
        gini += p * (1 - p)
    return gini

清单 1 介绍了基尼指数的实现代码。基尼指数的计算过程详见本文第二节内容。

清单 2. 选择最优特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def chooseBestFeatVal2Split(dataSet):
    #如果没有可划分的特征或所有目标变量相等,停止
    if(len(dataSet[0]) == 1): return None, None
    if(len(set([d[-1] for d in dataSet])) == 1): return None, None
    bestFeature = 0
    bestValue = 0
    lowestGini = 100000
    totalGini = calcGini(dataSet)
    totalNum = shape(dataSet)[0]
    for feature in range(shape(dataSet)[1] - 1):
        allValues = [d[feature] for d in dataSet]
        values = set(allValues)
        for value in values:
            leftChild, rightChild = splitByFeatVal(feature, value, dataSet)
            if(shape(leftChild)[0] == 0 or shape(rightChild)[0] == 0): continue
            leftNum = shape(leftChild)[0]
            rightNum = shape(rightChild)[0]
            curGini = leftNum / totalNum * calcGini(leftChild) + \
                      rightNum / totalNum * calcGini(rightChild)
            if(curGini < lowestGini):
                bestFeature = feature
                bestValue = value
                lowestGini = curGini
    #如果 gini 减少很小,停止
    if(totalGini - lowestGini < 0.00001): return None, None
    return bestFeature, bestValue

清单 2 介绍了分类树选择最优特征的过程。对所有特征及特征下的所有属性值,计算按其划分的子数据集的基尼指数,选择最小的基尼指数对应的特征和属性值为最优特征及最优特征对应的最优属性。注意分类树的结束条件。如果没有可以划分的特征或者所有目标变量都相等,则停止继续划分数据集;如果基尼指数减小很少,也停止继续划分数据集。

清单 3. 按特征划分数据集

1
2
3
4
5
6
7
def splitByFeatVal(feature, value, dataSet):
    #左子树的值大于根节点的值
    dataSet = mat(dataSet)
    leftChild = dataSet[nonzero(dataSet[:,feature] == value)[0], :].tolist()
    #右子树的值小于等于根节点的值
    rightChild = dataSet[nonzero(dataSet[:,feature] != value)[0], :].tolist()                   
    return leftChild, rightChild

清单 3 用来给分类树划分数据集。如果 feature 对应的属性值等于 value 值,则将该条数据划分到左子树;如果 feature 对应的属性值不等于 value 值,则将该条数据划分到右子树。

清单 4. 结束条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def checkIsOneCateg(newDataSet):
    flag = False
    categoryList = [data[-1] for data in newDataSet]
    category = set(categoryList)
    if(len(category) == 1):
        flag = True
    return flag
def majorityCateg(newDataSet):
    categCount = {}
    categList = [data[-1] for data in newDataSet]
    for c in categList:
        if c not in categCount:
            categCount[c] = 1
        else:
            categCount[c] += 1
    sortedCateg = sorted(categCount.items(), key = lambda x:x[1], reverse = True)
    return sortedCateg[0][0]

清单 4 介绍了分类树用来结束的条件。checkIsOneCateg 函数用来判断数据集的目标变量是否为一个分类结果。majorityCateg 函数用来选出目标变量中的大多数值作为输出变量。

清单 5. 创建分类树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def createClassifTree(dataSet):
    feature, value = chooseBestFeatVal2Split(dataSet)
    #如果无法分割,那么返回叶节点的值。如果业界点钟所有目标变量相同则为此值,不同则为多数值
    if feature == None and checkIsOneCateg(dataSet):
        return dataSet[0][-1]
    if feature == None and not checkIsOneCateg(dataSet):
        return majorityCateg(dataSet)
    #如果可以继续分割,那么继续创建新的子树
    classifTree = {}
    classifTree['featIndex'] = feature
    classifTree['value'] = value
    leftChild, rightChild = splitByFeatVal(feature, value, dataSet)
    classifTree['leftChild'] = createClassifTree(leftChild)
    classifTree['rightChild'] = createClassifTree(rightChild)                   
    return classifTree

清单 5 用来创建分类树。首先判断是否可以继续分割。如果无法分割,则出现叶节点。若所有目标变量值相同,则叶节点值就是此目标变量的值;否则,选出大多数值作为叶节点的值。如果可以继续分割,则创建子树 classifTree。这是一个 python 中的字典结构,存储四个键值对。featIndex 存储特征的下标,value1 存储特征的值;leftChild 存储左子树;rightChild 存储右子树。对左右子树继续递归调用 createClassifTree 函数,直到达到结束条件为止。

回归树

回归树与分类树的不同之处体现在选择最优特征的方法、划分数据集的方法、叶节点取值的方法上。

清单 6. 计算平方误差

1
2
3
def calcErr(dataSetMat):
    error = var(dataSetMat[:,-1]) * shape(dataSetMat)[0]
    return error

清单 6 介绍了平方误差的计算方法。具体方法详见本文第二节内容。

清单 7. 选择最优特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def chooseBestFeatVal2Split(dataSetMat):
    #如果所有目标变量相等,停止
    if(len(set(dataSetMat[:,-1].T.tolist()[0])) == 1): return None, None
    bestFeature = 0
    bestValue = 0
    lowestErr = 100000
    totalErr = calcErr(dataSetMat)
    for feature in range(shape(dataSetMat)[1] - 1):
        allValues = [d[feature] for d in dataSetMat.tolist()]
        values = set(allValues)
        for value in values:
            leftChild, rightChild = splitByFeatVal(feature, value, dataSetMat)
            if(shape(leftChild)[0] == 0 or shape(rightChild)[0] == 0): continue
            curErr = calcErr(leftChild) + calcErr(rightChild)
            if(curErr < lowestErr):
                bestFeature = feature
                bestValue = value
                lowestErr = curErr
    #如果误差减少很小,停止
    if(totalErr - lowestErr < 1): return None, None
    return bestFeature, bestValue

清单 7 用来为回归树选择最优特征。对每个特征的每个属性值,计算按该属性值二分后的两个子数据集的平方误差和,选择平方误差和最小的特征作为最优特征。除了用平方误差代替基尼指数之外,其他过程和分类树基本相同。

清单 8. 按特征划分数据集

1
2
3
4
5
6
def splitByFeatVal(feature, value, dataSetMat):
    #左子树的值大于根节点的值
    leftChild = dataSetMat[nonzero(dataSetMat[:,feature] > value)[0], :]
    #右子树的值小于等于根节点的值
    rightChild = dataSetMat[nonzero(dataSetMat[:,feature] <= value)[0], :]                   
    return leftChild, rightChild

清单 8 介绍了回归树划分数据集的方法。与分类树不同的是,回归树将 feature 属性值大于 value 的数据归入 leftChild 左子树,将 feature 属性值小于等于 value 的数据归入 rightChild 右子树。

清单 9. 创建回归树

1
2
3
4
5
6
7
8
9
10
11
12
def createRegTree(dataSetMat):
    feature, value = chooseBestFeatVal2Split(dataSetMat)
    #如果无法分割,那么返回叶节点的值,即所有 dataSetMat 的均值
    if feature == None: return mean(dataSetMat[:,-1])
    #如果可以继续分割,那么继续创建新的子树
    regTree = {}
    regTree['featIndex'] = feature
    regTree['value'] = value
    leftChild, rightChild = splitByFeatVal(feature, value, dataSetMat)
    regTree['leftChild'] = createRegTree(leftChild)
    regTree['rightChild'] = createRegTree(rightChild)                   
    return regTree

清单 9 是创建回归树的代码。与分类树不同之处在于,如果无法继续划分数据集,那么返回子数据集的所有目标变量的均值作为叶节点的值。其他部分和分类树基本相同。

代码下载

本文所有 CART 树实现代码可在文末下载。

本文数据集简介

图 3. 分类树数据集样例

数据集有 9 条数据。其中第一列数据代表颜色,取值范围是 0,1,2。其中 0 代表青色,1 代表红色,2 代表黄色。第二列数据代表形状,取值范围是 0,1,2。其中 0 代表扁的,1 代表圆的,2 代表椭圆的。第三列代表分类,取值范围是 0,1。其中 0 代表不是苹果,1 代表是苹果。

图 4. 回归树数据集样例

数据集共 25 条数据。第一列数据代表房子的评价得分,此数据集中所有数据的评价得分都是 5.23。第二列数据代表房子的平方数,第三列数据代表房子的价格,以万为单位。

应用示例: 应用实现的 CART 树解决实际问题

清单 10. 用分类树解决实际问题

1
2
3
4
if __name__ == '__main__':
    dataSet, labels = loadDataSet()
    classifTree = createClassifTree(dataSet)
    print(classifTree)

清单 10 列出调用分类树进行建模的代码。

运行结果:

{'value': 1, 'featIndex': 0, 'leftChild': 1, 'rightChild': {'value': 1, 'featIndex': 1, 'leftChild': 1, 'rightChild': 0}}

运行结果中的树用 python 中的字典结构表示。featIndex 表示 feature 的下标,其中 feature 指颜色和形状。value 指 feature 的取值,取值范围是 0, 1, 2。leftChid 表示左子树,左子树是等于父节点值的子集合数据。rightChild 表示右子树,右子树是不等于父节点值的子集合数据。如果左子树和右子树是叶节点,则叶节点的值就是最终的分类结果。其中叶节点的取值范围是 0 和 1。0 代表是苹果,1 代表不是苹果。运行结果含义为:如果颜色是红的,则是苹果;如果颜色不是红的,但形状是圆的,则是苹果;如果颜色不是红的,且形状不是圆的,则不是苹果。该运行结果与图 1 所示分类树相同。

清单 11. 用回归树解决实际问题

1
2
3
4
if __name__ == '__main__':
    dataSetMat = loadDataSet()
    regTree = createRegTree(dataSetMat)
    print(regTree)

清单 11 列出了调用回归树进行建模的代码。

运行结果:

{'featIndex': 1, 'value': 10.0, 'rightChild': {'featIndex': 1, 'value': 5.0, 'rightChild': 0.078, 'leftChild': 5.0700000000000003}, 'leftChild': {'featIndex': 1, 'value': 20.0, 'rightChild': {'featIndex': 1, 'value': 15.0, 'rightChild': 10.27, 'leftChild': 15.266}, 'leftChild': 20.268000000000001}}

运行结果中的树同样用 python 中的字典结构表示。featIndex 和 value 的含义和分类树中的相同。在回归树中,leftChild 代表的左子树的值比父节点值大,rightChild 代表的右节点的值小于等于父节点值。运行结果含义是:第一个特征的值小于 5.0 时,目标变量为 0.078;第一个特征的值大于 5.0 且小于 10.0 时,目标变量为 5.07;第一个特征的值大于 15.0 且小于 20.0 时,目标变量为 15.266;第一个特征的值大于 10.0 且小于 15.0 时,目标变量为 10.27;第一个特征的值大于 20.0 时,目标变量为 20.268。该运行结果与图 2 所示回归树相同。

总结

本文首先介绍了分类回归树及预剪枝、后剪枝的概念,接着从二分、基尼指数、平方误差等入手详细深入地讲解了 CART 树的原理。接着用例子介绍分类树与回归树的算法过程。然后通过代码样例,介绍了自己动手实现分类树与回归树的思路。最后,利用数据展示了如何应用 CART 树解决实际问题。通过上面介绍,读者已经知道回归树用来进行分段回归,每段输出的目标变量为该段所有目标变量的均值。这种方法简单易实现,但是也有局限。如果每段的目标变量并非一个常数,而是一个线性模型,用回归树拟合就会造成误差过大的后果。这时,就需要考虑模型树。同回归树一样,模型树也是对特征空间进行划分,但在每个划分内部建立线性模型,而非一个常数。关于模型树的详细介绍请参考下方所列文献。由于分类回归树对特征空间不断划分,这很容易造成过拟合的问题。处理过拟合不仅需要预剪枝,还需要后剪枝。预剪枝是在建立模型阶段通过制定终止条件来提早完成划分。后剪枝则是在模型建立后,利用测试数据计算剪除部分叶节点是否会造成模型误差减少,如果是,则剪除对应的叶节点。当然,后剪枝时不仅要考虑误差是否减少,还需考虑正则项,即模型的大小。最后综合选择模型误差较小且模型简单的分类回归树。由于篇幅有限,对后剪枝感兴趣的读者请参考下文所列文献。

参考资源

本文用到的参考文献如下:

  • 参考李航著《统计学习方法》,了解后剪枝概念。
  • 参考 Peter Harrington 著《机器学习实战》,了解 CART 树代码框架及模型树概念。

转载于:https://www.cnblogs.com/davidwang456/articles/8927019.html

机器学习系列之手把手教你实现一个分类回归树相关推荐

  1. 机器学习系列之手把手教你实现一个决策树

    https://www.ibm.com/developerworks/cn/analytics/library/machine-learning-hands-on4-decision-tree/ind ...

  2. 机器学习系列之手把手教你实现一个 naiveBayes

    https://www.ibm.com/developerworks/cn/analytics/library/machine-learning-hands-on3-naivebayes/index. ...

  3. 手把手教你写一个生成对抗网络

    成对抗网络代码全解析, 详细代码解析(TensorFlow, numpy, matplotlib, scipy) 那么,什么是 GANs? 用 Ian Goodfellow 自己的话来说: " ...

  4. 【程序员面试系列】手把手教你如何面试,你要的我都有(工作项目篇)

    作者:Dimple Solgan:当你的才华还无法撑起你的野心时候,那应该静下心来好好学习 前面两篇文章的总结,我们学会了面试前简历的准备.技术知识准备和算法题准备.不知道你是否看完了呢,如果没看完的 ...

  5. 手把手教你完成一个数据科学小项目(7):经纬度获取与BDP可视化

    前言 请先阅读"中国年轻人正带领国家走向危机",这锅背是不背? 一文,以对"手把手教你完成一个数据科学小项目"系列有个全局性的了解. 本系列代码统一开源在Git ...

  6. matlab 条形图误差线,数据可视化系列:手把手教你绘制带误差线的条形图

    原标题:数据可视化系列:手把手教你绘制带误差线的条形图 条形图可以用于展示数据不同分类下的均值.中位数.标准差和置信区间等,Excel可以实现,但对于带误差线的条形图而言,还是比较麻烦的.R语言的基础 ...

  7. 手把手教你做一个物联网视频监控项目(三)流媒体方案实现

    往期文章 手把手教你做一个物联网视频监控项目(一) 介绍 手把手教你做一个物联网视频监控项目(二)MJPG-streamer方案实现 文章目录 前言 一.软硬件准备 二.流媒体方案的实现之FFmpeg ...

  8. 手把手教你写一个中文聊天机器人

    本文来自作者 赵英俊(Enjoy) 在 GitChat 上分享 「手把手教你写一个中文聊天机器人」,「阅读原文」查看交流实录. 「文末高能」 编辑 | 哈比 一.前言 发布这篇 Chat 的初衷是想和 ...

  9. 手把手教你完成一个数据科学小项目(9):情感分析与词云

    前言 请先阅读"中国年轻人正带领国家走向危机",这锅背是不背? 一文,以对"手把手教你完成一个数据科学小项目"系列有个全局性的了解. 本系列代码统一开源在Git ...

最新文章

  1. PHP 中 include 和 require 的概要及区别介绍
  2. 亲戚再也看不见我一个人食吉野家了
  3. [codevs 2926] 黑白瓷砖(2002年安徽省队选拔赛)
  4. c 窗体程序 mysql_C\C++开发MySQL程序简介(下)
  5. 机器人辅助的符文天赋_10.5版本T1辅助盘点 进攻型辅助主宰下路
  6. WPF Path.Data 后台代码赋值
  7. jpa 原生sql 查询返回一个实体_JPA查询--使用原生sql 并且把查询结果转为实体对象...
  8. [paper reading] YOLO v1
  9. 以1敌10不是梦,Spring Boot企业级真实应用案例
  10. python批量写入数据库_python实现数据库批量数据插入.
  11. android高通WiFi,高通QCA9377 WiFi蓝牙模块芯片介绍,
  12. IT近年最新技术一览
  13. 修改oracle管理员用户密码
  14. pkg-php,node打包工具Pkg(详细教程)
  15. DevExpress的XtraReport控件相关功能介绍
  16. 关于固态硬盘的一些总结
  17. 湖北省211大学计算机分数线,2017年211高校各省市录取分数线汇总
  18. Charmer--viv【并查集】【最小生成树】
  19. LeetCode--路径总和III
  20. 中专计算机寒假作业,中职计算机寒假作业[优质课资]

热门文章

  1. 计算机科学导论第二章,补基础:自学:计算机科学导论 第二章 数字系统
  2. 大数据数据收集数据困难_大数据架构、大数据开发与数据分析的区别
  3. android android:process=,Android app启动流程
  4. Vivo手机调试 logcat 信息一堆星号问题
  5. java set 包含_关于Java的Set的集合是否包括问题,如下为什么不包括?
  6. matplotlib xticks 基于 旋转_数据可视化之 matplotlib 绘图篇
  7. NLP-基础知识-002 (语言模型)
  8. 推荐系统笔记(简单概念)
  9. web浏览器录音:web audio api
  10. NTU 课程 CE7454:信息论概述