树模型之回归树,模型树,树剪枝

在前面决策树的介绍中,我们使用ID3算法来构建决策树;这里我们使用CART算法来构建回归树和模型树。ID3算法是每次选取当前最佳的特征来分割数据,并按照该特征的所有可能取值来区分。比如,如果一个特征有4种取值,那么数据将被切分成4份。很明显,该算法不适用于标签值为连续型的数据。

CART算法使用二元切分法来处理连续型变量,即每次把数据集切分成左右两份。

回归树

回归树使用CART算法来构建树,使用二元切分法来分割数据,其叶节点包含单个值。创建回归树的函数createTree()的伪代码大致如下:

找到最佳的待切分特征:

    如果该节点不能再分,将该节点存为叶节点

    执行二元切分

    在左子树调用createTree()方法

    在右子树调用createTree()方法

创建回归树的过程与决策树类似,只是切分方法不同。同时,在计算数据的无序程度时,决策树是使用香农熵的方法,而我们的标签值是连续值,不能用该方法。那么,怎么计算连续型数值的混乱度呢?首先,计算所有数据的均值,然后计算每条数据的值到均值的差值,再求平方和,即我们用总方差的方法来度量连续型数值的混乱度。在回归树中,叶节点包含单个值,所以总方差可以通过均方差乘以数据集中样本点的个数来得到。

下面,将计算连续型数值混乱度的代码提供如下:

#计算分割代价
def spilt_loss(left,right):  #总方差越小,说明数据混乱度越小
    loss=0.0
    left_size=len(left)
    #print 'left_size:',left_size
    left_label=[row[-1] for row in left]
    right_size=len(right)
    right_label=[row[-1] for row in right]
    loss += var(left_label)*left_size + var(right_label)*right_size
    return loss

 得到叶节点预测值的代码:

#决定输出标签(取出叶节点数据的标签值,计算平均值)
def decide_label(data):
    output=[row[-1] for row in data]
    return mean(output)

模型树

模型树与回归树的差别在于:回归树的叶节点是节点数据标签值的平均值,而模型树的节点数据是一个线性模型(可用最简单的最小二乘法来构建线性模型),返回线性模型的系数W,我们只要将测试数据X乘以W便可以得到预测值Y,即Y=X*W。所以该模型是由多个线性片段组成的。

同样,给出叶节点预测值及计算待分割数据集混乱度的代码:

#生成叶节点
def decide_label(dataSet):
    ws,X,Y = linearModel(dataSet)
    return ws

#计算模型误差
def spilt_loss(dataSet):
    ws,X,Y = linearModel(dataSet)
    yat = X * ws
    return sum(power(yat-Y,2))
    
#模型预测数据
def modelTreeForecast(ws,dataRow):
    data = mat(dataRow)
    n = shape(data)[1]
    X = mat(ones((1,n)))
    X[:,1:n] = data[:,0:n-1]
    return X*ws

那么,如何比较回归树与模型树那种模型更好呢?一个比较客观的方法是计算预测值与实际值相关系数。该相关系数可以通过调用NumPy库中的命令corrcoef(yHat,y.rowvar=0)来求解,其中yHat是预测值,y是目标变量的实际值。


剪枝

通过降低树的复杂度来避免过拟合的过程称为剪枝。对树的剪枝分为预剪枝和后剪枝。一般地,为了寻求最佳模型可以同时使用这两种剪枝技术。

预剪枝:在选择创建树的过程中,我们限制树的迭代次数(即限制树的深度),以及限制叶节点的样本数不要过小,设定这种提前终止条件的方法实际上就是所谓的预剪枝。周志华的西瓜书中有对预剪枝的方法做具体描述,感兴趣的同学可以了解一下。因为我只是通过提前终止条件的方法来实现预剪枝,这种方法比较简单,不做具体描述。

后剪枝:使用后剪枝方法需要将数据集分为测试集和训练集。用测试集来判断将这些叶节点合并是否能降低测试误差,如果是的话将合并。

直接上代码:

'''后剪枝过程'''
#判断是否为字典
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(testData,tree):
    if len(testData)==0:
        return getMean(tree)
    if (isTree(tree['left']) or isTree(tree['right'])):    #判断tree['left']和tree['right']是否为字典,如果为字典则进行数据划分
        lSet,rSet = data_spilt(testData,tree['index'],tree['value'])  #划分数据集
    if isTree(tree['left']):            #如果tree['left']是字典,则执行prune()函数进行递归,直到tree['left']是叶节点时结束递归,往下继续执行函数
        tree['left'] = prune(lSet,tree['left'])
    if isTree(tree['right']):           #在tree['left']执行递归的基础上继续递归,这样可以取到所有左右两边的叶节点的值
        tree['right'] = prune(lSet,tree['right'])
    if not isTree(tree['left']) and not isTree(tree['right']):  #如果tree['left']和tree['right']都不是字典,执行下面操作
        lSet,rSet = data_spilt(testData,tree['index'],tree['value'])  #分割数据集
        left_value = [row[-1] for row in lSet]    #取出左数据集的节点值
        right_value = [row[-1] for row in rSet]   #取出右数据集的节点值
        if tree['left'] is None or tree['right'] is None:   #如果出现tree['left']或tree['right']为None时,返回树,不执行剪枝操作
            return tree
        else:
            errorNoMerge = sum(power(left_value-tree['left'],2)) + sum(power(right_value-tree['right'],2))   #计算没剪枝时测试集的标签值与叶节点的预测值的总方差
            treeMean = (tree['left'] + tree['right'])/2.0
            testSet_value = [row[-1] for row in testData]
            errorMerge = sum(power(testSet_value-treeMean,2))  #计算剪枝后测试集的标签值与叶节点的预测值的总方差
            if errorMerge < errorNoMerge:   #如果剪枝后的方差小于剪枝前,则执行剪枝;否则返回,不剪枝。
                print 'merging'
                return treeMean
            else:
                return tree
    else :
        return tree

以上,便是我在学习过程中对回归树,模型树,树剪枝的一些总结。

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值