树回归CART(Classification And Regression Tree)(1)

前面我们介绍了线性回归和加权线性回归,但是线性回归创建模型需要拟合所有的样本(对样本中所有的点都计算预测值和真实值的误差,并求出最小的回归系数)。当数据过多或者特征之间的关系十分复杂时,构建全局模型就比较困难,并且在实际的生活中很多问题都是非线性的,全局线性模型去拟合数据显然是不可能的。
一个可行的办法:分割原始数据集,使数据集分为多个易建模的数据,然后使用线性回归来建模。所以这里介绍一种树结构算法 —- CART

我们之前介绍过决策树:不断将数据且分为小数据集,直到所有的目标变量完全相同或者不能再切分为止。(属于贪心算法,不关心是否全局最优)
决策树构建算法核心是ID3,每次选取当前最佳的特征来分割数据,并且按照该特征所有的可能的取值来划分。并且ID3不能处理连续型特征,一般都是是先将连续特征离散化,然后再用决策树处理。
而CART是一种二元切分法,每次把数据集分为两份,如果数据的某个特征值等于切分所要求的值,那么这些数据进入树的左子树,反之进入右子树。CART也易于在树的构建过程中进行调整以处理连续型特征。

连续和离散型型特征的树的构建
CART的构建过程中,类似于决策树,我们采用字典来存储多种类型数据,包括:
a 待切分的特征 b 待切分的特征值 c 左子树(当不需要再切分时,可以为单个值) d 右子树

这里我们会介绍两种树:回归树模型树。前者的每个叶节点是单个值,而后者的每个叶节点是一个线性方程。我们首先给出两种树构建算法中的共用代码。
伪代码:(creatTree())

找到最佳的待切分特征:
    如果该节点不能再切分,将该节点存为叶节点
    执行二元切分
    在右子树回归调用creatTree()
    在左子树回归调用creatTree()
# regTrees.py
import numpy as np  # 导入模块

def loadData(fileName):
    '载入数据集'
    dataMat = []
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('\t')
        fltLine = list(map(float, curLine))  # 对队列中的每个元素使用float,变为浮点型数据(内建函数), 返回List
        dataMat.append(fltLine)
    return dataMat
def binSplitDataSet(dataSet, feature, value):
    '数组过滤。阈值为界,分为两个数据集。 见注(1)'
    mat0 = dataSet[np.nonzero(dataSet[:, feature] > value)[0], :][0]
    mat1 = dataSet[np.nonzero(dataSet[:, feature] <= value)[0], :][0]
    return mat0, mat1

def creatTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):
    """
    树构建函数,有四个参数,数据集和其他三个可选参数。
    regLeaf为建立叶节点的函数 (后面会介绍解读)
    regErr为计算误差的函数 (后面介绍)
    ops为其他需要的参数 容许的误差下降值,切分的最小样本数
    """
    feat, val = chooseBestSplit(dataSet, leafType, errType, ops) # 该函数将数据集分为两个部分,返回最佳切分的特征和阈值
    if feat == None:   # 不能再切分时,返回单个值
        return  val
    retTree = {} # 存储树的字典
    retTree['spInd'] = feat #  待切分的特征
    retTree['spVal'] = val  # 待切分的特征值
    lset, rset = binSplitDataSet(dataSet, feat, val)  # 以最佳的特征和阈值将数据集分为两个子集
    retTree['left'] = creatTree(lset, leafType, errType, ops)
    retTree['right'] = creatTree(rset, leafType, errType, ops)
    return retTree


(1)np.nonzero()
对于一维数组a:nonzeros(a)返回数组a中值不为零的元素的下标,它的返回值是一个长度为a.ndim(数组a的轴数)的元组,元组的每个元素都是一个整数数组,其值为非零元素的下标在对应轴上的值。
对于二维数组b:nonzero(b)所得到的是一个长度为2的元组。它的第0个元素是数组a中值不为0的元素的第0轴的下标,第1个元素则是第1轴的下标。比如下图中(0, 0) (0,2) (1,1) (1,2) 四个元素不为0,所以第一维的下标组成一个元组 (0, 0,1,1),第二位下标组成一个元组。
这里写图片描述

CART用于回归
我们前面决定用树结构来切分数据,但是如何进行切分?如何知道已经切分完成?这些主要取决于叶节点的建模方式
为成功构建以分段常数为叶节点的回归树,首先需要度量数据的一致性。在决策树中,离散型数据,通过计算信息熵等来度量数据的混乱度。而对于连续型数据,首先计算数据的均值,然后计算每个数据值到均值的差值(为了将正负值同等对待,一般使用平方值或者绝对值) ,前面的计算过程类似于方差 1N(yy¯)2 ,而这里需要的是平方误差的总值(总方差) (yy¯)2 。显然这可以通过均方差乘以数据集样本点个数得到
接着我们就可以用代码来构建树, 为了使前面的代码能够运行,我们需要补充完成之前的函数。
chooseBestSplit():给定误差计算方法,该函数找到最佳的二元切分方式,用最佳方式切分数据集和生成相应的叶节点。
regLeaf() :建立叶节点的函数
regErr():计算误差的函数

chooseBestSplit()函数遍历所有的特征和所有可能的特征值来找到是误差最小化的切分阈值。伪代码如下:

对每个特征:
    对每个特征值:
        将数据分为两份
        计算切分误差
        如果当前误差小于当前最小误差,那么将当前切分设定为最佳切分并更新最小误差
返回最佳切分特征和阈值
# 
def regLeaf(dataSet):
    '生成叶节点'
    return np.mean(dataSet[:, -1])

def regErr(dataSet):
    '计算总方差'
    return np.var(dataSet[:, -1]) * np.shape(dataSet)[0]
    # 为什么是最后一列数据呢??

def chooseBestSplit(dataSet, leafType, errType, ops=(1, 4)):
    '回归数的切分函数'
    tolS = ops[0] # 容许的误差下降值
    tolN = ops[1] # 切分的最少样本数
    if len(set(dataSet[:, -1].T.tolist()[0])) == 1: # 1
        return None, leafType(dataSet) # 统计不同剩余特征值的数目,如果所有值相等则返回最佳特征为None。
    m, n = np.shape(dataSet)
    S = errType(dataSet) #  首先计算dataSet的总方差
    bestS = np.inf # 初始化最小误差为inf
    bestIndex = 0  # 最佳特征的索引值,初始为0
    bestValue = 0
    for featIndex in range(n-1): # 对每个特征
        for splitVal in np.set(dataSet[:, featIndex].T.A.tolist()[0]): # 对每个特征值
            mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal) # 将数据集按当前的特征和特征值分为两个子集
            if (np.shape(mat0)<tolN) or (np.shape(mat1)<tolN):
                continue  # 如果达到停止条件,直接退出该循环,继续下一循环
            newS = errTypr(mat0) + errType(mat1) # 计算切分数据集新的总方差
            if newS < bestS:
                bestIndex = featIndex
                bestValue = splitVal
                bestS = newS
    if (S - bestS) < tolS:  # 2
        return None, leafType(dataSet)  #如果误差减小不大,退出
    mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
    if (np.shape(mat0)<tolN) or (np.shape(mat1)<tolN): # 3
        return None, leafType(dataSet) # 如果切分出的数据集很小,退出
    return bestIndex, bestValue


(1)该函数是回归数构建的核心,目的就是找到数据最佳的二元切分方式。如果找不到一个合适的二元切分,该函数返回None,并直接创建叶节点。在代码中共有3个情况不会切分:
1 如果不同剩余特征值的数目为1(数据集中所有值相等)
2 如果切分数据集后效果提升不大
3 检查两个切分后的自子集大小,如果某个子集大小小于定义的tolN
对测试数据:
这里写图片描述

运行的结果为:
这里写图片描述
用图示表示为:
这里写图片描述
该树的结构共有5个叶节点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值