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

来源:互联网 发布:全国软件设计师考试 编辑:程序博客网 时间:2024/05/20 21:24

在前面决策树的介绍中,我们使用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

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

1 0