CART算法只做二元切分,因此每个树节点包含待切分的特征,待切分的特征值,左子树,右子树。
import numpy as np class treeNode(object): def __init__(self, feat, val, right, left): featureToSplitOn = feat valueOfSplit = val rightBranch = right leftBranch = left
给定特征和特征值的数据切分函数
1 def binSplitDataSet(dataSet, feature, value): 2 mat0 = dataSet[np.nonzero(dataSet[:, feature] > value)[0], :] 3 mat1 = dataSet[np.nonzero(dataSet[:, feature] <= value)[0], :] 4 return mat0, mat1
树构建函数,包含4个参数,数据集和其他3个可选参数。这些可选参数决定了树的类型:leafType给出建立叶节点的函数;errType代表误差计算函数;ops是一个包含树构建所需其他参数的元组。
createTree是一个递归函数。该函数首先尝试将数据集分成两个部分,切分由函数chooseBestSplit()完成。如果满足停止条件,chooseBestSplit将返回None和某类模型的值。如果构建的是回归树,该模型是一个常数。如果是模型树,模型是一个线性方程。如果不满足停止条件,chooseBestSplit将创建一个新的Python字典并将数据集分成两份,在这两份数据集上将分别继续递归调用createTree函数。
1 def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)): 2 feat, val = chooseBestSplit(dataSet, leafType, errType, ops) 3 if feat == None: 4 return val 5 retTree = {} 6 retTree["spInd"] = feat 7 retTree["spVal"] = val 8 lSet, rSet = binSplitDataSet(dataSet, feat, val) 9 retTree["left"] = createTree(lSet, leafType, errType, ops) 10 retTree["right"] = createTree(rSet, leafType, errType, ops) 11 return retTree
1.回归树
chooseBestSplit只需完成两件事:用最佳方式切分数据集和生成相应地叶节点。其包含:dataSet、leafType、errType和ops。leafType是对创建叶节点的函数的引用,errType是计算总方差函数的引用。chooseBestSplit函数的目标是找到数据集切分的最佳位置。它遍历所有特征及其所有特征值来找到使误差最小化的切分阈值。
1 def regLeaf(dataSet): 2 return np.mean(dataSet[:, -1]) # 数据最后一列为预测值 3 4 def regErr(dataSet): 5 return np.var(dataSet[:, -1]) * np.shape(dataSet)[0] 6 7 def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)): 8 tolS = ops[0] 9 tolN = ops[1] 10 if len(set(dataSet[:, -1].tolist())) == 1: 11 return None, leafType(dataSet) 12 m, n = np.shape(dataSet) 13 S = errType(dataSet) 14 bestS = np.inf 15 bestIndex = 0 16 bestValue = 0 17 for featIndex in range(n-1): 18 for splitVal in set(dataSet[:, featIndex]): 19 mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal) 20 if (np.shape(mat0)[0] < tolN) or (np.shape(mat1)[0] < tolN): 21 continue 22 newS = errType(mat0) + errType(mat1) 23 if newS < bestS: 24 bestIndex = featIndex 25 bestValue = splitVal 26 bestS = newS 27 if (S - bestS) < tolS: # 如果误差减少不大则退出 28 return None, leafType(dataSet) 29 mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue) 30 if (np.shape(mat0)[0] < tolN) or (np.shape(mat1)[0] < tolN): # 如果切分出的数据集很小则退出 31 return None, leafType(dataSet) 32 return bestIndex, bestValue
2.模型树
用树来对数据建模,除了把叶节点简单的设定为常数值之外,还有一种方法是把叶节点设定为分段线性函数,这里所谓的分段线性是指模型由多个线性片段组成。
为了找到最佳切分应该怎样计算误差呢?对于给定的数据集,应该先用线性的模型对它进行拟合,然后计算真实的目标值与模型预测值间的差值。最后将这些差值的平方求和就得到了所需的误差。
1 def linearSolve(dataSet): 2 m, n = np.shape(dataSet) 3 X = np.mat(np.ones((m, n))) 4 Y = np.mat(np.ones((m, 1))) 5 X[:, 1:n] = dataSet[:, 0:n-1] 6 Y = dataSet[:, -1] 7 xTx = x.T * X 8 if np.linalg.det(xTx) == 0: 9 raise NameError('This matrix is singular, cannot do inverse, try increasing the second value of ops') 10 ws = xTx.I * (X.T * Y) 11 return ws, X, Y 12 13 def modelLeaf(dataSet): 14 ws, X, Y = linearSolve(dataSet) 15 return ws 16 17 def modelErr(dataSet): 18 ws, X, Y = linearSolve(dataSet) 19 yHat = X * ws 20 return np.sum(np.power(Y - yHat, 2))