机器学习实战---决策树CART回归树实现

时间:2022-09-10 13:22:10

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

一:对比分类树

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

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

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

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

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

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

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

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

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

表达式为:

机器学习实战---决策树CART回归树实现

其中,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
}
}

(七)全部代码

机器学习实战---决策树CART回归树实现机器学习实战---决策树CART回归树实现
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参数设置对剪枝的影响:

机器学习实战---决策树CART回归树实现机器学习实战---决策树CART回归树实现
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=(,)))

机器学习实战---决策树CART回归树实现

出现大量树分叉,过拟合

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

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

机器学习实战---决策树CART回归树实现

拟合状态还不错。

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

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

机器学习实战---决策树CART回归树实现

有点欠拟合。

(二)后剪枝

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

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

使用后剪枝方法需要将数据集分为测试集和训练集。通过训练集和参数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)

机器学习实战---决策树CART回归树实现

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

机器学习实战---决策树CART回归树实现机器学习实战---决策树CART回归树实现
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)

全部代码

四:模型树实现

机器学习实战---决策树CART回归树实现

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

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()

机器学习实战---决策树CART回归树实现

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

机器学习实战---决策树CART回归树实现

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

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

#实现预测回归树
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越好)

机器学习实战---决策树CART回归树实现机器学习实战---决策树CART回归树实现
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()

机器学习实战---决策树CART回归树实现

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

机器学习实战---决策树CART回归树实现机器学习实战---决策树CART回归树实现
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()

机器学习实战---决策树CART回归树实现

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

(五)一般线性回归

利用我们上面实现的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回归树实现

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

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

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

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

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

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

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

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

  4. CART回归树

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

  5. 机器学习实战 -- 决策树&lpar;ID3&rpar;

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

  6. &lbrack;机器学习&amp&semi;数据挖掘&rsqb;机器学习实战决策树plotTree函数完全解析

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

  7. 机器学习之路&colon; python 回归树 DecisionTreeRegressor 预测波士顿房价

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

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

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

  9. &lbrack;机器学习实战&rsqb; 决策树ID3算法

    1. 决策树特点: 1)优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据. 2)缺点:可能会产生过度匹配问题. 3)适用数据类型:数值型和标称型. 2. 一般流程: ...

随机推荐

  1. vuejs里封装的和IOS&comma;Android通信模块

    项目需要,在vuejs开发的web项目中与APP进行通信,实现原理和cordova一致.使用WebViewJavascriptBridge. 其实也是通过拦截url scheme,支持ios6往前的系 ...

  2. 【经验】ansible 批量推送公钥

    1.使用 ssh-keygen -t rsa生成密钥对 ssh-keygen -t rsa 2.推送单个公钥到远程机器 格式: ssh-copy-id -i ~/.ssh/id_rsa.pub use ...

  3. 和为S的连续正数序列

    小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100.但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数).没多久,他就得到另一 ...

  4. 【最小生成树】BZOJ 1196&colon; &lbrack;HNOI2006&rsqb;公路修建问题

    1196: [HNOI2006]公路修建问题 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1435  Solved: 810[Submit][Sta ...

  5. 秋叶PPT-三分钟教程

    http://yuedu.baidu.com/ebook/0596e5f858f5f61fb73666be <说服力-让你的PPT会说话>原创PPT分享 http://www.docin. ...

  6. asp&period;net 程序,当发生找不到文件的错误时,如何正确定位是哪个文件?

    需要在Global.asax.cs中添加Application_Error代码如下,在Log中查看是哪个文件缺失: protected void Application_Error(object se ...

  7. &lbrack;NewLife&period;XCode&rsqb;数据模型文件

    NewLife.XCode是一个有10多年历史的开源数据中间件,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含 ...

  8. BizTalk Server 如何处理大消息

    什么是大消息? 遗憾的是,此问题的答案不而直接与特定的消息大小,绑定,取决于你的 Microsoft 的特定瓶颈 BizTalk Server 系统. 与大消息关联的问题可分为以下几类: 内存不足错误 ...

  9. 大数据入门第四天——基础部分之轻量级RPC框架的开发

    一.概述 .掌握RPC原理 .掌握nio操作 .掌握netty简单的api .掌握自定义RPC框架 主要内容 1.RPC是什么 RPC(Remote Procedure Call)—远程过程调用,它是 ...

  10. Cacti监控mysql数据库服务器实现过程

    Cacti监控mysql数据库服务器实现过程 2014-05-29      0个评论    来源:Cacti监控mysql数据库服务器实现过程   收藏    我要投稿 1 先在cacti服务器端安 ...