一:背景
线性回归包含了强大的方法,但这些方法创建的模型需要拟合所有的样本(局部加权线性回归除外)。当数据拥有众多特征并且特征之间的关系十分复杂时,构建全局模型的想法就显得太难了。而且现实生活中很多数据都是非线性的,不可能使用全局线性模型来拟合这些数据。
一种可行的方法时将数据集切分成很多分容易建模的数据,然后可利用线性回归技术来建模。如果切分后的仍然难以拟合线性模型就继续切分。在这个切分方式背景下。树结构和回归方法就十分有用。
CART(Classification And Regression Tree,即分类回归树),该算法既可以用于分类也可以用于回归。下面将介绍复杂数据的局部性建模过程,并分析其中的缺陷,然后对比引出CART算法建树过程。
二:复杂数据的局部性建模
在利用决策树进行分类时,决策树不断将数据切分成小数据集。知道所有目标变量完全相同,或者数据不能再切分为止。实际上,这是一种贪心算法,它要在给定时间内做出最佳选择,而并没有关心能否达到全局最优。
树回归
优点 | 可以对复杂和非线性的数据建模 |
缺点 | 结果不容易理解 |
适用数据类型 | 数值型和标称型数据 |
在使用ID3算法构建决策树模型时,ID3的做法是每次选取当前最佳的特征类分割数据,并按照该特征的所有可能进行切分。也就是说特征有多少种取值,那么数据将被切分成多少份。一旦按某个特征切分后,该特征在之后的算法的执行过程中将不会在起作用。因此,这种切分方法过于迅速。所以又有一种二元切分方法,即每次把数据切分成两份。如果数据的某个特征值等于切分所要求的值,那么这些数据就进入数的左子树,否则进入树的右子树。
除了切分过于迅速之外,ID3算法还存在另外一种问题,它不能直接处理连续型特征,只有预先将连续型特征转换成离散型特征,才可以使用ID3算法。但是这种转换过程往往会破坏连续型数据特征内在的性质。而使用二元切分方法则容易对树构建过程进行调整以便处理连续型特征。具体的处理方法是:如果特征值大于给定的阈值就划分到左子数,佛则划分到右子树。
三:CART构建过程
下面将实现CART算法和回归树。回归树和分类树的思路类似,但叶节点的数据类型不是离散型而是连续型。
树回归的一般步骤:
- 收集数据:采用任意方法收集
- 准备数据:需要署执行的数据,标称型数据应该映射成二值型数据
- 分析数据:绘出数据的二维可视化显示结果,以字典方式生成树
- 训练算法:大部分时间都花费在叶节点树模型的构建上
- 测试算法:使用册数数据类分析模型的效果
- 使用算法:使用训练处的树做预测,预测结果还可以用来做很多事情
下面将用Python 来实现CART算法用于实现回归过程的基础代码:
from numpy import *
def loadDataSet(fileName): #general function to parse tab -delimited floats
dataMat = [] #assume last column is target value
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = map(float,curLine) #map all elements to float()
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)):#assume dataSet is NumPy Mat so we can array filtering
feat, val = chooseBestSplit(dataSet, leafType, errType, ops)#choose the best split
if feat == None: return val #if the splitting hit a stop condition 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
第一个函数loadDataSet是用于导入数据集;
第二个函数binSplitDataSet,该函数有3个参数:数据集合,待切分的特征和该特征的某个值。在给定特征和特征值的情况下,该函数通过数组过滤方式将上述数据集合切分得到两个子集并返回。
第三个函数createTree是一个递归函数,该函数首先尝试将数据集分层两个部分。即大于特征值的左子树,和小于特征值的右子树。
四:将CART算法用于回归
当我们决定由树结构来进行数据切分时,很自然联想到一个问题:如何实现数据的切分?如何知道是否已经充分切分?
这些问题的答案取决于叶节点的建模方式,回归树假设叶节点是常数值,这种策略认为数据中的复杂关系可以用树结构来概括。
为成功构建以分段常数为叶节点的树,需要度两处数据的一致性。在学习利用决策树进行分类时,会在给定节点时,利用信息熵计算数据的混乱度。而在计算连续型数据时,则利用均值计算混乱度,即:首先计算所有数据的均值,然后计算每条数据的值到均值的差值,为了对正负差值同等看待,一般是由绝对值或者平方值来代替差值。注意:这里需要进一步计算平方误差的总值(总方差),总方差等于均方差乘以数据集中的样本个数来得到。
def regLeaf(dataSet):#returns the value used for each leaf
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]
#if all the target variables are the same value: quit and return value
if len(set(dataSet[:,-1].T.tolist()[0])) == 1: #exit cond 1
return None, leafType(dataSet) #如果所有值相等则退出
m,n = shape(dataSet)
#the choice of the best feature is driven by Reduction in RSS error from mean
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 the decrease (S-bestS) is less than a threshold don't do the split
if (S - bestS) < tolS:
return None, leafType(dataSet) #如果过误差减少不大则退出
mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): #exit cond 3
return None, leafType(dataSet)
return bestIndex,bestValue #如果切分出的数据集很小则退出
上述代码实现的任务如下:
1 对每个特征:
2 对每个特征值:
3.1 将数据集切分成两份
3.2 计算切分的误差
3.3 如果当前误差小于当前最小误差,那么将当前切分设定为最佳切分并更新最小误差,返回最佳切分的特征和阈值
五:总结
CART是基于但特征的,没有考虑特征之间的关系,因此不需要在初始时进行特征归一化处理。
CART假设决策树是二叉树,内部节点特征取值只有"是" 或者“否”,左分支是“是”,右分支是“否”
CART ---> Boosting Decision Tree ---> adaboost ---> GBDT ---> XGBoost ,它们的基函数都是决策树。
参考文献:
《机器学习》--周志华
《机器学习实战》--Peter Harrington