1、分类与回归树
分类与回归树(Classification and Regression Trees, CART),既可用于分类也可用于回归。
本文基于机器学习实战这本书,将构建两种树,回归树-其每个叶节点包含单值
模型树-其每个叶节点是一个线性方程。这也就说CART既能分类又能回归的原因。
2、连续与离散型特征树的构建
回顾前面的决策树进行分裂,决策树不断江苏局切分小数据集,知道目标变量完全相同或者数据不能再切分为止。决策树是一种贪心算法,他要在给定时间内做出最佳选择。但并不关心达到全局最优。ID3算法是每次选取当前最佳的特征来切割数据,并按照该特征所有可能取值来切分,ID3算法存在两个问题,一个是切分过于迅速(一旦某个特征切分后,该特征在算法执行过程中以后将不再执行),另一种是,他不能切分连续性特征。只有事先将连续特征转化为离散才能使用。但这种转化过程会破坏连续型变量的内在性质。
而是用二元切分则易于对数构建过程进行调整以处理连续型特征。具体方法是:
如果特征值大于给定值就进入左子树,否则进入右子树。卧槽…………好像二叉排序树……有木有…………
首先设计一个字典来存储树的结构
1、待切分的特征
2、待切分的特征值
3、右子树。当不再需要切分的时候,也可以是单值
4、左子树与右子树类似
找到最佳的待切分特征:
如果该节点不能再分,将该节点存为叶节点
执行二元切分
在右子树调用creatTree()方法…………就是递归啦
在左子树调用creatTree()方法
3、构建树
4、树剪枝
5、模型树
#encoding:utf-8
import numpy as np
from numpy import *
#导入文件
def loadDataSet(fileName) :
dataMat = []
fr = open(fileName)
for line in fr.readlines() :
curLine = line.strip().split('\t')
fltLine = map(float, curLine)
dataMat.append(fltLine)
return dataMat
# dataSet: 数据集合
# feature: 待切分的特征
# value: 该特征的某个值
# 该函数有三个参数, 数据集合、待切分的特征和该特征的某个值
def binSplitDataSet(dataSet, feature, value) :
mat0 = dataSet[nonzero(dataSet[:,feature] > value)[0],:]
mat1 = dataSet[nonzero(dataSet[:,feature] <= value)[0],:]
return mat0, mat1
#回归树用于负责生成叶节点。该函数的叶节点代表的是目标变量的均值
def regLeaf(dataSet):
return mean(dataSet[:,-1])
#误差估计函数
'''该函数在给定数据计算目标变量的平方误差,'''
def regErr(dataSet):
return var(dataSet[:,-1])*shape(dataSet)[0]
'''模型树中负责生成的叶节点。 叶节点中存储的是该分类的回归系数'''
def modelLeaf(dataSet):
ws,X, Y= linerSlove(dataSet)
return ws
'''模型树中计算yHat与Y之间的平方误差'''
def modelErr(dataSet):
ws,X,Y = linerSlove(dataSet)
yHat = X*ws
return sum(power(Y-yHat, 2))
'''树回归中预测返回的叶节点'''
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)
'''最好的切分方式,既能切分回归树又能切分模型树'''
#dataSet数据集,leafType 选择生成叶子节点的函数 errType选择计算误差类型的函数
#ops[0]代表的容许误差下降值,ops[1]代表切分最少的样本数
#其实ops是用户用于控制函数停止的时机
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:#set([..,..,..])里面接收列表
return None,leafType(dataSet)
m, n = shape(dataSet)
S = errType(dataSet)#当前的误差值
bestS = inf; bestIndex = 0.0;bestValue = 0.0
for i in range(n - 1):#遍历所有的特征值
for splitVal in set(dataSet[:,i].T.tolist()[0]):#遍历特征中的 不同类型,寻找最优
lSet, rSet = binSplitDataSet(dataSet, i, splitVal)
if (shape(lSet)[0] < tolN) or (shape(rSet)[0] < tolN):#如果样本数少于用户指定的数目,则不切分,一定程度上防止过拟合
continue
newS = errType(lSet) + errType(rSet)#切分后的误差值
if newS < bestS:#寻找最优解
bestS = newS
bestIndex = i
bestValue = splitVal
if (S - bestS) < tolS:#如果最优解的变化的误差值都小于规定的容忍度,那么不用继续划分直接生成叶节点
return None, leafType(dataSet)
lSet, rSet = binSplitDataSet(dataSet, bestIndex, bestValue)#如果最优解切分的数目过小也是直接生成叶节点
if (shape(lSet)[0] < tolN) or (shape(rSet)[0] < tolN):
return None, leafType(dataSet)
return bestIndex,bestValue#如果符合用户指定要求那么返回最佳的特征值,和特征值中最佳的切割点
'''创建回归树或者模型树'''
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 = binSplitDataSet(dataSet, feat, val)#切割
retTree['left'] = createTree(lSet, leafType, errType,ops)#左子树继续递归调用
retTree['right'] = createTree(rSet, leafType, errType,ops)#右子树继续递归调用
return retTree
#----------------树剪枝--------------------
def isTree(obj):#判断是不是词典
return (type(obj).__name__=='dict')
'''函数getMean()是一个递归函数,它从上往下遍历树直到叶节点为止。如果找到两个叶节点
则计算他们的平均值。该函数对树进行“塌陷处理”(返回树的平均值),prunce中调用函数时候应该明确'''
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
#tree待剪树,testData测试数据
'''注意 树的剪枝是用测试集来协助剪枝的,这里的剪枝是在回归树中的应用'''
def prune(tree, testData):
if shape(testData)[0] == 0: return getMean(tree)#如果测试集是空的,那么返回这个树杈的均值(并且把这些树杈合并了相当于剪枝了)
if (isTree(tree['right']) or isTree(tree['left'])):#如果左右子树有树杈的存在,那么需要切割,继续递归调用
lSet, rSet = binSplitDataSet(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 = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
errorNoMerge = 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 < errorNoMerge: #如果误差小那么合并
print "merging"
return treeMean
else: return tree
else: return tree
'''模型树'''
#标准线性回归
def linerSlove(dataSet):
m, n =shape(dataSet)
X = mat(ones((m, n))); Y = mat(ones((m, 1)))
X[:,1:n] = dataSet[:,0:n-1];Y = dataSet[:,-1]
XTX = X.T*X
if linalg.det(XTX) == 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
'''用树回归进行预测'''
#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)
#预测整个测试集
def createForCast(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
#------------树回归与树剪枝----------------------
# data1 = mat(loadDataSet('ex2.txt'))
# print shape(data1)
# data = mat(loadDataSet('ex2test.txt'))
# print shape(data)
# mytree = createTree(data1,ops = (0,1))
# print mytree
# print prune(mytree, data)
# import matplotlib.pyplot as plt
# plt.plot(data[:,0],data[:,1],'o')
# plt.plot(data1[:,0],data1[:,1],'o')
# plt.show()
#--------------模型树-----------------------------
# data = mat(loadDataSet('exp2.txt'))
# mytree = createTree(data,leafType = modelLeaf,errType = modelErr, ops = (1, 10))
# print mytree
#--------------比较回归树、模型树、标准的线性回归--------
#回归树的预测
# tainSet = mat(loadDataSet('bikeSpeedVsIq_train.txt'))
# testSet = mat(loadDataSet('bikeSpeedVsIq_test.txt'))
# myTree = createTree(tainSet,ops=(1,20))
# yHat = createForCast(myTree, testSet[:,0])
# print '树回归',corrcoef(yHat, testSet[:,-1], rowvar=0)[0,1]
# #模型树的预测
# myTree = createTree(tainSet,leafType = modelLeaf,errType = modelErr,ops=(1,20))
# yHat = createForCast(myTree, testSet[:,0],modelEval = modelTreeEval)
# print '模型树',corrcoef(yHat, testSet[:,-1], rowvar=0)[0,1]
# #标准线性回归预测
# ws,x,y = linerSlove(tainSet)
# yHat[:,0] = testSet[:,0]*ws[1, 0]+ws[0,0]
# print '标准线性回归',corrcoef(yHat, testSet[:,-1], rowvar=0)[0,1]