机器学习实战---决策树CART简介及分类树实现

一:对比分类树

CART回归树和CART分类树的建立算法大部分是类似的,所以这里我们只讨论CART回归树和CART分类树的建立算法不同的地方。
首先,我们要明白,什么是回归树,什么是分类树。

两者的区别在于样本输出:

如果样本输出是离散值,那么这是一颗分类树。

如果果样本输出是连续值,那么那么这是一颗回归树。

除了概念的不同,CART回归树和CART分类树的建立和预测的区别主要有下面两点:

1)连续值的处理方法不同
2)决策树建立后做预测的方式不同。

对于连续值的处理,我们知道CART分类树采用的是用基尼系数的大小来度量特征的各个划分点的优劣情况,这比较适合分类模型。

但是对于回归模型,我们使用了常见的和方差的度量方式。

CART回归树的度量目标是,对于任意划分特征A,对应的任意划分点s两边划分成的数据集D1和D2,求出使D1和D2各自集合的均方差最小,同时D1和D2的均方差之和最小所对应的特征和特征值划分点。

表达式为:

其中,c1为D1数据集的样本输出均值,c2为D2数据集的样本输出均值。

对于决策树建立后做预测的方式,上面讲到了CART分类树采用叶子节点里概率最大的类别作为当前节点的预测类别。而回归树输出不是类别,它采用的是用最终叶子的均值或者中位数来预测输出结果

除了上面提到了以外,CART回归树和CART分类树的建立算法和预测没有什么区别。

二:回归树的实现

(一)实现叶子节点均值计算

def regLeaf(data_Y):    #用于计算指定样本中标签均值表示回归y值
return np.mean(data_Y)

(二)实现计算数据集总方差

def regErr(data_Y): #使用均方误差作为划分依据
return np.var(data_Y)*data_Y.size  #np.var是求解平均误差,我们这里需要总方差进行比较

(三)实现数据集切分

def binSplitDataSet(data_X,data_Y,fea_axis,fea_val):    #进行数据集划分
dataGtIdx = np.where(data_X[:,fea_axis]>fea_val)
dataLgIdx = np.where(data_X[:,fea_axis]<=fea_val) return data_X[dataGtIdx],data_Y[dataGtIdx],data_X[dataLgIdx],data_Y[dataLgIdx]

(四)实现选取最优特征及特征值(含预剪枝处理)

def chooseBestSplit(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)):
"""
选取的最好切分方式,使用回调方式调用叶节点计算和误差计算,函数中含有预剪枝操作
:param data_X: 传入数据集
:param data_Y: 传入标签值
:param leafType: 要调用计算的叶节点值 --- 虽然灵活,但是没必要
:param errType: 要计算误差的函数,这里是均方误差 --- 虽然灵活,但是没必要
:param ops: 包含了两个重要信息, tolS tolN用于控制函数的停止时机,tolS是容许的误差下降值,误差小于则不再切分,tosN是切分的最少样本数
:return:
"""
m,n = data_X.shape
tolS = ops[]
tolN = ops[]
#之前都是将判断是否继续划分子树放入createTree方法中,这里可以提到chooseBestSplit中进行判别。
#当然可以放入createTree方法中处理
if np.unique(data_Y).size == : #1.如果标签值全部相同,则返回特征None表示不需要进行下一步划分,返回叶节点
return None,leafType(data_Y) #遍历获取最优特征和特征值
TosErr = errType(data_Y) #获取全部数据集的误差,后面计算划分后两个子集的总误差,如果误差下降小于tolS,则不进行划分,返回该叶子节点即可(预剪枝操作)
bestErr = np.inf
bestFeaIdx = #注意:这里两个我们设置为0,而不是-,因为我们必须保证可以取到一个特征(后面循环可能一直continue),我们需要在后面进行额外处理
bestFeaVal =
for i in range(n): #遍历所有特征
for feaval in np.unique(data_X[:,i]):
dataGt_X,dataGt_Y,dataLg_X,dataLg_Y = binSplitDataSet(data_X,data_Y,i,feaval) #数据集划分
# print(dataGt_X.shape,dataLg_X.shape)
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: #不符合最小数据集,不进行计算
continue
concErr = errType(dataLg_Y)+errType(dataGt_Y)
# print(concErr)
if concErr < bestErr:
bestFeaIdx = i
bestFeaVal = feaval
bestErr = concErr #2.如果最后求解的误差,小于我们要求的误差距离,则不进行下一步划分数据集(预剪枝)
if (TosErr - bestErr) < tolS:
return None,leafType(data_Y) #3.如果我们上面的数据集本身较小,则无论如何切分,数据集都<tolN,我们就需要在这里再处理一遍,进行一下判断
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, bestFeaIdx, bestFeaVal) # 数据集划分
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: # 不符合最小数据集,不进行计算
return None,leafType(data_Y) return bestFeaIdx,bestFeaVal #正常情况下的返回结果

(五)实现决策树创建

def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)):   #建立回归树

    feaIdx,feaVal = chooseBestSplit(data_X,data_Y,leafType,errType,ops)
if feaIdx == None: #是叶子节点
return feaVal #递归建树
myTree = {}
myTree['feaIdx'] = feaIdx
myTree['feaVal'] = feaVal
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, feaIdx, feaVal) # 数据集划分
myTree['left'] = createTree(dataGt_X,dataGt_Y,leafType,errType,ops)
myTree['right'] = createTree(dataLg_X,dataLg_Y,leafType,errType,ops) return myTree

(六)数据集加载及测试

import numpy as np

def loadDataSet(filename):
dataSet = np.loadtxt(filename)
m,n = dataSet.shape
data_X = dataSet[:,:n-]
data_Y = dataSet[:,n-]
return data_X,data_Y
data_X,data_Y = loadDataSet("ex0.txt")
print(createTree(data_X,data_Y))

结果显示:

{'feaIdx': , 'feaVal': 0.39435,
'left': {
'feaIdx': , 'feaVal': 0.582002,
'left': {
'feaIdx': , 'feaVal': 0.797583,
'left': 3.9871632,
'right': 2.9836209534883724
},
'right': 1.980035071428571
},
'right': {
'feaIdx': , 'feaVal': 0.197834,
'left': 1.0289583666666666,
'right': -0.023838155555555553
}
}

(七)全部代码

import numpy as np

def loadDataSet(filename):
dataSet = np.loadtxt(filename)
m,n = dataSet.shape
data_X = dataSet[:,:n-]
data_Y = dataSet[:,n-]
return data_X,data_Y def regLeaf(data_Y): #用于计算指定样本中标签均值表示回归y值
return np.mean(data_Y) def regErr(data_Y): #使用均方误差作为划分依据
return np.var(data_Y)*data_Y.size def binSplitDataSet(data_X,data_Y,fea_axis,fea_val): #进行数据集划分
dataGtIdx = np.where(data_X[:,fea_axis]>fea_val)
dataLgIdx = np.where(data_X[:,fea_axis]<=fea_val) return data_X[dataGtIdx],data_Y[dataGtIdx],data_X[dataLgIdx],data_Y[dataLgIdx] def chooseBestSplit(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)):
"""
选取的最好切分方式,使用回调方式调用叶节点计算和误差计算,函数中含有预剪枝操作
:param data_X: 传入数据集
:param data_Y: 传入标签值
:param leafType: 要调用计算的叶节点值 --- 虽然灵活,但是没必要
:param errType: 要计算误差的函数,这里是均方误差 --- 虽然灵活,但是没必要
:param ops: 包含了两个重要信息, tolS tolN用于控制函数的停止时机,tolS是容许的误差下降值,误差小于则不再切分,tosN是切分的最少样本数
:return:
"""
m,n = data_X.shape
tolS = ops[]
tolN = ops[]
#之前都是将判断是否继续划分子树放入createTree方法中,这里可以提到chooseBestSplit中进行判别。
#当然可以放入createTree方法中处理
if np.unique(data_Y).size == : #.如果标签值全部相同,则返回特征None表示不需要进行下一步划分,返回叶节点
return None,leafType(data_Y) #遍历获取最优特征和特征值
TosErr = errType(data_Y) #获取全部数据集的误差,后面计算划分后两个子集的总误差,如果误差下降小于tolS,则不进行划分,返回该叶子节点即可(预剪枝操作)
bestErr = np.inf
bestFeaIdx = #注意:这里两个我们设置为0,而不是-,因为我们必须保证可以取到一个特征(后面循环可能一直continue),我们需要在后面进行额外处理
bestFeaVal =
for i in range(n): #遍历所有特征
for feaval in np.unique(data_X[:,i]):
dataGt_X,dataGt_Y,dataLg_X,dataLg_Y = binSplitDataSet(data_X,data_Y,i,feaval) #数据集划分
# print(dataGt_X.shape,dataLg_X.shape)
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: #不符合最小数据集,不进行计算
continue
concErr = errType(dataLg_Y)+errType(dataGt_Y)
# print(concErr)
if concErr < bestErr:
bestFeaIdx = i
bestFeaVal = feaval
bestErr = concErr #.如果最后求解的误差,小于我们要求的误差距离,则不进行下一步划分数据集(预剪枝)
if (TosErr - bestErr) < tolS:
return None,leafType(data_Y) #.如果我们上面的数据集本身较小,则无论如何切分,数据集都<tolN,我们就需要在这里再处理一遍,进行一下判断
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, bestFeaIdx, bestFeaVal) # 数据集划分
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: # 不符合最小数据集,不进行计算
return None,leafType(data_Y) return bestFeaIdx,bestFeaVal #正常情况下的返回结果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)): #建立回归树 feaIdx,feaVal = chooseBestSplit(data_X,data_Y,leafType,errType,ops)
if feaIdx == None: #是叶子节点
return feaVal #递归建树
myTree = {}
myTree['feaIdx'] = feaIdx
myTree['feaVal'] = feaVal
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, feaIdx, feaVal) # 数据集划分
myTree['left'] = createTree(dataGt_X,dataGt_Y,leafType,errType,ops)
myTree['right'] = createTree(dataLg_X,dataLg_Y,leafType,errType,ops) return myTree data_X,data_Y = loadDataSet("ex0.txt")
print(createTree(data_X,data_Y))

三:树剪枝

一棵树如果节点过多,表示该模型可能对数据进行了过拟合(使用测试集交叉验证法即可),这时就需要我们进行剪枝处理,避免过拟合

(一)预剪枝

前面建立决策树过程中,我们已经进行了预剪枝操作。即设置的ops参数,包含了两个重要信息, tolS tolN用于控制函数的停止时机,tolS是容许的误差下降值,误差小于则不再切分,tosN是切分的最少样本数。用于在建立决策树过程中进行预剪枝操作。

下面实例中,查看ops参数设置对剪枝的影响:

import numpy as np

def loadDataSet(filename):
dataSet = np.loadtxt(filename)
m,n = dataSet.shape
data_X = dataSet[:,:n-]
data_Y = dataSet[:,n-]
return data_X,data_Y def regLeaf(data_Y): #用于计算指定样本中标签均值表示回归y值
return np.mean(data_Y) def regErr(data_Y): #使用均方误差作为划分依据
return np.var(data_Y)*data_Y.size def binSplitDataSet(data_X,data_Y,fea_axis,fea_val): #进行数据集划分
dataGtIdx = np.where(data_X[:,fea_axis]>fea_val)
dataLgIdx = np.where(data_X[:,fea_axis]<=fea_val) return data_X[dataGtIdx],data_Y[dataGtIdx],data_X[dataLgIdx],data_Y[dataLgIdx] def chooseBestSplit(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)):
"""
选取的最好切分方式,使用回调方式调用叶节点计算和误差计算,函数中含有预剪枝操作
:param data_X: 传入数据集
:param data_Y: 传入标签值
:param leafType: 要调用计算的叶节点值 --- 虽然灵活,但是没必要
:param errType: 要计算误差的函数,这里是均方误差 --- 虽然灵活,但是没必要
:param ops: 包含了两个重要信息, tolS tolN用于控制函数的停止时机,tolS是容许的误差下降值,误差小于则不再切分,tosN是切分的最少样本数
:return:
"""
m,n = data_X.shape
tolS = ops[]
tolN = ops[]
#之前都是将判断是否继续划分子树放入createTree方法中,这里可以提到chooseBestSplit中进行判别。
#当然可以放入createTree方法中处理
if np.unique(data_Y).size == : #.如果标签值全部相同,则返回特征None表示不需要进行下一步划分,返回叶节点
return None,leafType(data_Y) #遍历获取最优特征和特征值
TosErr = errType(data_Y) #获取全部数据集的误差,后面计算划分后两个子集的总误差,如果误差下降小于tolS,则不进行划分,返回该叶子节点即可(预剪枝操作)
bestErr = np.inf
bestFeaIdx = #注意:这里两个我们设置为0,而不是-,因为我们必须保证可以取到一个特征(后面循环可能一直continue),我们需要在后面进行额外处理
bestFeaVal =
for i in range(n): #遍历所有特征
for feaval in np.unique(data_X[:,i]):
dataGt_X,dataGt_Y,dataLg_X,dataLg_Y = binSplitDataSet(data_X,data_Y,i,feaval) #数据集划分
# print(dataGt_X.shape,dataLg_X.shape)
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: #不符合最小数据集,不进行计算
continue
concErr = errType(dataLg_Y)+errType(dataGt_Y)
# print(concErr)
if concErr < bestErr:
bestFeaIdx = i
bestFeaVal = feaval
bestErr = concErr #.如果最后求解的误差,小于我们要求的误差距离,则不进行下一步划分数据集(预剪枝)
if (TosErr - bestErr) < tolS:
return None,leafType(data_Y) #.如果我们上面的数据集本身较小,则无论如何切分,数据集都<tolN,我们就需要在这里再处理一遍,进行一下判断
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, bestFeaIdx, bestFeaVal) # 数据集划分
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: # 不符合最小数据集,不进行计算
return None,leafType(data_Y) return bestFeaIdx,bestFeaVal #正常情况下的返回结果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)): #建立回归树 feaIdx,feaVal = chooseBestSplit(data_X,data_Y,leafType,errType,ops)
if feaIdx == None: #是叶子节点
return feaVal #递归建树
myTree = {}
myTree['feaIdx'] = feaIdx
myTree['feaVal'] = feaVal
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, feaIdx, feaVal) # 数据集划分
myTree['left'] = createTree(dataGt_X,dataGt_Y,leafType,errType,ops)
myTree['right'] = createTree(dataLg_X,dataLg_Y,leafType,errType,ops) return myTree

决策树创建函数

1.默认参数ops(1,4)---表示误差大于1,样本数大于4的划分结果

data_X,data_Y = loadDataSet("ex2.txt")
print(createTree(data_X,data_Y,ops=(,)))

出现大量树分叉,过拟合

3.设置参数ops(1000,4)---表示误差大于1000,样本数大于4的划分结果

data_X,data_Y = loadDataSet("ex2.txt")
print(createTree(data_X,data_Y,ops=(,)))

拟合状态还不错。

3.设置参数ops(10000,4)---表示误差大于10000,样本数大于4的划分结果

data_X,data_Y = loadDataSet("ex2.txt")
print(createTree(data_X,data_Y,ops=(,)))

有点欠拟合。

(二)后剪枝

后剪枝通常比预剪枝保留更多的分支,欠拟合风险小。但是后剪枝是在决策树构造完成后进行的,其训练时间的开销会大于预剪枝。

后剪枝是基于已经建立好的树,进行的叶子节点合并操作。

使用后剪枝方法需要将数据集分为测试集和训练集。通过训练集和参数ops使用预剪枝方法构建决策树。然后使用构建的决策树和测试集数据进行后剪枝处理

后剪枝算法实现:

#开启后剪枝处理
def isTree(tree):
return type(tree) == dict #是树的话返回字典,否则是数据 def getMean(tree): #获取当前树的合并均值REP---塌陷处理: 我们对一棵树进行塌陷处理,就是递归将这棵树进行合并返回这棵树的平均值。
if isTree(tree['right']):
tree['right'] = getMean(tree['right'])
if isTree(tree['left']):
tree['left'] = getMean(tree['left']) return (tree['left'] + tree['right'])/ #返回均值 def prune(tree,testData_X,testData_Y): #根据决策树和测试集数据进行后剪枝处理,不能按照训练集进行后剪枝,因为创建决策树时预剪枝操作中已经要求子树误差值小于根节点
#.若是当测试集数据为空,则不需要后面的子树了,直接进行塌陷处理
if testData_X.shape[] == :
return getMean(tree) #.如果当前测试集不为空,而且决策树含有左/右子树,则需要进入子树中进行剪枝操作---这里我们先将测试集数据划分
if isTree(tree['left']) or isTree(tree['right']):
TestDataGT_X,TestDataGT_Y,TestDataLG_X,TestDataLG_Y = binSplitDataSet(testData_X,testData_Y,tree['feaIdx'],tree['feaVal']) #.根据子树进行下一步剪枝
if isTree(tree['left']):
tree['left'] = prune(tree['left'],TestDataGT_X,TestDataGT_Y) #注意:这里是赋值操作,对树进行剪枝
if isTree(tree['right']):
tree['right'] = prune(tree['right'],TestDataLG_X,TestDataLG_Y) #注意:这里是赋值操作,对树进行剪枝 #.如果两个是叶子节点,我们开始计算误差,进行合并
if not isTree(tree['left']) and not isTree(tree['right']):
#先划分测试集数据
TestDataGT_X,TestDataGT_Y,TestDataLG_X,TestDataLG_Y = binSplitDataSet(testData_X,testData_Y,tree['feaIdx'],tree['feaVal'])
#进行误差比较
#-.先获取没有合并的误差
errorNoMerge = np.sum(np.power(TestDataGT_Y-tree['left'],)) + np.sum(np.power(TestDataLG_Y-tree['right'],))
#-.再获取合并后的误差
treemean = (tree['left'] + tree['right'])/ #因为是叶子节点,可以直接计算
errorMerge = np.sum(np.power(testData_Y- treemean,))
#-.进行判断
if errorMerge < errorNoMerge: #可以剪枝
print("merging") #打印提示信息
return treemean #返回合并后的塌陷值
else:
return tree #不进行合并,返回原树
return tree #返回树(但是该树的子树中可能存在剪枝合并情况由3可以知道

后剪枝算法测试:

data_X,data_Y = loadDataSet("ex2.txt")
myTree = createTree(data_X,data_Y,ops=(,)) #设置0,1表示不进行预剪枝,我们只对比后剪枝
print(myTree) Testdata_X,Testdata_Y = loadDataSet("ex2test.txt") #获取测试集,开始进行后剪枝
myTree2 = prune(myTree,Testdata_X,Testdata_Y)
print(myTree2)

可以看到进行了大量的剪枝操作!

import numpy as np

def loadDataSet(filename):
dataSet = np.loadtxt(filename)
m,n = dataSet.shape
data_X = dataSet[:,:n-]
data_Y = dataSet[:,n-]
return data_X,data_Y def regLeaf(data_Y): #用于计算指定样本中标签均值表示回归y值
return np.mean(data_Y) def regErr(data_Y): #使用均方误差作为划分依据
return np.var(data_Y)*data_Y.size def binSplitDataSet(data_X,data_Y,fea_axis,fea_val): #进行数据集划分
dataGtIdx = np.where(data_X[:,fea_axis]>fea_val)
dataLgIdx = np.where(data_X[:,fea_axis]<=fea_val) return data_X[dataGtIdx],data_Y[dataGtIdx],data_X[dataLgIdx],data_Y[dataLgIdx] def chooseBestSplit(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)):
"""
选取的最好切分方式,使用回调方式调用叶节点计算和误差计算,函数中含有预剪枝操作
:param data_X: 传入数据集
:param data_Y: 传入标签值
:param leafType: 要调用计算的叶节点值 --- 虽然灵活,但是没必要
:param errType: 要计算误差的函数,这里是均方误差 --- 虽然灵活,但是没必要
:param ops: 包含了两个重要信息, tolS tolN用于控制函数的停止时机,tolS是容许的误差下降值,误差小于则不再切分,tosN是切分的最少样本数
:return:
"""
m,n = data_X.shape
tolS = ops[]
tolN = ops[]
#之前都是将判断是否继续划分子树放入createTree方法中,这里可以提到chooseBestSplit中进行判别。
#当然可以放入createTree方法中处理
if np.unique(data_Y).size == : #.如果标签值全部相同,则返回特征None表示不需要进行下一步划分,返回叶节点
return None,leafType(data_Y) #遍历获取最优特征和特征值
TosErr = errType(data_Y) #获取全部数据集的误差,后面计算划分后两个子集的总误差,如果误差下降小于tolS,则不进行划分,返回该叶子节点即可(预剪枝操作)
bestErr = np.inf
bestFeaIdx = #注意:这里两个我们设置为0,而不是-,因为我们必须保证可以取到一个特征(后面循环可能一直continue),我们需要在后面进行额外处理
bestFeaVal =
for i in range(n): #遍历所有特征
for feaval in np.unique(data_X[:,i]):
dataGt_X,dataGt_Y,dataLg_X,dataLg_Y = binSplitDataSet(data_X,data_Y,i,feaval) #数据集划分
# print(dataGt_X.shape,dataLg_X.shape)
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: #不符合最小数据集,不进行计算
continue
concErr = errType(dataLg_Y)+errType(dataGt_Y)
# print(concErr)
if concErr < bestErr:
bestFeaIdx = i
bestFeaVal = feaval
bestErr = concErr #.如果最后求解的误差,小于我们要求的误差距离,则不进行下一步划分数据集(预剪枝)
if (TosErr - bestErr) < tolS:
return None,leafType(data_Y) #.如果我们上面的数据集本身较小,则无论如何切分,数据集都<tolN,我们就需要在这里再处理一遍,进行一下判断
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, bestFeaIdx, bestFeaVal) # 数据集划分
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: # 不符合最小数据集,不进行计算
return None,leafType(data_Y) return bestFeaIdx,bestFeaVal #正常情况下的返回结果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)): #建立回归树 feaIdx,feaVal = chooseBestSplit(data_X,data_Y,leafType,errType,ops)
if feaIdx == None: #是叶子节点
return feaVal #递归建树
myTree = {}
myTree['feaIdx'] = feaIdx
myTree['feaVal'] = feaVal
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, feaIdx, feaVal) # 数据集划分
myTree['left'] = createTree(dataGt_X,dataGt_Y,leafType,errType,ops)
myTree['right'] = createTree(dataLg_X,dataLg_Y,leafType,errType,ops) return myTree #开启后剪枝处理
def isTree(tree):
return type(tree) == dict #是树的话返回字典,否则是数据 def getMean(tree): #获取当前树的合并均值REP---塌陷处理: 我们对一棵树进行塌陷处理,就是递归将这棵树进行合并返回这棵树的平均值。
if isTree(tree['right']):
tree['right'] = getMean(tree['right'])
if isTree(tree['left']):
tree['left'] = getMean(tree['left']) return (tree['left'] + tree['right'])/ #返回均值 def prune(tree,testData_X,testData_Y): #根据决策树和测试集数据进行后剪枝处理,不能按照训练集进行后剪枝,因为创建决策树时预剪枝操作中已经要求子树误差值小于根节点
#.若是当测试集数据为空,则不需要后面的子树了,直接进行塌陷处理
if testData_X.shape[] == :
return getMean(tree) #.如果当前测试集不为空,而且决策树含有左/右子树,则需要进入子树中进行剪枝操作---这里我们先将测试集数据划分
if isTree(tree['left']) or isTree(tree['right']):
TestDataGT_X,TestDataGT_Y,TestDataLG_X,TestDataLG_Y = binSplitDataSet(testData_X,testData_Y,tree['feaIdx'],tree['feaVal']) #.根据子树进行下一步剪枝
if isTree(tree['left']):
tree['left'] = prune(tree['left'],TestDataGT_X,TestDataGT_Y) #注意:这里是赋值操作,对树进行剪枝
if isTree(tree['right']):
tree['right'] = prune(tree['right'],TestDataLG_X,TestDataLG_Y) #注意:这里是赋值操作,对树进行剪枝 #.如果两个是叶子节点,我们开始计算误差,进行合并
if not isTree(tree['left']) and not isTree(tree['right']):
#先划分测试集数据
TestDataGT_X,TestDataGT_Y,TestDataLG_X,TestDataLG_Y = binSplitDataSet(testData_X,testData_Y,tree['feaIdx'],tree['feaVal'])
#进行误差比较
#-.先获取没有合并的误差
errorNoMerge = np.sum(np.power(TestDataGT_Y-tree['left'],)) + np.sum(np.power(TestDataLG_Y-tree['right'],))
#-.再获取合并后的误差
treemean = (tree['left'] + tree['right'])/ #因为是叶子节点,可以直接计算
errorMerge = np.sum(np.power(testData_Y- treemean,))
#-.进行判断
if errorMerge < errorNoMerge: #可以剪枝
print("merging") #打印提示信息
return treemean #返回合并后的塌陷值
else:
return tree #不进行合并,返回原树
return tree #返回树(但是该树的子树中可能存在剪枝合并情况由3可以知道 data_X,data_Y = loadDataSet("ex2.txt")
myTree = createTree(data_X,data_Y,ops=(,)) #设置0,1表示不进行预剪枝,我们只对比后剪枝
print(myTree) Testdata_X,Testdata_Y = loadDataSet("ex2test.txt") #获取测试集,开始进行后剪枝
myTree2 = prune(myTree,Testdata_X,Testdata_Y)
print(myTree2)

全部代码

四:模型树实现

(一)实现模型树叶节点生成函数和误差计算函数

import numpy as np
import matplotlib.pyplot as plt def linearSolve(data_X,data_Y):
X = np.c_[np.ones(data_X.shape[]), data_X]
XTX = X.T @ X
if np.linalg.det(XTX) == :
raise NameError("this matrix can`t inverse") W = np.linalg.inv(XTX) @ (X.T @ data_Y)
return W,X,data_Y def modelLeaf(data_X,data_Y):
W,X,Y = linearSolve(data_X,data_Y)
return W def modelErr(data_X,data_Y):
W,X,Y = linearSolve(data_X,data_Y)
yPred = X@W
return sum(np.power(yPred-data_Y,))

(二)修改原有函数

def binSplitDataSet(data_X,data_Y,fea_axis,fea_val):    #进行数据集划分
dataGtIdx = np.where(data_X[:,fea_axis]>fea_val)
dataLgIdx = np.where(data_X[:,fea_axis]<=fea_val) return data_X[dataGtIdx],data_Y[dataGtIdx],data_X[dataLgIdx],data_Y[dataLgIdx] def chooseBestSplit(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)):
"""
选取的最好切分方式,使用回调方式调用叶节点计算和误差计算,函数中含有预剪枝操作
:param data_X: 传入数据集
:param data_Y: 传入标签值
:param leafType: 要调用计算的叶节点值 --- 虽然灵活,但是没必要
:param errType: 要计算误差的函数,这里是均方误差 --- 虽然灵活,但是没必要
:param ops: 包含了两个重要信息, tolS tolN用于控制函数的停止时机,tolS是容许的误差下降值,误差小于则不再切分,tosN是切分的最少样本数
:return:
"""
m,n = data_X.shape
tolS = ops[]
tolN = ops[]
#之前都是将判断是否继续划分子树放入createTree方法中,这里可以提到chooseBestSplit中进行判别。
#当然可以放入createTree方法中处理
if np.unique(data_Y).size == : #.如果标签值全部相同,则返回特征None表示不需要进行下一步划分,返回叶节点
return None,leafType(data_X,data_Y) #遍历获取最优特征和特征值
TosErr = errType(data_X,data_Y) #获取全部数据集的误差,后面计算划分后两个子集的总误差,如果误差下降小于tolS,则不进行划分,返回该叶子节点即可(预剪枝操作)
bestErr = np.inf
bestFeaIdx = #注意:这里两个我们设置为0,而不是-,因为我们必须保证可以取到一个特征(后面循环可能一直continue),我们需要在后面进行额外处理
bestFeaVal =
for i in range(n): #遍历所有特征
for feaval in np.unique(data_X[:,i]):
dataGt_X,dataGt_Y,dataLg_X,dataLg_Y = binSplitDataSet(data_X,data_Y,i,feaval) #数据集划分
# print(dataGt_X.shape,dataLg_X.shape)
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: #不符合最小数据集,不进行计算
continue
concErr = errType(dataLg_X,dataLg_Y)+errType(dataGt_X,dataGt_Y)
# print(concErr)
if concErr < bestErr:
bestFeaIdx = i
bestFeaVal = feaval
bestErr = concErr #.如果最后求解的误差,小于我们要求的误差距离,则不进行下一步划分数据集(预剪枝)
if (TosErr - bestErr) < tolS:
return None,leafType(data_X,data_Y) #.如果我们上面的数据集本身较小,则无论如何切分,数据集都<tolN,我们就需要在这里再处理一遍,进行一下判断
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, bestFeaIdx, bestFeaVal) # 数据集划分
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: # 不符合最小数据集,不进行计算
return None,leafType(data_X,data_Y) return bestFeaIdx,bestFeaVal #正常情况下的返回结果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)): #建立回归树 feaIdx,feaVal = chooseBestSplit(data_X,data_Y,leafType,errType,ops)
if feaIdx == None: #是叶子节点
return feaVal #递归建树
myTree = {}
myTree['feaIdx'] = feaIdx
myTree['feaVal'] = feaVal
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, feaIdx, feaVal) # 数据集划分
myTree['left'] = createTree(dataGt_X,dataGt_Y,leafType,errType,ops)
myTree['right'] = createTree(dataLg_X,dataLg_Y,leafType,errType,ops) return myTree #开启后剪枝处理
def isTree(tree):
return type(tree) == dict #是树的话返回字典,否则是数据 def getMean(tree): #获取当前树的合并均值REP---塌陷处理: 我们对一棵树进行塌陷处理,就是递归将这棵树进行合并返回这棵树的平均值。
if isTree(tree['right']):
tree['right'] = getMean(tree['right'])
if isTree(tree['left']):
tree['left'] = getMean(tree['left']) return (tree['left'] + tree['right'])/ #返回均值 def prune(tree,testData_X,testData_Y): #根据决策树和测试集数据进行后剪枝处理,不能按照训练集进行后剪枝,因为创建决策树时预剪枝操作中已经要求子树误差值小于根节点
#.若是当测试集数据为空,则不需要后面的子树了,直接进行塌陷处理
if testData_X.shape[] == :
return getMean(tree) #.如果当前测试集不为空,而且决策树含有左/右子树,则需要进入子树中进行剪枝操作---这里我们先将测试集数据划分
if isTree(tree['left']) or isTree(tree['right']):
TestDataGT_X,TestDataGT_Y,TestDataLG_X,TestDataLG_Y = binSplitDataSet(testData_X,testData_Y,tree['feaIdx'],tree['feaVal']) #.根据子树进行下一步剪枝
if isTree(tree['left']):
tree['left'] = prune(tree['left'],TestDataGT_X,TestDataGT_Y) #注意:这里是赋值操作,对树进行剪枝
if isTree(tree['right']):
tree['right'] = prune(tree['right'],TestDataLG_X,TestDataLG_Y) #注意:这里是赋值操作,对树进行剪枝 #.如果两个是叶子节点,我们开始计算误差,进行合并
if not isTree(tree['left']) and not isTree(tree['right']):
#先划分测试集数据
TestDataGT_X,TestDataGT_Y,TestDataLG_X,TestDataLG_Y = binSplitDataSet(testData_X,testData_Y,tree['feaIdx'],tree['feaVal'])
#进行误差比较
#-.先获取没有合并的误差
errorNoMerge = np.sum(np.power(TestDataGT_Y-tree['left'],)) + np.sum(np.power(TestDataLG_Y-tree['right'],))
#-.再获取合并后的误差
treemean = (tree['left'] + tree['right'])/ #因为是叶子节点,可以直接计算
errorMerge = np.sum(np.power(testData_Y- treemean,))
#-.进行判断
if errorMerge < errorNoMerge: #可以剪枝
print("merging") #打印提示信息
return treemean #返回合并后的塌陷值
else:
return tree #不进行合并,返回原树
return tree #返回树(但是该树的子树中可能存在剪枝合并情况由3可以知道

(三)测试函数

data_X,data_Y = loadDataSet("exp2.txt")

myTree = createTree(data_X,data_Y,modelLeaf,modelErr,ops=(,))  #设置0,1表示不进行预剪枝,我们只对比后剪枝
print(myTree) plt.figure()
plt.scatter(data_X.flatten(),data_Y.flatten())
plt.show()

五:实现回归树预测,对比决策树和线性回归

由于我上面没有很好的处理回归树和模型树的参数保持一致性,所以这里我对每一个预测使用不同代码(就是同上面一样,各自改变了参数,也可以该一下即可)

(一)实现决策树--回归树和模型树预测函数

#实现预测回归树
def regTreeEval(model,data_X): #对于回归树,直接返回model(预测值),对于模型树,通过model和我们传递的测试集数据进行预测
return model #实现预测模型树
def modelTreeEval(model,data_X): #为了使得回归树和模型树保持一致,所以我们上面为regTreeEval加了data_X
X = np.c_[np.ones(data_X.shape[]),data_X]
return X@model #开始递归预测
def treeForeCast(tree,TestData,modelEval=regTreeEval):
if not isTree(tree):
return modelEval(tree,TestData) #如果是叶子节点,直接返回预测值 if TestData[tree['feaIdx']] > tree['feaVal']: #如果测试集指定特征上的值大于决策树特征值,则进入左子树
if isTree(tree['left']):
return treeForeCast(tree['left'],TestData,modelEval)
else: #如果左子树是叶子节点,直接返回预测值
return modelEval(tree['left'],TestData)
else: #进入右子树
if isTree(tree['right']):
return treeForeCast(tree['right'],TestData,modelEval)
else: #如果左子树是叶子节点,直接返回预测值
return modelEval(tree['right'],TestData) def createForecast(tree,testData_X,modelEval = regTreeEval): #进行测试集数据预测
m,n = testData_X.shape
yPred = np.zeros((m,)) for i in range(m): #开始预测
yPred[i] = treeForeCast(tree,testData_X[i],modelEval) return yPred

(三)测试回归树预测结果和测试集标签相关性(R2越接近1越好)

import numpy as np
import matplotlib.pyplot as plt def loadDataSet(filename):
dataSet = np.loadtxt(filename)
m,n = dataSet.shape
data_X = dataSet[:,:n-]
data_Y = dataSet[:,n-]
return data_X,data_Y def regLeaf(data_Y): #用于计算指定样本中标签均值表示回归y值
return np.mean(data_Y) def regErr(data_Y): #使用均方误差作为划分依据
return np.var(data_Y)*data_Y.size def binSplitDataSet(data_X,data_Y,fea_axis,fea_val): #进行数据集划分
dataGtIdx = np.where(data_X[:,fea_axis]>fea_val)
dataLgIdx = np.where(data_X[:,fea_axis]<=fea_val) return data_X[dataGtIdx],data_Y[dataGtIdx],data_X[dataLgIdx],data_Y[dataLgIdx] def chooseBestSplit(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)):
"""
选取的最好切分方式,使用回调方式调用叶节点计算和误差计算,函数中含有预剪枝操作
:param data_X: 传入数据集
:param data_Y: 传入标签值
:param leafType: 要调用计算的叶节点值 --- 虽然灵活,但是没必要
:param errType: 要计算误差的函数,这里是均方误差 --- 虽然灵活,但是没必要
:param ops: 包含了两个重要信息, tolS tolN用于控制函数的停止时机,tolS是容许的误差下降值,误差小于则不再切分,tosN是切分的最少样本数
:return:
"""
m,n = data_X.shape
tolS = ops[]
tolN = ops[]
#之前都是将判断是否继续划分子树放入createTree方法中,这里可以提到chooseBestSplit中进行判别。
#当然可以放入createTree方法中处理
if np.unique(data_Y).size == : #.如果标签值全部相同,则返回特征None表示不需要进行下一步划分,返回叶节点
return None,leafType(data_Y) #遍历获取最优特征和特征值
TosErr = errType(data_Y) #获取全部数据集的误差,后面计算划分后两个子集的总误差,如果误差下降小于tolS,则不进行划分,返回该叶子节点即可(预剪枝操作)
bestErr = np.inf
bestFeaIdx = #注意:这里两个我们设置为0,而不是-,因为我们必须保证可以取到一个特征(后面循环可能一直continue),我们需要在后面进行额外处理
bestFeaVal =
for i in range(n): #遍历所有特征
for feaval in np.unique(data_X[:,i]):
dataGt_X,dataGt_Y,dataLg_X,dataLg_Y = binSplitDataSet(data_X,data_Y,i,feaval) #数据集划分
# print(dataGt_X.shape,dataLg_X.shape)
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: #不符合最小数据集,不进行计算
continue
concErr = errType(dataLg_Y)+errType(dataGt_Y)
# print(concErr)
if concErr < bestErr:
bestFeaIdx = i
bestFeaVal = feaval
bestErr = concErr #.如果最后求解的误差,小于我们要求的误差距离,则不进行下一步划分数据集(预剪枝)
if (TosErr - bestErr) < tolS:
return None,leafType(data_Y) #.如果我们上面的数据集本身较小,则无论如何切分,数据集都<tolN,我们就需要在这里再处理一遍,进行一下判断
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, bestFeaIdx, bestFeaVal) # 数据集划分
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: # 不符合最小数据集,不进行计算
return None,leafType(data_Y) return bestFeaIdx,bestFeaVal #正常情况下的返回结果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)): #建立回归树 feaIdx,feaVal = chooseBestSplit(data_X,data_Y,leafType,errType,ops)
if feaIdx == None: #是叶子节点
return feaVal #递归建树
myTree = {}
myTree['feaIdx'] = feaIdx
myTree['feaVal'] = feaVal
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, feaIdx, feaVal) # 数据集划分
myTree['left'] = createTree(dataGt_X,dataGt_Y,leafType,errType,ops)
myTree['right'] = createTree(dataLg_X,dataLg_Y,leafType,errType,ops) return myTree #开启后剪枝处理
def isTree(tree):
return type(tree) == dict #是树的话返回字典,否则是数据 def getMean(tree): #获取当前树的合并均值REP---塌陷处理: 我们对一棵树进行塌陷处理,就是递归将这棵树进行合并返回这棵树的平均值。
if isTree(tree['right']):
tree['right'] = getMean(tree['right'])
if isTree(tree['left']):
tree['left'] = getMean(tree['left']) return (tree['left'] + tree['right'])/ #返回均值 def prune(tree,testData_X,testData_Y): #根据决策树和测试集数据进行后剪枝处理,不能按照训练集进行后剪枝,因为创建决策树时预剪枝操作中已经要求子树误差值小于根节点
#.若是当测试集数据为空,则不需要后面的子树了,直接进行塌陷处理
if testData_X.shape[] == :
return getMean(tree) #.如果当前测试集不为空,而且决策树含有左/右子树,则需要进入子树中进行剪枝操作---这里我们先将测试集数据划分
if isTree(tree['left']) or isTree(tree['right']):
TestDataGT_X,TestDataGT_Y,TestDataLG_X,TestDataLG_Y = binSplitDataSet(testData_X,testData_Y,tree['feaIdx'],tree['feaVal']) #.根据子树进行下一步剪枝
if isTree(tree['left']):
tree['left'] = prune(tree['left'],TestDataGT_X,TestDataGT_Y) #注意:这里是赋值操作,对树进行剪枝
if isTree(tree['right']):
tree['right'] = prune(tree['right'],TestDataLG_X,TestDataLG_Y) #注意:这里是赋值操作,对树进行剪枝 #.如果两个是叶子节点,我们开始计算误差,进行合并
if not isTree(tree['left']) and not isTree(tree['right']):
#先划分测试集数据
TestDataGT_X,TestDataGT_Y,TestDataLG_X,TestDataLG_Y = binSplitDataSet(testData_X,testData_Y,tree['feaIdx'],tree['feaVal'])
#进行误差比较
#-.先获取没有合并的误差
errorNoMerge = np.sum(np.power(TestDataGT_Y-tree['left'],)) + np.sum(np.power(TestDataLG_Y-tree['right'],))
#-.再获取合并后的误差
treemean = (tree['left'] + tree['right'])/ #因为是叶子节点,可以直接计算
errorMerge = np.sum(np.power(testData_Y- treemean,))
#-.进行判断
if errorMerge < errorNoMerge: #可以剪枝
print("merging") #打印提示信息
return treemean #返回合并后的塌陷值
else:
return tree #不进行合并,返回原树
return tree #返回树(但是该树的子树中可能存在剪枝合并情况由3可以知道 #实现预测回归树
def regTreeEval(model,data_X): #对于回归树,直接返回model(预测值),对于模型树,通过model和我们传递的测试集数据进行预测
return model #实现预测模型树
def modelTreeEval(model,data_X): #为了使得回归树和模型树保持一致,所以我们上面为regTreeEval加了data_X
X = np.c_[np.ones(data_X.shape[]),data_X]
return X@model #开始递归预测
def treeForeCast(tree,TestData,modelEval=regTreeEval):
if not isTree(tree):
return modelEval(tree,TestData) #如果是叶子节点,直接返回预测值 if TestData[tree['feaIdx']] > tree['feaVal']: #如果测试集指定特征上的值大于决策树特征值,则进入左子树
if isTree(tree['left']):
return treeForeCast(tree['left'],TestData,modelEval)
else: #如果左子树是叶子节点,直接返回预测值
return modelEval(tree['left'],TestData)
else: #进入右子树
if isTree(tree['right']):
return treeForeCast(tree['right'],TestData,modelEval)
else: #如果左子树是叶子节点,直接返回预测值
return modelEval(tree['right'],TestData) def createForecast(tree,testData_X,modelEval = regTreeEval): #进行测试集数据预测
m,n = testData_X.shape
yPred = np.zeros((m,)) for i in range(m): #开始预测
yPred[i] = treeForeCast(tree,testData_X[i],modelEval) return yPred data_X,data_Y = loadDataSet("bikeSpeedVsIq_train.txt") #训练集数据 myTree = createTree(data_X,data_Y,ops=(,)) #训练集数据建决策模型树
print(myTree) testData_X,testData_Y = loadDataSet('bikeSpeedVsIq_test.txt') #测试集数据
yPred = createForecast(myTree,testData_X) #使用模型树预测
print(np.corrcoef(yPred,testData_Y,rowvar=)[,]) plt.figure()
plt.scatter(data_X.flatten(),data_Y.flatten())
plt.show()

全部代码

data_X,data_Y = loadDataSet("bikeSpeedVsIq_train.txt")  #训练集数据

myTree = createTree(data_X,data_Y,ops=(,))  #训练集数据建决策模型树
print(myTree) testData_X,testData_Y = loadDataSet('bikeSpeedVsIq_test.txt') #测试集数据
yPred = createForecast(myTree,testData_X) #使用模型树预测
print(np.corrcoef(yPred,testData_Y,rowvar=)[,]) plt.figure()
plt.scatter(data_X.flatten(),data_Y.flatten())
plt.show()

(四)测试模型树预测结果和测试集标签相关性(R2越接近1越好)

import numpy as np
import matplotlib.pyplot as plt def loadDataSet(filename):
dataSet = np.loadtxt(filename)
m,n = dataSet.shape
data_X = dataSet[:,:n-]
data_Y = dataSet[:,n-]
return data_X,data_Y def regLeaf(data_Y): #用于计算指定样本中标签均值表示回归y值
return np.mean(data_Y) def regErr(data_Y): #使用均方误差作为划分依据
return np.var(data_Y)*data_Y.size def linearSolve(data_X,data_Y):
X = np.c_[np.ones(data_X.shape[]), data_X]
XTX = X.T @ X
if np.linalg.det(XTX) == :
raise NameError("this matrix can`t inverse") W = np.linalg.inv(XTX) @ (X.T @ data_Y)
return W,X,data_Y def modelLeaf(data_X,data_Y):
W,X,Y = linearSolve(data_X,data_Y)
return W def modelErr(data_X,data_Y):
W,X,Y = linearSolve(data_X,data_Y)
yPred = X@W
return sum(np.power(yPred-data_Y,)) def binSplitDataSet(data_X,data_Y,fea_axis,fea_val): #进行数据集划分
dataGtIdx = np.where(data_X[:,fea_axis]>fea_val)
dataLgIdx = np.where(data_X[:,fea_axis]<=fea_val) return data_X[dataGtIdx],data_Y[dataGtIdx],data_X[dataLgIdx],data_Y[dataLgIdx] def chooseBestSplit(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)):
"""
选取的最好切分方式,使用回调方式调用叶节点计算和误差计算,函数中含有预剪枝操作
:param data_X: 传入数据集
:param data_Y: 传入标签值
:param leafType: 要调用计算的叶节点值 --- 虽然灵活,但是没必要
:param errType: 要计算误差的函数,这里是均方误差 --- 虽然灵活,但是没必要
:param ops: 包含了两个重要信息, tolS tolN用于控制函数的停止时机,tolS是容许的误差下降值,误差小于则不再切分,tosN是切分的最少样本数
:return:
"""
m,n = data_X.shape
tolS = ops[]
tolN = ops[]
#之前都是将判断是否继续划分子树放入createTree方法中,这里可以提到chooseBestSplit中进行判别。
#当然可以放入createTree方法中处理
if np.unique(data_Y).size == : #.如果标签值全部相同,则返回特征None表示不需要进行下一步划分,返回叶节点
return None,leafType(data_X,data_Y) #遍历获取最优特征和特征值
TosErr = errType(data_X,data_Y) #获取全部数据集的误差,后面计算划分后两个子集的总误差,如果误差下降小于tolS,则不进行划分,返回该叶子节点即可(预剪枝操作)
bestErr = np.inf
bestFeaIdx = #注意:这里两个我们设置为0,而不是-,因为我们必须保证可以取到一个特征(后面循环可能一直continue),我们需要在后面进行额外处理
bestFeaVal =
for i in range(n): #遍历所有特征
for feaval in np.unique(data_X[:,i]):
dataGt_X,dataGt_Y,dataLg_X,dataLg_Y = binSplitDataSet(data_X,data_Y,i,feaval) #数据集划分
# print(dataGt_X.shape,dataLg_X.shape)
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: #不符合最小数据集,不进行计算
continue
concErr = errType(dataLg_X,dataLg_Y)+errType(dataGt_X,dataGt_Y)
# print(concErr)
if concErr < bestErr:
bestFeaIdx = i
bestFeaVal = feaval
bestErr = concErr #.如果最后求解的误差,小于我们要求的误差距离,则不进行下一步划分数据集(预剪枝)
if (TosErr - bestErr) < tolS:
return None,leafType(data_X,data_Y) #.如果我们上面的数据集本身较小,则无论如何切分,数据集都<tolN,我们就需要在这里再处理一遍,进行一下判断
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, bestFeaIdx, bestFeaVal) # 数据集划分
if dataGt_X.shape[] < tolN or dataLg_X.shape[] < tolN: # 不符合最小数据集,不进行计算
return None,leafType(data_X,data_Y) return bestFeaIdx,bestFeaVal #正常情况下的返回结果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(,)): #建立回归树 feaIdx,feaVal = chooseBestSplit(data_X,data_Y,leafType,errType,ops)
if feaIdx == None: #是叶子节点
return feaVal #递归建树
myTree = {}
myTree['feaIdx'] = feaIdx
myTree['feaVal'] = feaVal
dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, feaIdx, feaVal) # 数据集划分
myTree['left'] = createTree(dataGt_X,dataGt_Y,leafType,errType,ops)
myTree['right'] = createTree(dataLg_X,dataLg_Y,leafType,errType,ops) return myTree #开启后剪枝处理
def isTree(tree):
return type(tree) == dict #是树的话返回字典,否则是数据 def getMean(tree): #获取当前树的合并均值REP---塌陷处理: 我们对一棵树进行塌陷处理,就是递归将这棵树进行合并返回这棵树的平均值。
if isTree(tree['right']):
tree['right'] = getMean(tree['right'])
if isTree(tree['left']):
tree['left'] = getMean(tree['left']) return (tree['left'] + tree['right'])/ #返回均值 def prune(tree,testData_X,testData_Y): #根据决策树和测试集数据进行后剪枝处理,不能按照训练集进行后剪枝,因为创建决策树时预剪枝操作中已经要求子树误差值小于根节点
#.若是当测试集数据为空,则不需要后面的子树了,直接进行塌陷处理
if testData_X.shape[] == :
return getMean(tree) #.如果当前测试集不为空,而且决策树含有左/右子树,则需要进入子树中进行剪枝操作---这里我们先将测试集数据划分
if isTree(tree['left']) or isTree(tree['right']):
TestDataGT_X,TestDataGT_Y,TestDataLG_X,TestDataLG_Y = binSplitDataSet(testData_X,testData_Y,tree['feaIdx'],tree['feaVal']) #.根据子树进行下一步剪枝
if isTree(tree['left']):
tree['left'] = prune(tree['left'],TestDataGT_X,TestDataGT_Y) #注意:这里是赋值操作,对树进行剪枝
if isTree(tree['right']):
tree['right'] = prune(tree['right'],TestDataLG_X,TestDataLG_Y) #注意:这里是赋值操作,对树进行剪枝 #.如果两个是叶子节点,我们开始计算误差,进行合并
if not isTree(tree['left']) and not isTree(tree['right']):
#先划分测试集数据
TestDataGT_X,TestDataGT_Y,TestDataLG_X,TestDataLG_Y = binSplitDataSet(testData_X,testData_Y,tree['feaIdx'],tree['feaVal'])
#进行误差比较
#-.先获取没有合并的误差
errorNoMerge = np.sum(np.power(TestDataGT_Y-tree['left'],)) + np.sum(np.power(TestDataLG_Y-tree['right'],))
#-.再获取合并后的误差
treemean = (tree['left'] + tree['right'])/ #因为是叶子节点,可以直接计算
errorMerge = np.sum(np.power(testData_Y- treemean,))
#-.进行判断
if errorMerge < errorNoMerge: #可以剪枝
print("merging") #打印提示信息
return treemean #返回合并后的塌陷值
else:
return tree #不进行合并,返回原树
return tree #返回树(但是该树的子树中可能存在剪枝合并情况由3可以知道 #实现预测回归树
def regTreeEval(model,data_X): #对于回归树,直接返回model(预测值),对于模型树,通过model和我们传递的测试集数据进行预测
return model #实现预测模型树
def modelTreeEval(model,data_X): #为了使得回归树和模型树保持一致,所以我们上面为regTreeEval加了data_X
X = np.c_[np.ones(data_X.shape[]),data_X]
return X@model #开始递归预测
def treeForeCast(tree,TestData,modelEval=regTreeEval):
if not isTree(tree):
return modelEval(tree,TestData) #如果是叶子节点,直接返回预测值 if TestData[tree['feaIdx']] > tree['feaVal']: #如果测试集指定特征上的值大于决策树特征值,则进入左子树
if isTree(tree['left']):
return treeForeCast(tree['left'],TestData,modelEval)
else: #如果左子树是叶子节点,直接返回预测值
return modelEval(tree['left'],TestData)
else: #进入右子树
if isTree(tree['right']):
return treeForeCast(tree['right'],TestData,modelEval)
else: #如果左子树是叶子节点,直接返回预测值
return modelEval(tree['right'],TestData) def createForecast(tree,testData_X,modelEval = regTreeEval): #进行测试集数据预测
m,n = testData_X.shape
yPred = np.zeros((m,)) for i in range(m): #开始预测
yPred[i] = treeForeCast(tree,testData_X[i],modelEval) return yPred data_X,data_Y = loadDataSet("bikeSpeedVsIq_train.txt") #训练集数据 myTree = createTree(data_X,data_Y,modelLeaf,modelErr,ops=(,)) #训练集数据建决策模型树
print(myTree) testData_X,testData_Y = loadDataSet('bikeSpeedVsIq_test.txt') #测试集数据
yPred = createForecast(myTree,testData_X,modelTreeEval) #使用模型树预测
print(np.corrcoef(yPred,testData_Y,rowvar=)[,]) plt.figure()
plt.scatter(data_X.flatten(),data_Y.flatten())
plt.show()

全部代码

data_X,data_Y = loadDataSet("bikeSpeedVsIq_train.txt")  #训练集数据

myTree = createTree(data_X,data_Y,modelLeaf,modelErr,ops=(,))  #训练集数据建决策模型树
print(myTree) testData_X,testData_Y = loadDataSet('bikeSpeedVsIq_test.txt') #测试集数据
yPred = createForecast(myTree,testData_X,modelTreeEval) #使用模型树预测
print(np.corrcoef(yPred,testData_Y,rowvar=)[,]) plt.figure()
plt.scatter(data_X.flatten(),data_Y.flatten())
plt.show()

可以看到模型树优于回归树

(五)一般线性回归

利用我们上面实现的linearSolve方法,获取训练集的参数向量权重即可!!

data_X,data_Y = loadDataSet("bikeSpeedVsIq_train.txt")  #训练集数据

testData_X,testData_Y = loadDataSet('bikeSpeedVsIq_test.txt')  #测试集数据

W,X,Y = linearSolve(data_X,data_Y)
yPred2 = np.zeros((testData_X.shape[],))
testDX = np.c_[np.ones(testData_X.shape[]),testData_X] for i in range(testData_X.shape[]):
yPred2[i] = testDX[i]@W print(np.corrcoef(yPred2,testData_Y,rowvar=)[,])

所以,树回归方法在预测复杂数据时,会比简单的线性模型更加有效

机器学习实战---决策树CART回归树实现的更多相关文章

  1. 机器学习实战---决策树CART简介及分类树实现

    https://blog.csdn.net/weixin_43383558/article/details/84303339?utm_medium=distribute.pc_relevant_t0. ...

  2. 决策树CART回归树——算法实现

    决策树模型 选择最好的特征和特征的值进行数据集划分 根据上面获得的结果创建决策树 根据测试数据进行剪枝(默认没有数据的树分支被剪掉) 对输入进行预测 模型树 import numpy as np de ...

  3. 【机器学习实战 第九章】树回归 CART算法的原理与实现 - python3

    本文来自<机器学习实战>(Peter Harrington)第九章"树回归"部分,代码使用python3.5,并在jupyter notebook环境中测试通过,推荐c ...

  4. 大白话5分钟带你走进人工智能-第二十六节决策树系列之Cart回归树及其参数(5)

                                                    第二十六节决策树系列之Cart回归树及其参数(5) 上一节我们讲了不同的决策树对应的计算纯度的计算方法, ...

  5. CART回归树

    决策树算法原理(ID3,C4.5) 决策树算法原理(CART分类树) 决策树的剪枝 CART回归树模型表达式: 其中,数据空间被划分为R1~Rm单元,每个单元有一个固定的输出值Cm.这样可以计算模型输 ...

  6. 机器学习实战 -- 决策树(ID3)

    机器学习实战 -- 决策树(ID3)   ID3是什么我也不知道,不急,知道他是干什么的就行   ID3是最经典最基础的一种决策树算法,他会将每一个特征都设为决策节点,有时候,一个数据集中,某些特征属 ...

  7. [机器学习&数据挖掘]机器学习实战决策树plotTree函数完全解析

    在看机器学习实战时候,到第三章的对决策树画图的时候,有一段递归函数怎么都看不懂,因为以后想选这个方向为自己的职业导向,抱着精看的态度,对这本树进行地毯式扫描,所以就没跳过,一直卡了一天多,才差不多搞懂 ...

  8. 机器学习之路: python 回归树 DecisionTreeRegressor 预测波士顿房价

    python3 学习api的使用 git: https://github.com/linyi0604/MachineLearning 代码: from sklearn.datasets import ...

  9. 机器学习实战之Logistic回归

    Logistic回归一.概述 1. Logistic Regression 1.1 线性回归 1.2 Sigmoid函数 1.3 逻辑回归 1.4 LR 与线性回归的区别 2. LR的损失函数 3. ...

随机推荐

  1. Eplan PLC连接点模块为什么不显示“路径功能文本”,已解决

    Eplan PLC连接点模块为什么不显示“路径功能文本”,已解决 如果“路径功能文本”的文字开头的位置没有对准PLC模块的中心,PLC连接点模块就不会显示.

  2. 深入理解跨域SSO单点登录原理与技术

    [本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 一:SSO体系结 ...

  3. python多线程+生产者和消费者模型+queue使用

    多线程简介 多线程:在一个进程内部,要同时干很多事情,就需要同时执行多个子任务,我们把进程内的这些子任务叫线程. 线程的内存空间是共享的,每个线程都共享同一个进程的资源 模块: 1._thread模块 ...

  4. 重学 Java 设计模式:实战迭代器模式「模拟公司组织架构树结构关系,深度迭代遍历人员信息输出场景」

    作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相信相信的力量! 从懵懂的少年,到拿起键盘,可以写一个Hell ...

  5. 前端笔记(关于webpack打包时内存溢出问题的解决)

    首先安装increase-memory-limit cnpm install -g increase-memory-limit 重启cmd,并在项目跟目录中运行一下 increase-memory-l ...

  6. 使用 Nginx 部署静态页面

    Nginx 介绍 Nginx 是俄罗斯人编写的十分轻量级的 HTTP 服务器, Nginx,它的发音为「engine X」,是一个高性能的 HTTP 和反向代理服务器,同时也是一个 IMAP/ POP ...

  7. Spreading the Wealth

    题目 A Communist regime is trying to redistribute wealth in a village. They have have decided to sit e ...

  8. Python之浅谈多态和封装

    目录 组合 什么是组合 为什么使用组合 多态和多态性 多态 什么是多态? 多态性 好处 多态性 什么是多态性 封装 封装是什么意思? 隐藏 如何用代码实现隐藏 python 实际上是可以访问隐藏属性的 ...

  9. idea2020.1.2破解,亲测可行,激活至2089年!

    一.下载最新版IDEA2020安装包 官网:https://www.jetbrains.com/idea/download/ 旧版:https://www.jetbrains.com/idea/dow ...

  10. kubernetes-pod驱逐机制

    1.驱逐策略 kubelet持续监控主机的资源使用情况,并尽量防止计算资源被耗尽.一旦出现资源紧缺的迹象,kubelet就会主动终止部分pod的运行,以回收资源. 2.驱逐信号 以下是一些kubele ...