树回归(Tree Regression)


一、分类树构建存在的问题

1. 切分过于迅速

假定当前叶子节点选择特征A来分割数据,那么数据A将不再后续的叶子节点中起作用,这样就会造成切分过于迅速

2. 不能处理连续数据

想要处理连续型数据,必须先将连续性数据转化成离散型数据。CART就是特别有名的利用二分法来处理连续性变量的树形算法。将度量方法稍作修改,就可以实现回归树。

二、混乱度

分类树中我们用信息熵和信息增益来决定最优划分属性,实际上信息增益的意义就是,选择这个划分属性能够达相对最好的分类效果,也就是给定节点时区计算数据的混乱度。

对于连续型数据来说,混乱度的衡量方法很简单,计算所有数据的均值,统计出所有数据到均值点的差值,为了将正负同等看待,一般使用平方值或者是绝对值来代替。

三、伪代码

for 遍历 特征

  for 遍历 特征值(我们可以选择连续两个数的平均值)

    将数据切分成两部分

    计算切分后的误差(混乱度)

    if 判断 误差和历史最小误差比较

      该误差较小 存储特征和特征值并更新历史最小误差

if 判断 误差减少较少 or 数据可供分割项较少(当误差设定为0,可供分割项选1的时候,可以生成最大树)

  结束回归树构建

结束所有遍历,返回最佳特征和特征值(切分目标特征和阈值)

四、数值划分代码

#-*-coding:utf-8-*-
## 引入numpy包
from numpy import *

## 加载数据
def loadDataSet(fileName):
    # 按照数据应该是3
    numFeat=len(open(fileName).readline().split('\t'))
    dataMat=[]
    fr=open(fileName)
    # for line in fr.readlines():
        # curLine=line.strip().split('\t')
        # # map(float,curLine)用来将每行的内容保存成一组浮点数,map形式
        # fltLine=map(float,curLine)
        # dataMat.append(fltLine)
    # return dataMat
    for line in fr.readlines():
        lineArr=[]
        curLine=line.strip().split('\t')
        for i in range(numFeat):
            lineArr.append(float(curLine[i]))
        dataMat.append(lineArr)
    return dataMat

## 通过数组过滤的方式来集合且分为两个子集合 参数:数据集合,待切分的特征,该特征的某个值
def binSplitDataSet(dataSet,feature,value):
    # 大于小于得到的是一个布尔值,nonzero是根据布尔值得到相应的坐标,[0]指的是横坐标,仔细想想就知道了
    mat0=dataSet[nonzero(dataSet[:,feature]>value)[0],:]
    mat1=dataSet[nonzero(dataSet[:,feature]<=value)[0],:]
    return mat0,mat1

## 均值函数和方差函数mean&var
# 叶子节点的确定 其实简单来说就是求均值嘛
def regLeaf(dataSet):
    return mean(dataSet[:,-1])
# 计算目标变量的平方误差,因为这里用到的是总误差,所以乘上了样本个数
def regErr(dataSet):
    return var(dataSet[:,-1])*shape(dataSet)[0]
    
## 用最佳方式分割数据集,并生成相应的叶子节点
# 第一个返回值为None的时候不会再进行切分
def chooseBestSplit(dataSet,leafType=regLeaf,errType=regErr,ops=(0,4)):
    # 用户指定 控制函数停止时机 (容许的误差下降值,切分的最少样本数)
    tolS=ops[0]
    tolN=ops[1]
    # 如果目标变量是相同值,则返回变量
    if len(set(dataSet[:,-1].T.tolist()[0]))==1:
        print ("aaa")
        print (len(set(dataSet[:,-1].T.tolist()[0])))
        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].T.tolist()[0]):
            # 调用函数binSplitDataSet 将数据按照上一层结果进行简单分类
            mat0,mat1=binSplitDataSet(dataSet,featIndex,splitVal)
            if (shape(mat0)[0]<tolN) or (shape(mat1)[0]<tolN):
                continue
            newS=errType(mat0)+errType(mat1)
            if newS<bestS:
                bestIndex=featIndex
                bestValue=splitVal
                bestS=newS
    # 根据运行结果找到合适的叶子节点 feat & val
    # 当S和bestS之间差距满足最小下降值  则不再进行切分
    if (S-bestS)<tolS:
        print ("bbb")
        print (S,bestS,tolS)
        return None,leafType(dataSet)
    # 当两边的切分样本  有一方小于4个  则不再进行切分
    if(shape(mat0)[0]<tolN) or (shape(mat1)[0]<tolN):
        print ("ccc")
        print (shape(mat0)[0])
        print (shape(mat1)[0])
        print (tolN)
        return None,leafType(dataSet)
    return bestIndex,bestValue
        
## 创建树形结构 参数(数据集,创建叶子节点的函数,计算误差的函数,(用户定义)树构建所需元祖)
def createTree(dataSet,leafType=regLeaf,errType=regErr,ops=(0,4)):
    feat,val=chooseBestSplit(dataSet,leafType,errType,ops)
    # 直到无法区分为止
    if feat==None:
        return val
    # 定义返回值--二叉树
    retTree={}
    # 定义分支节点的属性和值
    retTree['spInd']=feat
    retTree['spVal']=val
    # 用binSplitDataSet将数据分割成两个部分
    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):
    # 是的话,就返回true,表明这个是个有叶子节点的节点
    return (type(obj).__name__=='dict')

## 递归方式获取当前值
def getMean(tree):
    if isTree(tree['left']):
        tree['left']=getMean(tree['left'])
    if isTree(tree['right']):
        tree['right']=getMean(tree['right'])
    return (tree['left']+tree['right'])/2.0
## 剪枝处理
def prune(tree, testData):
    # 如果test的目标值为0,那么根据数结构,获取左右数据
    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'])
        # 合并后的节点存储值
        treeMean=(tree['left']+tree['right'])/2.0
        # 重新生成合并前的和合并后的误差值,进行判断
        errorNoMerge=sum(power(lSet[:,-1]-tree['left'],2))+sum(power(rSet[:,-1]-tree['right'],2))
        errorMerge=sum(power(testData[:,-1]-treeMean,2))
        #
        if errorMerge<errorNoMerge:
            print "merging"
            return treeMean
        else:
            return tree
    else:
        return tree

 七、模型树

回归树就是之前提到的用节点来进行划分,模型树是用线性函数来进行划分。回归树的叶节点是节点数据标签值的平均值,而模型树的节点数据是一个线性模型(可用最简单的最小二乘法来构建线性模型)。

把一个叶节点定义为线性函数能够更容易的将树型结构缩减,其解释性也是由于回归树的特点之一。

回归树种用的是均值记录叶子节点信息,方差和记录误差。在模型树种使用不一样的概念。

## 线性回归解决器
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("abc") 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))

哇,可是线性回归又是怎么回事呢,别急,代码中给到的,一会儿会整理。

补充:线性回归(当然会单列专题来说这个事情)

利用线性模型可以尝试去搞一个线性组合来进行预测,函数形式如下:

为了让线性模型尽可能的完善,我们需要尽可能的减少均方误差:

在线性回归中,最小二乘法就是尝试去找一条直线,让所有的样本到这个直线上的欧式距离之和最小,也就是上述公式,为了让其得到最小值,我们通常需要对式子对未知数w向量和求导并设置为0可以进行求解。

为了方便讨论我们通常将w向量和b放在一起进行讨论,设定一个全新的矩阵D,D的前n-1列恒定为属性值,最后一列设定初始值为1,实际上就是wx+b的形式,因为b的参数是1嘛,因此得到的误差函数为:

求导并另其为0,得到线性回归公式:

这个也就是上面公式的由来,啧啧

 

 

 

 

 

 

 

    


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM