树回归
优点:可以对复杂和非线性的数据建模
缺点:结果不易理解
适用类型:数值型、标称型
决策树构建时使用的ID3算法不能直接处理连续型特征;故使用二元切分法以处理连续型特征。具体方法:
如果特征值大于给定值就走左子树,否则就走右子树。
CART(分类回归树)是十分著名的树构建算法,它使用二元切分法来处理连续型变量,对其稍作修改就可以处理回归问题,选用其他方法代替香农熵即可。
1、树的构建
将使用一部字典来存储树的数据结构,该字典包含:待切分的特征,待切分的特征值,右子树,左子树。
本文将构建两种树:回归树,其每个叶节点包含单个值;模型树,其每个叶节点包含一个线性方程。
构建
from numpy import *
class treeNode():#树的结构
def __init__(self,feat,val,right,left):
featureToSplitOn=feat
valueOfSplit=val
rightBranch=right
leftBranch=left
def loadDataSet(filename):#加载数据
dataMat=[]
f=open(filename)
lines=f.readlines()
for line in lines:
line=line.strip().split()
fltline=map(float,line)
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 createTree(dataSet,leafType=regLeaf,errType=regErr,ops=(1,4)):#数据集和3个可选参数,决定树的类型
#leafType给出建立叶节点的函数;errType代表误差计算函数;ops是一个包含树构建所需其他参数的元组
feat,val=chooseBestSplit(dataSet,leafType,errType,ops)
#如果满足停止条件,返回None和某类模型的值;否则创建新字典将数据集分成两份
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
2、回归树
叶节点为常数值
连续型数值的混乱度计算:
计算所有数据的均值,然后计算每条数据的值到均值的绝对值(平方值),即平方误差的总值(总方差),可以通过均方差乘以样本数得到。
回归树的切分函数
def regLeaf(dataSet):#生成叶节点,回归树中为目标变量的均值
return mean(dataSet[:,-1])
def regErr(dataSet):#误差估计函数,返回总方差
return var(dataSet[:,-1])*shape(dataSet)[0]
#核心函数,目的是找到数据的最佳二元切分方式
def chooseBestSplit(dataSet,leafType=regLeaf,errType=regErr,ops=(1,4)):
tolS=ops[0];tolN=ops[1]#用户指定参数,用于控制函数的停止时机
#tolS是容许的误差下降值;tolN是切分的最小样本数
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 splitVal in set(dataSet[:,featIndex]):
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
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#返回切分特征和特征值
3、树剪枝
一棵树如果节点过多,表明该模型可能对数据进行了“过拟合”。可以使用交叉验证来发现。
通过降低树的复杂度来避免过拟合的过程称为剪枝。
(1)预剪枝
缺点:对输入参数tolS/tolN非常敏感,用户需要不断修改停止条件来取得合理结果。
(2)后剪枝
需要将数据集分成测试集和训练集。
首先指定参数,使得构建的树足够大、足够复杂,便于剪枝。
接下来从上而下找到叶节点,用测试集来判断将这些叶节点合并是否能降低测试误差。
如果是,则合并叶节点。
回归树剪枝函数
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
后剪枝可能不如预剪枝有效,一般为了寻求最佳模型可以同时使用两种剪枝技术。
4、模型树
叶节点为分段线性函数,分段线性指模型由多个线性片度组成。
模型树的可解释性优于回归树,也具有更高的预测准确度。
误差计算:对于给定的数据集,先用线性模型对它进行拟合,然后计算真实目标值与模型预测值间的差值,最后将这些差值的平方求和即可。
模型树的叶子节点生成函数
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,\ntry 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))
5、用树回归进行预测
树回归方法在预测复杂数据时会比简单的线性模型更有效。
预测
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
6、多元自适应回归样条(MARS)
该方法以样条函数的张量积作为基函数,分为前向过程、后向剪枝过程与模型选取三个步骤。
优势在于能够处理数据量大、维度高的数据,而且计算快捷、模型精确。
在前向过程中,通过自适应的选取节点对数据进行分割,每选取一个节点就生成两个新的基函数,前向过程结束后生成一个过拟合的模型。后向剪枝过程中在保证模型准确度的前提下,删除过拟合模型中对模型贡献度小的基函数,最后选取一个最优的模型作为回归模型。
MARS是回归的自适应过程,非常适合高维问题(比如,存在大量的输入)。可以从两个角度来理解它,首先,它可以看成是逐步线性回归的推广,其次,也可以看成是为了提高CART在回归中的效果而进行的改进。