上一章介绍的线性回归,创建的模型需要拟合所有样本点(局部加权线性回归除外)。当数据拥有众多特征并且特征之间关系
十分复杂时,构建全局模型的就非常困难,且实际生活中很多问题都是非线性的,不可能使用全局线性模型来拟合任何数据。
那么有一种方法,将数据集切分成很多份容易建模的数据,然后利用线性回归技术来建模,如果切分后仍然难以模拟线性模型
就继续切分。这种切分方式,树结构和回归的结合。
本章介绍两种回归树和模型树两种树构建算法:
回归树(regression tree) 其每个节点包含单个值
模型树(model tree) 其每个节点包含一个线性方程
1 回归树
复杂数据的局部性建模:
前面介绍的决策树,做法是每次选取当前最佳的特征来分割数据,并按照该特征的所有可能取值来切分。比如特征有4种取值,
那么数据分成4份。且按某特征切分后,该特征无法再起作用。所有观点认为这个切分过于迅速。除外决策树不能直接处理连续性特征,
必须先把连续型特征转化成离散型。这样会破坏连续型特征。
而本章使用树构建过程为二元切分法:如果特征值大于给定的数据走左子树,否则就走右子树。下面介绍使用广泛的树构建
算法CART
1.1CART
CART(Classification And Regression Trees,分类回归树),该算法即可以用于分类还可以用于回归,后面再看树减枝技术
(主要目的是防止树的过拟合)
CART使用的二元切分方法,createtree()的大致伪代码如下:
找到最佳待切分特征
如果该节点不能再分,将该节点存为叶节点
执行二元切分
在右子树调动createtree函数
在左子树调动createtree函数
代码
代码说明:
loadDataSet - 构建数据集,该函数读取一个tab键为分隔符的文件,将每行内容保存成一组浮点数。
binSplitDataSet - 指定特征及阈值把一个矩阵分成两个
createTree - 创建一个数,递归函数。先找到最佳分类特征(chooseBestSplit),按特征值阈值一分二,构建左子树和右子树。直到
无法分为止。
regLeaf - 负责生成叶子节点,chooseBestSplit确定不再数据进行分割时调用该函数来得到叶节点的模型。在回归树中,即取目标
变量(y)的均值。用于预测测试目标的值。
chooseBestSplit - 寻找最佳分类特征及阈值,其中参数tolS和tolN 用于控制函数的停止时机。tolS为是允许的误差下降值,tolN是切分
的最小样本数。思路如下:
regErr - 给定数据上目标变量的平方误差
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],:]
mat1 = dataSet[nonzero(dataSet[:,feature] <= value)[0],:]
#print mat0,mat1
return mat0,mat1
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].T.tolist())[0]):
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) #exit cond 2
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#returns the best feature to split on
#and the value used for that split
1.2 减枝算法
上面提到过如果树节点过多,可能会过拟合。通过降低树的复杂度来避免过拟合称为剪枝(pruning)。其实前面构建树时已经
使用过,chooseBestSplit 中止条件tolS和tolN是一种所谓的预剪枝操作。另一种形式的剪枝需要测试集和训练集,称作为后剪枝。
后减枝算法
原理是先用训练数据建立一个最复杂的回归树(误差为0,叶子节点分到每一个数值)。然后从上而下找到叶节点,用测试数据来
判断将这些叶节点合并后是否降低误差,如果是的话合并。
伪代码如下:
基于已有的树切分测试数据:
如果存在任意子集是一棵树,则再该子集递归剪枝过程
计算将当前两个叶节点合并后误差
计算不合并的误差
如果合并会降低误差的话,就将叶节点合并
2 模型树
节点不是简单的数值,取代的是一些线性模型
区别就是叶子节点不是数值,一个线性函数,modelLeaf,算法使用回顾算法中的
误差算法:平方误差