机器学习day14 机器学习实战树回归之CART与模型树

这几天完成了树回归的相关学习,这一部分内容挺多,收获也挺多,刚刚终于完成了全部内容,非常开心。

树回归这一章涉及了CART,CART树称作(classify and regression tree) 分类与回归树,既可以用于分类,也可以用于回归。这正是前面决策树没有说到的内容,在这里补充一下。正好也总结一下我们学的3种决策树。

ID3:用信息增益来选择特性进行分类,只能处理分类问题。缺点是往往偏向于特性种类多的特性进行分解,比如特性A有2种选择,特性B有3种选择,混乱度差不多的情况下,ID3会偏向特性B进行选择,这样会使得树变得复杂,增加了建树难度,节点的增加也使得过拟合情况的出现。因为这个缺点,我们发明了ID3的优化版C4.5。

C4.5:用信息增益率来选择特性进行分类,信息增益和特性种类的个数多少正好抵消,信息增益率可以客观的表示选择某一特性标签的混乱程度。

CART:用基尼指数(类似于熵的概念)来选择特性进行分类。用总方差进行回归预测。上述两种算法分割特性是一旦选择了该特性,该特性便不再起作用,由于该分割过于‘迅速’,不能更好的利用特性的价值,CART算法横空出世

在以上的ID3和C4.5,只能处理分类问题,在遇到标签为连续型数值便无法处理,所以CART的优势便体现出来,CART可以进行回归分析,预测回归值,称作树回归,CART可以进行剪枝操作避免过拟合,分为预剪枝和后剪枝,后剪枝后面会有,预剪枝为修改函数的参数ops。也可以把叶节点的回归数值改成线性回归,称作模型树。回归效果略优于简单的线性回归(最后的三种回归结果我们可以看到)。

step1:

读取数据:

#读取数据
def loadDataSet(filename):
    feanum = len(open(filename).readline().strip().split('\t'))
    datamat = []
    f = open(filename)
    for i in f.readlines():
        line = i.strip().split('\t')
        l = []
        for j in range(feanum):
            l.append(float(line[j]))
        datamat.append(l)
    return datamat

step2:

递归创建回归树:

#创建树(ops两个参数第一个表示最小允许误差,第二个为样本最小容量)
def createTree(dataset, leaftype = regLeaf, errortype = regError, ops = [1, 4]):
    tree = {}
    fea, val = chooseBestFeatrue(dataset, leaftype, errortype, ops)
    if fea == None:
        return val
    tree['fea'] = fea
    tree['val'] = val
    lefttree, righttree = splitDataSet(dataset, fea, val)
    tree['left'] = createTree(lefttree, leaftype, errortype, ops)
    tree['right'] = createTree(righttree, leaftype, errortype, ops)
    return tree

step3:

回归树中的分割子集函数:

#分类函数
def splitDataSet(Data, featrue, val):
    mat0 = []
    mat1 = []
    data = mat(Data)
    m = shape(data)[0]
    for i in range(m):
        if data[i, featrue] > val:
            mat0.append(data[i].tolist()[0])
        else:
            mat1.append(data[i].tolist()[0])
    return mat0, mat1

step4:

回归树中的查找最佳特性函数:

停止条件:

1:分类的子集数量小于4个

2:误差值降低量小于最小允许值

3:标签值都为重复值,无需再次分解,返回为叶子节点。

#寻找最佳分类特性
def chooseBestFeatrue(dataset, leaftype = regLeaf, errortype = regError, ops = [1, 4]):
    d = mat(dataset)
    leastnum = ops[1]
    leasterr = ops[0]
    m, n = shape(d)
    if len(set(d[ : , -1].T.tolist()[0])) == 1:
        return None, leaftype(d)
    errorsum = errortype(d)
    bestfea = -1
    bestval = -1
    besterror = inf
    for i in range(n - 1):
        for temp in set(d[ : , i].T.tolist()[0]):
            mat0, mat1 = splitDataSet(d, i, temp)
            if shape(mat0)[0] < leastnum or shape(mat1)[0] < leastnum:
                continue
            tempsumerror = errortype(mat0) + errortype(mat1)
            if tempsumerror < besterror:
                bestfea = i
                bestval = temp
                besterror = tempsumerror
    if (errorsum - besterror) < leasterr:
        print errorsum, besterror
        return None, leaftype(d)
    mat0, mat1 = splitDataSet(d, bestfea, bestval)
    if shape(mat0)[0] < leastnum or shape(mat1)[0] < leastnum:
        return None, leaftype(d)
    return bestfea, bestval   
step5:

相关函数:

#计算平方误差
def regError(dataset):
    t = mat(dataset)
    return var(t[ : , -1]) * shape(t)[0]

#计算叶子节点的值(叶子节点的平均值)
def regLeaf(dataset):
    t = mat(dataset)
    return mean(t[ : , -1])
#判断节点是否为树
def isTree(tree):
    return isinstance(tree, dict)

#节点值的平均值
def getMean(tree):
    if isTree(tree['left']):
        tree['left'] = getMean(tree['left'])
    if isTree(tree['right']):
        tree['right'] = getMean(tree['right'])
    return (tree['right'] + tree['left']) / 2.0     

我们看一下效果:

图1:

结果为:

图2:

机器学习day14 机器学习实战树回归之CART与模型树_第1张图片

结果为:

我们看到符合我们的预期。

step6:

回归树的剪枝:这里的剪枝和算法中的剪枝不同,算法中的剪枝为剪去不必要的节点,这里的剪枝为把繁琐的节点合并。目的位避免CART过拟合。分为预剪枝和后剪枝,

此为后剪枝,预剪枝为修改函数的参数ops。

这里有两个细节:

1:当没有数据进入树节点的时候,我们把这个树合并为一个节点。

2:当该节点的左节点和右节点都为值时,我们判断是分开好还是合并好。

为取得更好的剪枝效果,应该预剪枝和后剪枝一起用。

#回归树剪枝
def pruneTree(tree, testdata):
    if shape(testdata)[0] == 0:
        return getMean(tree)
    if isTree(tree['left']) or isTree(tree['right']):
        lset, rset = splitDataSet(testdata, tree['fea'], tree['val'])
    if isTree(tree['left']):
        tree['left'] = pruneTree(tree['left'], lset)
    if isTree(tree['right']):
        tree['right'] = pruneTree(tree['right'], rset)
    if (not isTree(tree['left'])) and (not isTree(tree['right'])):
        l, r = splitDataSet(testdata, tree['fea'], tree['val'])
        lset = mat(l)
        rset = mat(r)
        e1 = 0.0
        e2 = 0.0
        #书中忽略了这一点可能会有一个集合没有元素
        if shape(lset)[1] != 0:
            e1 = sum(power(lset[ : , -1] - tree['left'], 2))
        if shape(rset)[1] != 0:            
            e2 = sum(power(rset[ : , -1] - tree['right'], 2))
        errornomerge = e1 + e2
        average = (tree['left'] + tree['right']) / 2.0
        test = mat(testdata)
        errormerge = sum(power(test[ : , -1] - average, 2))
        if errormerge < errornomerge:
            print 'merging'
            return average
        else:
            return tree
    else:       
        return tree
step7:

模型树:

修改了回归树中的参数函数和加入了叶节点的线性回归函数。

#模型树线性回归函数(简单的线性回归函数,矩阵逆不存在时出错)
def lineSolve(data):
    linedata = mat(data)
    m, n = shape(linedata)
    x = mat(ones((m, n)))
    x[ : , 1 : n] = linedata[ : , 0 : n - 1]
    y = linedata[ : , -1]
    xtemp = x.T * x
    if linalg.det(xtemp) == 0.0:
        print 'error,没有逆矩阵'
        return
    w = xtemp.I * x.T * y
    return x, y, w

#模型树的误差
def modError(dataset):
    x, y, w = lineSolve(dataset)
    yhat = x * w
    return sum(power(yhat - y, 2))

#模型树的叶子节点    
def modLeaf(dataset):
    x, y, w = lineSolve(dataset)
    return w
图为:

结果为:

叶子节点的值为w,x * w得到y的预测值。

step8:

树回归的预测函数:

#回归值(树回归)
def regValue(value, dataset):
    return value

#回归值(模型树回归)
def modValue(value, dataset):
    data = mat(dataset)
    n = shape(data)[1]
    x = mat(ones((1, n + 1)))
    x[ : , 1 : n + 1] = data
    yhat = x * value
    return yhat
    
#树回归预测
def predictTree(tree, t, valuetype = regValue):
    testdata = mat(t)
    m, n = shape(testdata)
    yhat = mat(zeros((m, 1)))
    for i in range(m):
        yhat[i, 0] = predictValue(tree, testdata[i], valuetype)
    return yhat

#回归预测值
def predictValue(tree, test, valuetype = regValue):
    if not isTree(tree):
        return valuetype(tree, test)
    if test[tree['fea']] > tree['val']:
        return predictValue(tree['left'], test, valuetype)
    else:
        return predictValue(tree['right'], test, valuetype)         

接下来我们进行3种回归的比较:

图为:


相关系数越接近1,相关性越大。

1:CART树回归:

相关系数为:


2:模型树:

相关系数为:

3:线性回归

相关系数为:

可见大小顺序为 模型树 > 回归树 > 线性回归

终于完成了监督学习的相关学习部分,SVM还不懂,有机会学习一下SVM的应用,明天开始学习无监督学习,先学习聚类算法。加油!


你可能感兴趣的:(算法,python,机器学习,numpy)