什么是CART?
CART是英文Classification And Regression Tree的简写,又称为分类回归树。从它的名字我们就可
以看出,它是一个很强大的算法,既可以用于分类还可以用于回归,所以非常值得我们来学习。
CART算法使用的就是二元切分法,这种方法可以通过调整树的构建过程,使其能够处理连续型变量。
具体的处理方法是:如果特征值大于给定值就走左子树,否则就走右子树。
首先找到最佳的列来切分数据集,每次都执行二元切分法,如果特征值大于给定值就走左子树,否则就走右子树,当节点不能再分时就将该节点保存为叶节点。
这里我们介绍回归树和模型树。
回归树:叶子结点为特征值的平均值。
模型树:叶子结点为线性方程。
自定义回归树的数据为整条数据,即特征列后面紧跟目标列。
主要函数:
binSplitDataSet(dataSet, feature, value)#根据特征切分数据集合 errType(dataSet)#计算总方差:均方差*样本数 leafType(dataSet)#生成叶节点 chooseBestSplit(dataSet, leafType=leafType, errType=errType, ops = (1,4))#找到数据的最佳二元切分方式函数 createTree(dataSet, leafType = leafType, errType = errType, ops = (1, 4))#树构建函数
手动实现回归树code:
""" 函数说明:根据特征切分数据集合 参数说明: dataSet:原始数据集 feature:待切分的特征索引 value:该特征的值 返回: mat0:切分的数据集合0 mat1:切分的数据集合1 """ def binSplitDataSet(dataSet, feature, value): mat0 = dataSet.loc[dataSet.iloc[:,feature] > value,:] mat0.index = range(mat0.shape[0]) mat1 = dataSet.loc[dataSet.iloc[:,feature] <= value,:] mat1.index = range(mat1.shape[0]) return mat0, mat1 #计算总方差:均方差*样本数 def errType(dataSet): var= dataSet.iloc[:,-1].var() *dataSet.shape[0] return var #生成叶节点 def leafType(dataSet): leaf = dataSet.iloc[:,-1].mean() return leaf """ 函数说明:找到数据的最佳二元切分方式函数 参数说明: dataSet:原始数据集 leafType:生成叶结点函数 errType:误差估计函数 ops:用户定义的参数构成的元组 返回: bestIndex:最佳切分特征 bestValue:最佳特征值 """ def chooseBestSplit(dataSet, leafType=leafType, errType=errType, ops = (1,4)): #tolS允许的误差下降值,tolN切分的最少样本数 tolS = ops[0]; tolN = ops[1] #如果当前所有值相等,则退出。(根据set的特性) if len(set(dataSet.iloc[:,-1].values)) == 1: return None, leafType(dataSet) #统计数据集合的行m和列n m, n = dataSet.shape #默认最后一个特征为最佳切分特征,计算其误差估计 S = errType(dataSet) #分别为最佳误差,最佳特征切分的索引值,最佳特征值 bestS = np.inf; bestIndex = 0; bestValue = 0 #遍历所有特征列 for featIndex in range(n - 1): #原始数据集是带标签的,特征个数就是n-1 colval= set(dataSet.iloc[:,featIndex].values) #提取出当前切分列的所有取值 #遍历所有特征值 for splitVal in colval: #根据特征和特征值切分数据集 mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal) #如果数据少于tolN,则退出 if (mat0.shape[0] < tolN) or (mat1.shape[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 (mat0.shape[0] < tolN) or (mat1.shape[0] < tolN): return None, leafType(dataSet) #返回最佳切分特征和特征值 return bestIndex, bestValue """ 函数功能:树构建函数 参数说明: dataSet:原始数据集 leafType:建立叶结点的函数 errType:误差计算函数 ops:包含树构建所有其他参数的元组 返回: retTree:构建的回归树 """ def createTree(dataSet, leafType = leafType, errType = errType, ops = (1, 4)): #选择最佳切分特征和特征值 col, value = chooseBestSplit(dataSet, leafType, errType, ops) #如果没有特征,则返回特征值 if col == None: return value #回归树 retTree = {} #储存树的信息 retTree['spInd'] = col retTree['spVal'] = value #分成左数据集和右数据集 lSet, rSet = binSplitDataSet(dataSet, col, value) #创建左子树和右子树 retTree['left'] = createTree(lSet, leafType, errType, ops) retTree['right'] = createTree(rSet, leafType, errType, ops) return retTree
回归树的SKlearn实现:
数据集:
from sklearn.tree import DecisionTreeRegressor from sklearn import linear_model #用来训练的数据 x = (ex0.iloc[:,1].values).reshape(-1,1) y = (ex0.iloc[:,-1].values).reshape(-1,1) # 训练模型 model1 = DecisionTreeRegressor(max_depth=1) model2 = DecisionTreeRegressor(max_depth=3) model3 = linear_model.LinearRegression() model1.fit(x, y) model2.fit(x, y) model3.fit(x, y) # 预测 X_test = np.arange(0, 1, 0.01).reshape(-1,1) y_1 = model1.predict(X_test) y_2 = model2.predict(X_test) y_3 = model3.predict(X_test) # 可视化结果 plt.figure() #创建画布 plt.scatter(x, y, s=20, edgecolor="black",c="darkorange", label="data") plt.plot(X_test, y_1, color="cornflowerblue",label="max_depth=1", linewidth=2) plt.plot(X_test, y_2, color="yellowgreen", label="max_depth=3", linewidth=2) plt.plot(X_test, y_3, color='red', label='liner regression', linewidth=2) plt.xlabel("data") plt.ylabel("target") plt.title("Decision Tree Regression") plt.legend() plt.show()
效果:
用树来对数据进行建模,除了把叶节点简单地设定成常数值之外,还有一种方法是把叶节点设定为分段线性函数,这里的分段线性函数(piecewise linear)指的是模型由多个线性片段组成。
在前面讲的回归树中,每个叶节点中包含的是单个值;下面我们要讲的这种模型树,每个叶节点中包含的就是一个线性方程。
手动实现模型树code:
""" 函数功能:测试输入变量是否是字典类型,返回布尔类型的结果 """ def isTree(obj): return type(obj).__name__=='dict' """ 函数功能:计算特征矩阵、标签矩阵、回归系数 参数说明: dataSet:原始数据集 返回: ws:回归系数 X:特征矩阵(第一列增加x0=1) Y:标签矩阵 """ def linearSolve(dataSet): m,n = dataSet.shape con = pd.DataFrame(np.ones((m,1)))#在第一列增加一列常数值X0=1 conX = pd.concat([con,dataSet.iloc[:,:-1]],axis=1,ignore_index=True) X = np.mat(conX) Y = np.mat(dataSet.iloc[:,-1].values).T xTx = X.T*X if np.linalg.det(xTx) == 0: raise NameError('奇异矩阵无法求逆,请尝试增大tolN,即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 err = sum(np.power(Y-yHat,2)) return err
#利用回归树的createTree函数构建模型树 createTree(exp2,modelLeaf,modelErr,(1, 10))
回归树和模型树的预测函数:
#回归树叶节点预测函数 def regTreeEval(model,inData): return model #模型树叶节点预测函数 def modelTreeEval(model,inData): n = len(inData) X = np.mat(np.ones((1,n+1))) #增加一列常数项x0=1,放在第一列 X[:,1:n+1] = inData return X*model
预测结果:
""" 函数功能:返回单个测试数据的预测结果 参数说明: 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) """ 函数功能:返回整个测试集的预测结果 参数说明: tree:字典形式的树 testData:测试集 modelEval:叶节点预测函数 返回: yHat:每条数据的预测结果 """ def createForeCast(tree, testData, modelEval = regTreeEval): m = testData.shape[0] yHat = np.mat(np.zeros((m,1))) for i in range(m): inData = testData.iloc[i,:-1].values yHat[i,0]= treeForeCast(tree,inData,modelEval) return yHat
回归树的预测代码:
#创建回归树 regTree = createTree(biketrain,ops=(1,20)) #回归树预测结果 yHat = createForeCast(regTree,biketest, regTreeEval) #计算相关系数R2 np.corrcoef(yHat.T,biketest.iloc[:,-1].values)[0,1] #计算均方误差SSE sum((yHat.A.flatten()-biketest.iloc[:,-1].values)**2)
模型树的预测代码:
#创建模型树 modelTree = createTree(biketrain, modelLeaf, modelErr, ops=(1,20)) #模型树预测结果 yHat1 = createForeCast( modelTree, biketest, modelTreeEval) #计算相关系数R2 np.corrcoef(yHat1.T,biketest.iloc[:,-1].values)[0,1] #计算均方误差SSE sum((yHat1.A.flatten()-biketest.iloc[:,-1].values)**2)
标准线性回归:
#标准线性回归 ws,X,Y = linearSolve(biketrain) #在第一列增加常数项1,构建特征矩阵 testX = pd.concat([pd.DataFrame(np.ones((biketest.shape[0],1))),biketest.iloc[:,:-1]], axis=1,ignore_index = True) testMat = np.mat(testX) #标准线性回归预测结果 yHat_2 = testMat*ws #相关系数R2 R2_2 = np.corrcoef(yHat_2.T,biketest.iloc[:,-1].values)[0,1] #均方误差SSE SSE_2 = sum((yHat_2.A.flatten()-biketest.iloc[:,-1].values)**2)