《机器学习之CRAT回归树》

上篇博文介绍了ID3决策树的基本原理,ID3的缺陷是没有剪枝操作(容易过拟合),无法处理连续数据,而且直接使用信息增益作为判别分裂属性的选择也有问题,详情请看相关文章介绍C45决策树的,这里我就不去多介绍了,我的目的是跟着《Machine in Action》这本书进行学习,讲解最基础的算法。
回归也是有监督学习算法的一种,可以理解为连续取值标签的分类。传统的分类算法是依据属于某个类别的概率(得分等)判断属于某一个类,那决策树如何实现回归哪?上一篇博文中,我介绍了如何根据属性进行分裂数据集,不断的进行上述操作,生成一个树。被划分在同一个数据集中的数据(同一个子树)显然有某种相似的特征,CRAT决策树就是利用这种方法实现回归算法的。比如平面上有如下几个点:
《机器学习之CRAT回归树》_第1张图片

import matplotlib.pyplot as plt
    plt.figure(1)
    plt.scatter([0.8,1,1.2,2.8,3.0,3.2],[0.9,1.0,1.1,2.9,3.0,3.1])
    plt.show()

我现在有x=0.9这个点,它的取值是多少(y)?如果使用决策树,会这样操作:0.9会被划分到(0.8,0.9),(1,1),(1.2,1.1)这三个点构成的子叶点上,那么我就可以使用这三个点的均值估计x=0.9时y的取值y=1。具体的算法不仅要选择分裂属性,而且还要选择分裂属性的分裂值,具体来讲其实CRAT就是一个二叉树,每一次选择一个属性进行二次划分,直到满足停止条件为止。那么停止条件是什么哪?停止条件可以是:树的深度,每一个属性最多被划分的次数,每一个树最少的数据数目,数据集的目标值相等,或者误差很小等等。每一个属性分裂值的选择是根据误差最小的方法来判断,即判断分裂的左右数据集的误差,这个误差定义为方差(因为最后判断的准则是赋予均值,所以偏离均值的大小可以作为衡量的标准)。CRAT回归树还有一个重要的性质,就是进行剪枝操作,能够预防过拟合(关于过拟合如果都不知道的话,呵呵。。。)。如何进行剪枝操作,有很多种,比如前面介绍的停止条件: 树的深度,每一个属性最多被划分的次数,每一个树最少的数据数目等称为前剪枝,而应用最多的是后剪枝:如果测试集没有到达某一个节点(或者数目比较少),则可以撤销这个节点变为叶子节点(用左右叶子节点数值的均值作为新叶子节点的值);若果把该节点变为叶子节点时误差也会随之减小,则进行剪枝操作(过程和上一步骤一样),具体过程还是直接看代码吧(代码来自于Machine Learning Action):

''' Created on Feb 4, 2011 Tree-Based Regression Methods @author: Peter Harrington '''
from numpy import *
def loadDataSet(fileName):
    dataMat = []
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('\t')
        fltLine = map(float,curLine) #使用map函数将数据转换为float
        dataMat.append(fltLine)
    return dataMat
def binSplitDataSet(dataSet, feature, value):#根据提供的分裂点将数据集分割为两个子集,分别作为子树的数据集
    mat0 = dataSet[nonzero(dataSet[:,feature] > value)[0],:][0]
    mat1 = dataSet[nonzero(dataSet[:,feature] <= value)[0],:][0]
    return mat0,mat1
def regLeaf(dataSet):#返回叶子节点的平均值
    return mean(dataSet[:,-1])
def regErr(dataSet):#这里误差的定义为偏离均值的平方和,方差的平方和
    return var(dataSet[:,-1]) * shape(dataSet)[0]
def linearSolve(dataSet):   #使用最小二乘法解决方程
    m,n = shape(dataSet)
    X = mat(ones((m,n))); Y = mat(ones((m,1)))
    X[:,1:n] = dataSet[:,0:n-1]; Y = dataSet[:,-1]
    xTx = X.T*X
    if linalg.det(xTx) == 0.0:
        raise NameError('This matrix is singular, cannot do inverse,\n\ try increasing the second value of ops')
    ws = xTx.I * (X.T * Y)
    return ws,X,Y
def modelLeaf(dataSet):#对叶子节点的数据使用线性模型
    ws,X,Y = linearSolve(dataSet)
    return ws
def modelErr(dataSet):#计算误差函数,这里的误差函数经过了高斯函数的转换
    ws,X,Y = linearSolve(dataSet)
    yHat = X * ws
    return sum(power(Y - yHat,2))
def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):
    tolS = ops[0]; tolN = ops[1]
    #如果数据集的目标(类别)都一样,那么作为叶子节点
    if len(set(dataSet[:,-1].T.tolist()[0])) == 1: #exit cond 1
        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]):#该属性每一个取过的值都可能作为分裂点
            mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)
            if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): continue#如果分裂后某一个子数据集的数量小于tolN,则跳入下一个循环
            newS = errType(mat0) + errType(mat1)
            if newS < bestS: #选取误差小的作为最有分裂点
                bestIndex = featIndex
                bestValue = splitVal
                bestS = newS
    #
    if (S - bestS) < tolS:#两者差值小于门限值,则不进行分裂
        return None, leafType(dataSet) #exit cond 2
    mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
    if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN):  #达到作为叶子节点的条件,就以均值作为叶子节点的值
        return None, leafType(dataSet)
    return bestIndex,bestValue#返回分裂属性的index和value
def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):#assume dataSet is NumPy Mat so we can array filtering
    feat, val = chooseBestSplit(dataSet, leafType, errType, ops)#choose the best split
    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  
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):
    if shape(testData)[0] == 0: return getMean(tree) #若果测试集没有到达该子树,剪枝
    if (isTree(tree['right']) or isTree(tree['left'])):#若左右子树存在,则尝试剪枝
        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: 
            print "merging"
            return treeMean
        else: return tree
    else: return tree#继续进行剪枝

def regTreeEval(model, inDat):
    return float(model)
def modelTreeEval(model, inDat):
    n = shape(inDat)[1]
    X = mat(ones((1,n+1)))
    X[:,1:n+1]=inDat
    return float(X*model)
#预测
def treeForeCast(tree, inData, modelEval=regTreeEval):
    if not isTree(tree): return modelEval(tree, inData)
    if inData[tree['spInd']] > tree['spVal']:
        if isTree(tree['left']): return treeForeCast(tree['left'], inData, modelEval)
        else: return modelEval(tree['left'], inData)
    else:
        if isTree(tree['right']): return treeForeCast(tree['right'], inData, modelEval)
        else: return modelEval(tree['right'], inData)

def createForeCast(tree, testData, modelEval=regTreeEval):
    m=len(testData)
    yHat = mat(zeros((m,1)))
    for i in range(m):
        yHat[i,0] = treeForeCast(tree, mat(testData[i]), modelEval)
    return yHat

main.py

main.py
from regTrees import *
myDat = loadDataSet("ex00.txt")
myMat = mat(myDat)
print createTree(myMat)

你可能感兴趣的:(python,决策树,决策回归树,CRAT回归树)