机器学习实践——树回归(CART算法)

背景:线性回归需要拟合所有的数据才能生成模型,但是,当数据拥有众多的特征以及特征之间的关系十分复杂时,这种方法显得太难了。除此之外,实际生活中很多数据都是非线性的,不能使用全局线性模型进行拟合。因此提出树结构与回归法。

CART算法使用二元切分来处理连续性变量,ID3算法使用香农熵来度量集合的无组织程度,如果采用其他的度量就可以采用树构建算法完成回归树。回归树与分类树的思路类似,不过分类树的叶节点是离散型,回归树的叶节点是连续型。

将CART算法用于回归

from numpy import *
#加载数据
def loadDateSet(filename):
    fr = open(filename)
    dataMat = []
    for line in fr.readlines():
        currline = line.strip().split('\t')
        fltline = map(float,currline)
        dataMat.append(fltline)
    return dataMat
#切分数据,feature:待切分的特征,value:切分的特征值
def BinSplitDataSet(dataset,feature,value):
    #nonzero(a)返回元组a中非零元素的索引值,返回一个二维数组
    #第一个array从行维度来描述索引值;第二个array从列维度来描述索引值
    mat0 = dataset[nonzero(dataset[:,feature] > value)[0],:]
    mat1 = dataset[nonzero(dataset[:,feature] <= value)[0],:]
    return mat0,mat1

#负责生成叶节点,也就是目标变量的平均值
def regLeaf(dataSet):
    return mean(dataSet[:,-1])

#计算目标变量的平均误差
def regErr(dataSet):
    #var()是均方差函数,用均方差乘上样本个数即为总方差
    return var(dataSet[:,-1]) * shape(dataSet)[0]

def chooseBestSplit(dataSet,leafType=regLeaf,errType=regErr,ops=(1,4)):
    #tols与toln是用户指定的参数,用于控住函数的停止时机
    #tols是容许的误差下降值,toln是切分的最小样本数
    tols = ops[0];tolN = ops[1]
    #如果只剩下一种标签,则退出
    if len(set(dataSet[:,-1].T.tolist()[0]))==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 splitValue in set(dataSet[:,featIndex].T.A.tolist()[0]):
            #划分为两个数据集
            mat0,mat1 = BinSplitDataSet(dataSet,featIndex,splitValue)
            if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN):continue
            #计算总方差
            newS = errType(mat0) + errType(mat1)
            if newS < bestS:
                bestIndex = featIndex
                bestValue = splitValue
                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

#利用CART算法建立回归树
#
def createTree(dataSet,leafType = regLeaf,errType = regErr,ops = (1,4)):
    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
myDat = loadDateSet('Ch09/ex00.txt')
myMat = mat(myDat)
myTree = createTree(myMat,ops=(0,1))
print myTree

实验结果:这里只截取了部分

机器学习实践——树回归(CART算法)

 

树剪枝

剪枝:通过降低决策树的复杂度来避免过拟合的过程

剪枝分为预剪枝与后剪枝,预剪枝在构建树的过程中通过调整终止条件起到剪枝的过程,而后剪枝则是在决策树建立之后通过测试集与训练集进行修剪。这里只给出后剪枝的过程

#判断是否为一棵树,也就是判断当前处理的结点是不是叶节点
def isTree(obj):
    return (type(obj)==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

#后剪枝,tree:待剪枝的树,testData:剪枝所需要的测试数据
def prune(tree,testData):
    #如果测试数据为空,则返回均值
    if shape(testData)[0]==0:return getMean(tree)
     #若左右子树均不为叶节点,则划分左右子树
    if isTree(tree['left']) or isTree(tree['right']):
        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 '合并'
            return treeMean
        else:
            return tree
    else:
        return tree

实验结果:

机器学习实践——树回归(CART算法)

模型树

除了把叶结点定位常数值外,还可以把叶节点定义为分段线性函数,所谓分段线性是指模型由多个线性片段组成。

这里只需要将前面的代码稍加修改就可以在叶节点生成模型,这里需要修改的CreateTree()中叶子节点的类型,以及误差的计算方法。这里的误差计算先用线性模型进行拟合,然后计算真实的目标值与预测值之间的差值。最后将这些差值的平方求和就得到所需的误差。 

#执行简单的线性回归拟合
def linearSolve(dataSet):
    m,n = shape(dataSet)
    X = mat(ones((m,n)));Y = mat(ones((m,1)))
    #将数据集拆分为目标变量y与自变量X
    X[:,1:n] = dataSet[:,0:n-1];Y = dataSet[:,-1]
    #计算权重weight
    xTx = X.T*X
    if linalg.det(xTx) == 0:
        print "无法求逆"
        return
    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))
myMat = mat(loadDateSet('Ch09/exp2.txt'))
tree = createTree(myMat,leafType=modelLeaf,errType=modelErr,ops=(1,10))
print tree

实验结果

机器学习实践——树回归(CART算法)

有实验结果可以看到生成的线性模型分别是y=3.468x+1.1852与y=11.964x+0.00169,这与真实的数据y=3.5+1.0x与y=0+12x很接近。

上一篇:php – 设置WooCommerce购物车到期日


下一篇:php – 基于Woocommerce购物车项目计数的条件累进百分比折扣