1. 两种树构建算法比较
ID3:
每次选取当前最优的特征来分割数据,并按照该特征所有可能取值来切分;一旦按某特征切分后,该特征在之后的算法执行过程中不会再起作用.
缺点: 不能处理连续型特征,除非事先将连续型特征转换成离散型,但转换过程破坏了连续型变量的内在性质.
CART;
二元切分法,每次把数据切成两份;若特征值大于给定值,就走左子树,反之,右子树.
2. 树构建
2.1 树存储
采用字典来存储树的数据结构,该字典包含以下4个元素:待切分的特征;待切分的特征值;右子树(当不再需要继续切分时,是单个值);左子树。
2.2 创建树
流程如下:找到最佳的待切分特征:
如果该节点不能再分,将该节点存为叶节点
执行二元切分
在左、右子树分别调用创建树的函数
<span style="font-size:18px;">#负责产生叶节点
def regLeaf(dataset):
return mean(dataset[:,-1])
#误差估计函数.计算目标变量的平均误差 总方差=均方差*样本数
def regErr(dataset):
return var(dataset[:,-1])*shape(dataset)[0]
#
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 = binarySplitData(dataset,feat,val)
retTree['left'] = createTree(lset,leaftype,errType,ops)
retTree['right'] = createTree(rset,leaftype,errType,ops)
return retTree</span>
2.3 创建回归树
(1)回归树:其叶节点是常数值
(2)选择最佳特征的依据:计算数据的混乱度:计算所有数据的平均值,然后计算每条数据到均值的差值的平方,这里需要的是总方差,即均方差乘以数据个数。
(3)如何寻找当前最佳切分特征和特征值:函数choosebest。其思想是:遍历所有的特征及其可能的取值来找到误差最小化的切分阈值。
在此函数中,有三种情况不会切分数据集:
- 如果数据集的值全部相等
- 若找到最优划分之后,但是划分后的数据集的误差相比划分前的数据集的误差减少不大时,即效果提升不明显时,不执行划分操作
- 若找到的最优划分切分得到的数据集很小时,不执行划分
(4)其伪代码如下:
对每个特征:
对当前特征可能的每个取值:
将数据分成两部分
计算切分误差
若当前误差小于当前最小误差,更新当前最小误差,及其最佳切分方式
返回最佳切分特征和特征值
实现代码如下:
<span style="font-size:18px;">#找到数据的最佳二元划分方式
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:
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 = binarySplitData(dataset,featindex,splitVal)
if (shape(mat0)[0] < toln) or (shape(mat1)[0] < toln):
continue
newS = errtype(mat0) + errtype(mat1)
if newS < bestS:
bestS = newS
bestindex = featindex
bestvalue = splitVal
#切分数据集后,效果提升不大,则不应进行切分操作而直接创建叶节点
if (S- bestS) < tols:
return None,leaftype(dataset)
mat0,mat1 = binarySplitData(dataset,bestindex,bestvalue)
if (shape(mat0)[0] < toln) or (shape(mat1)[0] < toln):
return None,leaftype(dataset)
return bestindex,bestvalue</span>
2.4 创建模型树
(1)模型树: 其叶节点是分段线性函数;分段线性指的是模型由多个线性片段组成
(2)如何找到最佳切分?对于给定的数据集,先用线性模型对它进行拟合,然后计算真实的目标值与模型预测值间的差值,最后将这些差值的平方求和作为所需的误差值。
其计算过程如下所示:
<span style="font-size:18px;">def linearSolve(dataSet): #helper function used in two places
m,n = shape(dataSet)
X = mat(ones((m,n))); Y = mat(ones((m,1)))#create a copy of data with 1 in 0th postion
X[:,1:n] = dataSet[:,0:n-1]; Y = dataSet[:,-1]#and strip out Y
xTx = X.T*X
if linalg.det(xTx) == 0.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
def modelLeaf(dataSet):#create linear model and return coeficients
ws,X,Y = linearSolve(dataSet)
return ws
def modelErr(dataSet):
ws,X,Y = linearSolve(dataSet)
yHat = X * ws
return sum(power(Y - yHat,2))</span>
3. 树剪枝(tree pruning)
(1)基本概念
过拟合:一棵树节点过多,且使用测试集验证过的。
剪枝:通过降低决策树的复杂度来避免过拟合的过程。
- 预剪枝(prepruning):即函数chooseBestSplit()中的不切分的那三个条件
- 后剪枝(postpruning):需要使用测试集和训练集
过程:首先指定参数,使得构建得到的树足够大,足够复杂,便于剪枝。然后从上向下找到叶节点,用测试集来判断将这些叶节点合并是否能够降低测试误差,若是的话就合并。
其伪代码如下:
基于已有的树切分测试数据:
如果存在任一子集是一棵树,则在该子集中地柜剪枝过程
计算将当前两个叶节点合并后的误差
计算不合并的误差
如果合并会降低误差的话,就将叶节点合并
其实现代码如下:
<span style="font-size:18px;">
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
#input:待剪枝的树与剪枝所需的测试数据
def prune(tree,testdata):
if shape(testdata)[0] == 0:
return getMean(tree)
if (isTree(tree['right']) or isTree(tree['left'])):
lset,rset = binarySplitData(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 = binarySplitData(testdata,tree['spInd'],tree['spVal'])
errorNmerge = 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 < errorNmerge:
print "merging"
return treeMean
else:
return tree
else:
return tree</span>
4. 模型评价
如何评价回归树、模型树、线性回归等模型的好坏呢?
比较客观的方法是计算相关系数。numpy.corrcoef()函数实现
5. 使用回归树和模型树进行预测
其实现代码如下:
<span style="font-size:18px;">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</span>