树回归

1、分类与回归树

分类与回归树(Classification and Regression Trees, CART),既可用于分类也可用于回归。

本文基于机器学习实战这本书,将构建两种树,回归树-其每个叶节点包含单值
模型树-其每个叶节点是一个线性方程。这也就说CART既能分类又能回归的原因。

2、连续与离散型特征树的构建

回顾前面的决策树进行分裂,决策树不断江苏局切分小数据集,知道目标变量完全相同或者数据不能再切分为止。决策树是一种贪心算法,他要在给定时间内做出最佳选择。但并不关心达到全局最优。ID3算法是每次选取当前最佳的特征来切割数据,并按照该特征所有可能取值来切分,ID3算法存在两个问题,一个是切分过于迅速(一旦某个特征切分后,该特征在算法执行过程中以后将不再执行),另一种是,他不能切分连续性特征。只有事先将连续特征转化为离散才能使用。但这种转化过程会破坏连续型变量的内在性质。
而是用二元切分则易于对数构建过程进行调整以处理连续型特征。具体方法是:
如果特征值大于给定值就进入左子树,否则进入右子树。卧槽…………好像二叉排序树……有木有…………

首先设计一个字典来存储树的结构
1、待切分的特征
2、待切分的特征值
3、右子树。当不再需要切分的时候,也可以是单值
4、左子树与右子树类似
找到最佳的待切分特征:
   如果该节点不能再分,将该节点存为叶节点
   执行二元切分
   在右子树调用creatTree()方法…………就是递归啦
   在左子树调用creatTree()方法

3、构建树

4、树剪枝

5、模型树

#encoding:utf-8
import numpy as np
from numpy import *
#导入文件
def loadDataSet(fileName) : 
    dataMat = []
    fr = open(fileName)
    for line in fr.readlines() :
        curLine = line.strip().split('\t')
        fltLine = map(float, curLine)
        dataMat.append(fltLine)
    return dataMat

# dataSet: 数据集合
# feature: 待切分的特征
# value: 该特征的某个值    
# 该函数有三个参数, 数据集合、待切分的特征和该特征的某个值
def binSplitDataSet(dataSet, feature, value) :
    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):
    return var(dataSet[:,-1])*shape(dataSet)[0]
'''模型树中负责生成的叶节点。 叶节点中存储的是该分类的回归系数'''
def modelLeaf(dataSet):
    ws,X, Y= linerSlove(dataSet)
    return ws
'''模型树中计算yHat与Y之间的平方误差'''
def modelErr(dataSet):
    ws,X,Y = linerSlove(dataSet)
    yHat = X*ws
    return sum(power(Y-yHat, 2))
'''树回归中预测返回的叶节点'''
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)
'''最好的切分方式,既能切分回归树又能切分模型树'''
#dataSet数据集,leafType 选择生成叶子节点的函数 errType选择计算误差类型的函数
#ops[0]代表的容许误差下降值,ops[1]代表切分最少的样本数
#其实ops是用户用于控制函数停止的时机
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:#set([..,..,..])里面接收列表
        return None,leafType(dataSet)
    m, n = shape(dataSet)

    S = errType(dataSet)#当前的误差值
    bestS = inf; bestIndex = 0.0;bestValue = 0.0
    for i in range(n - 1):#遍历所有的特征值 
        for splitVal in set(dataSet[:,i].T.tolist()[0]):#遍历特征中的 不同类型,寻找最优
            lSet, rSet = binSplitDataSet(dataSet, i, splitVal)
            if (shape(lSet)[0] < tolN) or (shape(rSet)[0] < tolN):#如果样本数少于用户指定的数目,则不切分,一定程度上防止过拟合
                continue
            newS = errType(lSet) + errType(rSet)#切分后的误差值
            if newS < bestS:#寻找最优解
                bestS = newS
                bestIndex = i 
                bestValue = splitVal
    if (S - bestS) < tolS:#如果最优解的变化的误差值都小于规定的容忍度,那么不用继续划分直接生成叶节点
        return None, leafType(dataSet)
    lSet, rSet = binSplitDataSet(dataSet, bestIndex, bestValue)#如果最优解切分的数目过小也是直接生成叶节点
    if (shape(lSet)[0] < tolN) or (shape(rSet)[0] < tolN):
        return None, leafType(dataSet)
    return bestIndex,bestValue#如果符合用户指定要求那么返回最佳的特征值,和特征值中最佳的切割点
'''创建回归树或者模型树'''
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
#----------------树剪枝--------------------
def isTree(obj):#判断是不是词典
    return (type(obj).__name__=='dict')
'''函数getMean()是一个递归函数,它从上往下遍历树直到叶节点为止。如果找到两个叶节点
则计算他们的平均值。该函数对树进行“塌陷处理”(返回树的平均值),prunce中调用函数时候应该明确'''
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['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 linerSlove(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:
        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    

'''用树回归进行预测'''
#tree:建立好的树模型
#indata:预测的向量
#modelEval对节点进行预测的引用,根据回归树和模型树的不同调用不同的函数
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 createForCast(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
#------------树回归与树剪枝----------------------
# data1 = mat(loadDataSet('ex2.txt'))
# print shape(data1)
# data = mat(loadDataSet('ex2test.txt'))
# print shape(data)
# mytree = createTree(data1,ops = (0,1))
# print mytree
# print prune(mytree, data)
# import matplotlib.pyplot as plt
# plt.plot(data[:,0],data[:,1],'o')
# plt.plot(data1[:,0],data1[:,1],'o')
# plt.show()
#--------------模型树-----------------------------
# data = mat(loadDataSet('exp2.txt'))
# mytree = createTree(data,leafType = modelLeaf,errType = modelErr, ops = (1, 10))
# print mytree
#--------------比较回归树、模型树、标准的线性回归--------
#回归树的预测
# tainSet = mat(loadDataSet('bikeSpeedVsIq_train.txt'))
# testSet = mat(loadDataSet('bikeSpeedVsIq_test.txt'))
# myTree = createTree(tainSet,ops=(1,20))
# yHat = createForCast(myTree, testSet[:,0])
# print '树回归',corrcoef(yHat, testSet[:,-1], rowvar=0)[0,1]
# #模型树的预测
# myTree = createTree(tainSet,leafType =  modelLeaf,errType = modelErr,ops=(1,20))
# yHat = createForCast(myTree, testSet[:,0],modelEval = modelTreeEval)
# print '模型树',corrcoef(yHat, testSet[:,-1], rowvar=0)[0,1]
# #标准线性回归预测
# ws,x,y = linerSlove(tainSet)
# yHat[:,0] = testSet[:,0]*ws[1, 0]+ws[0,0]
# print '标准线性回归',corrcoef(yHat, testSet[:,-1], rowvar=0)[0,1] 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值