机器学习笔记(8)-树回归

树回归CART概述

线性回归创建的模型需要拟合所有的样本点(局部加权线性回归除外)。当数据拥有众多特征并且特征之间关系十分复杂时,构建全局模型的想法就显得太难了,也略显笨拙。而且,实际生活中很多问题都是非线性的,不可能使用全局线性模型来拟合任何数据。一种可行的方法是将数据集切分成很多份易建模的数据,然后利用我们的线性回归技术来建模。如果首次切分后仍然难以拟合线性模型就继续切分。在这种切分方式下,树回归和回归法就相当有用。
如何计算连续型数值的混乱度:计算连续型数值的混乱度是非常简单的。首先计算所有数据的均值,然后计算每条数据的值到均值的差值。为了对正负差值同等看待,一般使用绝对值或平方值来代替上述差值。这种做法有点类似于前面介绍过的统计学中常用的方差计算。唯一不同就是,方差是平方误差的均值(均方差),而这里需要的是平方误差的总值(总方差)。总方差可以通过均方差乘以数据集中样本点的个数来得到。
CART(Classification And Regression Trees, 分类回归树) :可以用于分类还可以用于回归。树构建算法ID3 的做法是每次选取当前最佳的特征来分割数据,并按照该特征的所有可能取值来切分。切分过于迅速外,它不能直接处理连续型特征。CART 是十分著名且广泛记载的树构建算法,它使用二元切分来处理连续型变量,对 CART 稍作修改就可以处理回归问题。

CART算法

分类与回归树(classification and regression tree ,CART)可以用于分类与回归。ID3 和C4.5 树都是多叉结构,CART树是二叉树,内部节点特征的取值为“是”和“否”,左分支是取值为“是”的分支,右分支是取值为“否”的分支。
回归树的生成:一个回归树对应着输入空间(特征空间)的一个划分以及在划分的单元上的输出值。假设已将输入空间划分为M个单元R1,R2,⋯,RM,并在每个单元Rm上有一个固定的输出值,于是回归树模型可表示为:
这里写图片描述
当输入空间的划分确定时,可以用平方误差来表示回归树对于训练数据的预测误差:
这里写图片描述
下面算法第一步中公式里面的c1和c2是用求得的切分点j和s划分的两个数据集并求其平均值。
这里写图片描述

分类树的生成

基尼指数:分类问题中,假设有K个类,样本点属于第k类的概率为pk,则概率分布的基尼指数定义为:
这里写图片描述
对于给定的样本集合D,其基尼指数为:
这里写图片描述
概率分布的基尼指数与熵意义类似,值越大,其不确定性越大。
如果样本集合D根据特征A是否取某一可能值a被分割成D1和D2两部分,则在特征A的条件下,集合D的基尼指数定义为:
这里写图片描述
Gini(D)是计算样本集的基尼指数,表示集合D的不确定性;
Gini(D,A)是表示经A=a分割后集合D的不确定性,选择时要选择最小的,和H(D|A)意义类似,都是取最小的。H(D|A)是对特征做计算,选择一个特征,Gini(D,A)是对特征及其一个取值做计算,选择一个特征及其分割点。
注意:ID3和C4.5建树时是选取特征,切分点自动根据特征取值确定;CART分类树建树时是选取特征及其切分点。
建树的过程和ID3、C4.5类似,三个结束条件:1.无更多特征(建树时下层的节点仍然会有可能用到上层特征,但不可能是同一切分点);2.节点中的样本数小于预定的阈值;3.样本集的基尼指数大于指定阈值。
数据处理
按指定某个值切分矩阵
这里写图片描述

def binSplitDataSet(dataSet, feature, value):
    mat0 = dataSet[nonzero(dataSet[:,feature] > value)[0],:][0]
    mat1 = dataSet[nonzero(dataSet[:,feature] <= value)[0],:][0]
    return mat0,mat1
'''
>>> testMat = mat(eye(4))
>>> testMat[:,1] > 0.5
matrix([[False],[ True],[False],[False]], dtype=bool)
>>> nonzero(testMat[:,1] > 0.5)
(array([1], dtype=int64), array([0], dtype=int64))
>>> nonzero(testMat[:,1] > 0.5)[0]
array([1], dtype=int64)
'''
def regLeaf(dataSet):
    # 负责生成叶节点,当不在对数据进行切分时,调用该函数生成叶节点模型,在回归树中,该模型就是目标变量的均值
    return mean(dataSet[:,-1])
def regErr(dataSet):
    return var(dataSet[:,-1]) * shape(dataSet)[0]
def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):
    '''用最佳方式切分数据集 和 生成相应的叶节点
    对每个特征:
        对每个特征值:
            将数据集切分成两份(小于该特征值的数据样本放在左子树,否则放在右子树)
            计算切分的误差
            如果当前误差小于当前最小误差,那么将当前切分设定为最佳切分并更新最小误差
    返回最佳切分的特征和阈值
    :param dataSet:加载的原始数据集
    :param leafType:建立叶子点的函数
    :param errType:误差计算函数(求总方差)
    :param ops:[容许误差下降值,切分的最少样本数]。
    :return:
        bestIndex:bestIndex feature的index坐标
        bestValue:bestValue 切分的最优值
    '''
    tolS,tolN = ops[0],ops[1]
    if len(set(dataSet[:,-1].T.tolist()[0])) == 1:  # matrix([[ 0.],[ 1.],[ 0.],[ 0.]]).T.tolist() --> [[0.0, 1.0, 0.0, 0.0]]
        return None, leafType(dataSet)
    m,n = shape(dataSet)
    S = errType(dataSet)
    bestS = inf; bestIndex = 0; bestValue = 0
    for featIndex in range(n-1):
        for splitVal in set(dataSet[:, featIndex].T.tolist()[0]):  # [0]表示这一列的[所有行],不要[0]就是一个array[[所有行]]
            mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)
            if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): continue
            newS = errType(mat0) + errType(mat1)
            if newS < bestS: 
                bestIndex = featIndex
                bestValue = splitVal
                bestS = newS
    if (S - bestS) < tolS:     # 如果误差减少不大,则退出
        return None, leafType(dataSet)
    mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
    if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN):
        return None, leafType(dataSet)
    return bestIndex,bestValue
def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):
    '''获取回归树
    :param dataSet:加载的原始数据集
    :param leafType:建立叶子点的函数
    :param errType:误差计算函数
    :param ops:容许误差下降值,切分的最少样本数
    :return:
        retTree:决策树最后的结果
    '''
    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'] = createTree(lSet, leafType, errType, ops)
    retTree['right'] = createTree(rSet, leafType, errType, ops)
    return retTree  
树减枝

一棵树如果节点过多,表明该模型可能对数据进行了 “过拟合”。通过降低决策树的复杂度来避免过拟合的过程称为 剪枝(pruning)。在函数 chooseBestSplit() 中提前终止条件,实际上是在进行一种所谓的 预剪枝(prepruning)操作。另一个形式的剪枝需要使用测试集和训练集,称作 后剪枝(postpruning)预剪枝就是及早的停止树增长,在构造决策树的同时进行剪枝。所有决策树的构建方法,都是在无法进一步降低熵的情况下才会停止创建分支的过程,为了避免过拟合,可以设定一个阈值,熵减小的数量小于这个阈值,即使还可以继续降低熵,也停止继续创建分支。但是这种方法实际中的效果并不好。后剪枝就是决策树构造完成后进行剪枝。剪枝的过程是对拥有同样父节点的一组节点进行检查,判断如果将其合并,熵的增加量是否小于某一阈值。如果确实小,则这一组节点可以合并一个节点,其中包含了所有可能的结果。合并也被称作 塌陷处理 ,在回归树中一般采用取需要合并的所有子树的平均值。后剪枝是目前最普遍的做法。

基于已有的树切分测试数据:
    如果存在任一子集是一棵树,则在该子集递归剪枝过程
    计算将当前两个叶节点合并后的误差
    计算不合并的误差
    如果合并会降低误差的话,就将叶节点合并

后剪枝

def isTree(obj):  # 测试输入变量是否是一棵树,即是否是一个字典
    return (type(obj).__name__=='dict')

def getMean(tree):  # 计算左右枝丫的均值
    if isTree(tree['right']): tree['right'] = getMean(tree['right'])
    if isTree(tree['left']): tree['left'] = getMean(tree['left'])
    return (tree['left']+tree['right'])/2.0

def prune(tree, testData):
    '''
    :param tree:待剪枝的树
    :param testData:剪枝所需要的测试数据 testData
    :return:
        tree:剪枝完成的树
    '''
    if shape(testData)[0] == 0: return getMean(tree)  # 判断是否测试数据集没有数据,如果没有,就直接返回tree本身的均值(塌陷处理)
    if (isTree(tree['right']) or isTree(tree['left'])):  # 判断分枝是否是dict字典,如果是就将测试数据集进行切分
        lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
    if isTree(tree['left']): tree['left'] = prune(tree['left'], lSet)
    if isTree(tree['right']): tree['right'] =  prune(tree['right'], rSet)
    if not isTree(tree['left']) and not isTree(tree['right']):
        lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
        errorNoMerge = sum(power(lSet[:,-1] - tree['left'],2)) +\
            sum(power(rSet[:,-1] - tree['right'],2))
        treeMean = (tree['left']+tree['right'])/2.0
        errorMerge = sum(power(testData[:,-1] - treeMean,2))
        if errorMerge < errorNoMerge:    # 注意返回的结果: 如果可以合并,原来的dict就变为了 数值
            print "merging"
            return treeMean
        else: return tree
    else: return tree

后剪枝可能不如预剪纸有效,一般为求最大模型可以同时使用两种剪枝技术

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值