CART迴歸樹和CART分類樹的創建算法大部分是相似的,因此這裏咱們只討論CART迴歸樹和CART分類樹的創建算法不一樣的地方。
首先,咱們要明白,什麼是迴歸樹,什麼是分類樹。html
若是樣本輸出是離散值,那麼這是一顆分類樹。 若是果樣本輸出是連續值,那麼那麼這是一顆迴歸樹。
1)連續值的處理方法不一樣
2)決策樹創建後作預測的方式不一樣。
對於連續值的處理,咱們知道CART分類樹採用的是用基尼係數的大小來度量特徵的各個劃分點的優劣狀況,這比較適合分類模型。算法
可是對於迴歸模型,咱們使用了常見的和方差的度量方式。機器學習
CART迴歸樹的度量目標是,對於任意劃分特徵A,對應的任意劃分點s兩邊劃分紅的數據集D1和D2,求出使D1和D2各自集合的均方差最小,同時D1和D2的均方差之和最小所對應的特徵和特徵值劃分點。ide
表達式爲:函數
其中,c1爲D1數據集的樣本輸出均值,c2爲D2數據集的樣本輸出均值。 post
對於決策樹創建後作預測的方式,上面講到了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=(1,4)): """ 選取的最好切分方式,使用回調方式調用葉節點計算和偏差計算,函數中含有預剪枝操做 :param data_X: 傳入數據集 :param data_Y: 傳入標籤值 :param leafType: 要調用計算的葉節點值 --- 雖然靈活,可是不必 :param errType: 要計算偏差的函數,這裏是均方偏差 --- 雖然靈活,可是不必 :param ops: 包含了兩個重要信息, tolS tolN用於控制函數的中止時機,tolS是允許的偏差降低值,偏差小於則再也不切分,tosN是切分的最少樣本數 :return: """ m,n = data_X.shape tolS = ops[0] tolN = ops[1] #以前都是將判斷是否繼續劃分子樹放入createTree方法中,這裏能夠提到chooseBestSplit中進行判別。 #固然能夠放入createTree方法中處理 if np.unique(data_Y).size == 1: #1.若是標籤值所有相同,則返回特徵None表示不須要進行下一步劃分,返回葉節點 return None,leafType(data_Y) #遍歷獲取最優特徵和特徵值 TosErr = errType(data_Y) #獲取所有數據集的偏差,後面計算劃分後兩個子集的總偏差,若是偏差降低小於tolS,則不進行劃分,返回該葉子節點便可(預剪枝操做) bestErr = np.inf bestFeaIdx = 0 #注意:這裏兩個咱們設置爲0,而不是-1,由於咱們必須保證能夠取到一個特徵(後面循環可能一直continue),咱們須要在後面進行額外處理 bestFeaVal = 0 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[0] < tolN or dataLg_X.shape[0] < 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[0] < tolN or dataLg_X.shape[0] < tolN: # 不符合最小數據集,不進行計算 return None,leafType(data_Y) return bestFeaIdx,bestFeaVal #正常狀況下的返回結果
def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(1,4)): #創建迴歸樹 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[:,0:n-1] data_Y = dataSet[:,n-1] return data_X,data_Y
data_X,data_Y = loadDataSet("ex0.txt") print(createTree(data_X,data_Y))
{'feaIdx': 1, 'feaVal': 0.39435, 'left': { 'feaIdx': 1, 'feaVal': 0.582002, 'left': { 'feaIdx': 1, 'feaVal': 0.797583, 'left': 3.9871632, 'right': 2.9836209534883724 }, 'right': 1.980035071428571 }, 'right': { 'feaIdx': 1, '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[:,0:n-1] data_Y = dataSet[:,n-1] 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=(1,4)): """ 選取的最好切分方式,使用回調方式調用葉節點計算和偏差計算,函數中含有預剪枝操做 :param data_X: 傳入數據集 :param data_Y: 傳入標籤值 :param leafType: 要調用計算的葉節點值 --- 雖然靈活,可是不必 :param errType: 要計算偏差的函數,這裏是均方偏差 --- 雖然靈活,可是不必 :param ops: 包含了兩個重要信息, tolS tolN用於控制函數的中止時機,tolS是允許的偏差降低值,偏差小於則再也不切分,tosN是切分的最少樣本數 :return: """ m,n = data_X.shape tolS = ops[0] tolN = ops[1] #以前都是將判斷是否繼續劃分子樹放入createTree方法中,這裏能夠提到chooseBestSplit中進行判別。 #固然能夠放入createTree方法中處理 if np.unique(data_Y).size == 1: #1.若是標籤值所有相同,則返回特徵None表示不須要進行下一步劃分,返回葉節點 return None,leafType(data_Y) #遍歷獲取最優特徵和特徵值 TosErr = errType(data_Y) #獲取所有數據集的偏差,後面計算劃分後兩個子集的總偏差,若是偏差降低小於tolS,則不進行劃分,返回該葉子節點便可(預剪枝操做) bestErr = np.inf bestFeaIdx = 0 #注意:這裏兩個咱們設置爲0,而不是-1,由於咱們必須保證能夠取到一個特徵(後面循環可能一直continue),咱們須要在後面進行額外處理 bestFeaVal = 0 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[0] < tolN or dataLg_X.shape[0] < 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[0] < tolN or dataLg_X.shape[0] < tolN: # 不符合最小數據集,不進行計算 return None,leafType(data_Y) return bestFeaIdx,bestFeaVal #正常狀況下的返回結果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(1,4)): #創建迴歸樹 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))
一棵樹若是節點過多,表示該模型可能對數據進行了過擬合(使用測試集交叉驗證法便可),這時就須要咱們進行剪枝處理,避免過擬合this
前面創建決策樹過程當中,咱們已經進行了預剪枝操做。即設置的ops參數,包含了兩個重要信息, tolS tolN用於控制函數的中止時機,tolS是允許的偏差降低值,偏差小於則再也不切分,tosN是切分的最少樣本數。用於在創建決策樹過程當中進行預剪枝操做。url
下面實例中,查看ops參數設置對剪枝的影響:
import numpy as np def loadDataSet(filename): dataSet = np.loadtxt(filename) m,n = dataSet.shape data_X = dataSet[:,0:n-1] data_Y = dataSet[:,n-1] 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=(1,4)): """ 選取的最好切分方式,使用回調方式調用葉節點計算和偏差計算,函數中含有預剪枝操做 :param data_X: 傳入數據集 :param data_Y: 傳入標籤值 :param leafType: 要調用計算的葉節點值 --- 雖然靈活,可是不必 :param errType: 要計算偏差的函數,這裏是均方偏差 --- 雖然靈活,可是不必 :param ops: 包含了兩個重要信息, tolS tolN用於控制函數的中止時機,tolS是允許的偏差降低值,偏差小於則再也不切分,tosN是切分的最少樣本數 :return: """ m,n = data_X.shape tolS = ops[0] tolN = ops[1] #以前都是將判斷是否繼續劃分子樹放入createTree方法中,這裏能夠提到chooseBestSplit中進行判別。 #固然能夠放入createTree方法中處理 if np.unique(data_Y).size == 1: #1.若是標籤值所有相同,則返回特徵None表示不須要進行下一步劃分,返回葉節點 return None,leafType(data_Y) #遍歷獲取最優特徵和特徵值 TosErr = errType(data_Y) #獲取所有數據集的偏差,後面計算劃分後兩個子集的總偏差,若是偏差降低小於tolS,則不進行劃分,返回該葉子節點便可(預剪枝操做) bestErr = np.inf bestFeaIdx = 0 #注意:這裏兩個咱們設置爲0,而不是-1,由於咱們必須保證能夠取到一個特徵(後面循環可能一直continue),咱們須要在後面進行額外處理 bestFeaVal = 0 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[0] < tolN or dataLg_X.shape[0] < 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[0] < tolN or dataLg_X.shape[0] < tolN: # 不符合最小數據集,不進行計算 return None,leafType(data_Y) return bestFeaIdx,bestFeaVal #正常狀況下的返回結果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(1,4)): #創建迴歸樹 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("ex2.txt") print(createTree(data_X,data_Y,ops=(1,4)))
出現大量樹分叉,過擬合
data_X,data_Y = loadDataSet("ex2.txt") print(createTree(data_X,data_Y,ops=(1000,4)))
擬合狀態還不錯。
data_X,data_Y = loadDataSet("ex2.txt") print(createTree(data_X,data_Y,ops=(10000,4)))
有點欠擬合。
後剪枝一般比預剪枝保留更多的分支,欠擬合風險小。可是後剪枝是在決策樹構造完成後進行的,其訓練時間的開銷會大於預剪枝。
後剪枝是基於已經創建好的樹,進行的葉子節點合併操做。
使用後剪枝方法須要將數據集分爲測試集和訓練集。經過訓練集和參數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'])/2 #返回均值 def prune(tree,testData_X,testData_Y): #根據決策樹和測試集數據進行後剪枝處理,不能按照訓練集進行後剪枝,由於建立決策樹時預剪枝操做中已經要求子樹偏差值小於根節點 #1.如果當測試集數據爲空,則不須要後面的子樹了,直接進行塌陷處理 if testData_X.shape[0] == 0: return getMean(tree) #2.若是當前測試集不爲空,並且決策樹含有左/右子樹,則須要進入子樹中進行剪枝操做---這裏咱們先將測試集數據劃分 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']) #3.根據子樹進行下一步剪枝 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) #注意:這裏是賦值操做,對樹進行剪枝 #4.若是兩個是葉子節點,咱們開始計算偏差,進行合併 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']) #進行偏差比較 #4-1.先獲取沒有合併的偏差 errorNoMerge = np.sum(np.power(TestDataGT_Y-tree['left'],2)) + np.sum(np.power(TestDataLG_Y-tree['right'],2)) #4-2.再獲取合併後的偏差 treemean = (tree['left'] + tree['right'])/2 #由於是葉子節點,能夠直接計算 errorMerge = np.sum(np.power(testData_Y- treemean,2)) #4-3.進行判斷 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)) #設置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[:,0:n-1] data_Y = dataSet[:,n-1] 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=(1,4)): """ 選取的最好切分方式,使用回調方式調用葉節點計算和偏差計算,函數中含有預剪枝操做 :param data_X: 傳入數據集 :param data_Y: 傳入標籤值 :param leafType: 要調用計算的葉節點值 --- 雖然靈活,可是不必 :param errType: 要計算偏差的函數,這裏是均方偏差 --- 雖然靈活,可是不必 :param ops: 包含了兩個重要信息, tolS tolN用於控制函數的中止時機,tolS是允許的偏差降低值,偏差小於則再也不切分,tosN是切分的最少樣本數 :return: """ m,n = data_X.shape tolS = ops[0] tolN = ops[1] #以前都是將判斷是否繼續劃分子樹放入createTree方法中,這裏能夠提到chooseBestSplit中進行判別。 #固然能夠放入createTree方法中處理 if np.unique(data_Y).size == 1: #1.若是標籤值所有相同,則返回特徵None表示不須要進行下一步劃分,返回葉節點 return None,leafType(data_Y) #遍歷獲取最優特徵和特徵值 TosErr = errType(data_Y) #獲取所有數據集的偏差,後面計算劃分後兩個子集的總偏差,若是偏差降低小於tolS,則不進行劃分,返回該葉子節點便可(預剪枝操做) bestErr = np.inf bestFeaIdx = 0 #注意:這裏兩個咱們設置爲0,而不是-1,由於咱們必須保證能夠取到一個特徵(後面循環可能一直continue),咱們須要在後面進行額外處理 bestFeaVal = 0 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[0] < tolN or dataLg_X.shape[0] < 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[0] < tolN or dataLg_X.shape[0] < tolN: # 不符合最小數據集,不進行計算 return None,leafType(data_Y) return bestFeaIdx,bestFeaVal #正常狀況下的返回結果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(1,4)): #創建迴歸樹 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'])/2 #返回均值 def prune(tree,testData_X,testData_Y): #根據決策樹和測試集數據進行後剪枝處理,不能按照訓練集進行後剪枝,由於建立決策樹時預剪枝操做中已經要求子樹偏差值小於根節點 #1.如果當測試集數據爲空,則不須要後面的子樹了,直接進行塌陷處理 if testData_X.shape[0] == 0: return getMean(tree) #2.若是當前測試集不爲空,並且決策樹含有左/右子樹,則須要進入子樹中進行剪枝操做---這裏咱們先將測試集數據劃分 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']) #3.根據子樹進行下一步剪枝 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) #注意:這裏是賦值操做,對樹進行剪枝 #4.若是兩個是葉子節點,咱們開始計算偏差,進行合併 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']) #進行偏差比較 #4-1.先獲取沒有合併的偏差 errorNoMerge = np.sum(np.power(TestDataGT_Y-tree['left'],2)) + np.sum(np.power(TestDataLG_Y-tree['right'],2)) #4-2.再獲取合併後的偏差 treemean = (tree['left'] + tree['right'])/2 #由於是葉子節點,能夠直接計算 errorMerge = np.sum(np.power(testData_Y- treemean,2)) #4-3.進行判斷 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)) #設置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[0]), data_X] XTX = X.T @ X if np.linalg.det(XTX) == 0: 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,2))
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=(1,4)): """ 選取的最好切分方式,使用回調方式調用葉節點計算和偏差計算,函數中含有預剪枝操做 :param data_X: 傳入數據集 :param data_Y: 傳入標籤值 :param leafType: 要調用計算的葉節點值 --- 雖然靈活,可是不必 :param errType: 要計算偏差的函數,這裏是均方偏差 --- 雖然靈活,可是不必 :param ops: 包含了兩個重要信息, tolS tolN用於控制函數的中止時機,tolS是允許的偏差降低值,偏差小於則再也不切分,tosN是切分的最少樣本數 :return: """ m,n = data_X.shape tolS = ops[0] tolN = ops[1] #以前都是將判斷是否繼續劃分子樹放入createTree方法中,這裏能夠提到chooseBestSplit中進行判別。 #固然能夠放入createTree方法中處理 if np.unique(data_Y).size == 1: #1.若是標籤值所有相同,則返回特徵None表示不須要進行下一步劃分,返回葉節點 return None,leafType(data_X,data_Y) #遍歷獲取最優特徵和特徵值 TosErr = errType(data_X,data_Y) #獲取所有數據集的偏差,後面計算劃分後兩個子集的總偏差,若是偏差降低小於tolS,則不進行劃分,返回該葉子節點便可(預剪枝操做) bestErr = np.inf bestFeaIdx = 0 #注意:這裏兩個咱們設置爲0,而不是-1,由於咱們必須保證能夠取到一個特徵(後面循環可能一直continue),咱們須要在後面進行額外處理 bestFeaVal = 0 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[0] < tolN or dataLg_X.shape[0] < tolN: #不符合最小數據集,不進行計算 continue concErr = errType(dataLg_X,dataLg_Y)+errType(dataGt_X,dataGt_Y) # print(concErr) if concErr < bestErr: bestFeaIdx = i bestFeaVal = feaval bestErr = concErr #2.若是最後求解的偏差,小於咱們要求的偏差距離,則不進行下一步劃分數據集(預剪枝) if (TosErr - bestErr) < tolS: return None,leafType(data_X,data_Y) #3.若是咱們上面的數據集自己較小,則不管如何切分,數據集都<tolN,咱們就須要在這裏再處理一遍,進行一下判斷 dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, bestFeaIdx, bestFeaVal) # 數據集劃分 if dataGt_X.shape[0] < tolN or dataLg_X.shape[0] < tolN: # 不符合最小數據集,不進行計算 return None,leafType(data_X,data_Y) return bestFeaIdx,bestFeaVal #正常狀況下的返回結果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(1,4)): #創建迴歸樹 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'])/2 #返回均值 def prune(tree,testData_X,testData_Y): #根據決策樹和測試集數據進行後剪枝處理,不能按照訓練集進行後剪枝,由於建立決策樹時預剪枝操做中已經要求子樹偏差值小於根節點 #1.如果當測試集數據爲空,則不須要後面的子樹了,直接進行塌陷處理 if testData_X.shape[0] == 0: return getMean(tree) #2.若是當前測試集不爲空,並且決策樹含有左/右子樹,則須要進入子樹中進行剪枝操做---這裏咱們先將測試集數據劃分 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']) #3.根據子樹進行下一步剪枝 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) #注意:這裏是賦值操做,對樹進行剪枝 #4.若是兩個是葉子節點,咱們開始計算偏差,進行合併 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']) #進行偏差比較 #4-1.先獲取沒有合併的偏差 errorNoMerge = np.sum(np.power(TestDataGT_Y-tree['left'],2)) + np.sum(np.power(TestDataLG_Y-tree['right'],2)) #4-2.再獲取合併後的偏差 treemean = (tree['left'] + tree['right'])/2 #由於是葉子節點,能夠直接計算 errorMerge = np.sum(np.power(testData_Y- treemean,2)) #4-3.進行判斷 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=(1,10)) #設置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[0]),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,1)) for i in range(m): #開始預測 yPred[i] = treeForeCast(tree,testData_X[i],modelEval) return yPred
import numpy as np import matplotlib.pyplot as plt def loadDataSet(filename): dataSet = np.loadtxt(filename) m,n = dataSet.shape data_X = dataSet[:,0:n-1] data_Y = dataSet[:,n-1] 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=(1,4)): """ 選取的最好切分方式,使用回調方式調用葉節點計算和偏差計算,函數中含有預剪枝操做 :param data_X: 傳入數據集 :param data_Y: 傳入標籤值 :param leafType: 要調用計算的葉節點值 --- 雖然靈活,可是不必 :param errType: 要計算偏差的函數,這裏是均方偏差 --- 雖然靈活,可是不必 :param ops: 包含了兩個重要信息, tolS tolN用於控制函數的中止時機,tolS是允許的偏差降低值,偏差小於則再也不切分,tosN是切分的最少樣本數 :return: """ m,n = data_X.shape tolS = ops[0] tolN = ops[1] #以前都是將判斷是否繼續劃分子樹放入createTree方法中,這裏能夠提到chooseBestSplit中進行判別。 #固然能夠放入createTree方法中處理 if np.unique(data_Y).size == 1: #1.若是標籤值所有相同,則返回特徵None表示不須要進行下一步劃分,返回葉節點 return None,leafType(data_Y) #遍歷獲取最優特徵和特徵值 TosErr = errType(data_Y) #獲取所有數據集的偏差,後面計算劃分後兩個子集的總偏差,若是偏差降低小於tolS,則不進行劃分,返回該葉子節點便可(預剪枝操做) bestErr = np.inf bestFeaIdx = 0 #注意:這裏兩個咱們設置爲0,而不是-1,由於咱們必須保證能夠取到一個特徵(後面循環可能一直continue),咱們須要在後面進行額外處理 bestFeaVal = 0 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[0] < tolN or dataLg_X.shape[0] < 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[0] < tolN or dataLg_X.shape[0] < tolN: # 不符合最小數據集,不進行計算 return None,leafType(data_Y) return bestFeaIdx,bestFeaVal #正常狀況下的返回結果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(1,4)): #創建迴歸樹 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'])/2 #返回均值 def prune(tree,testData_X,testData_Y): #根據決策樹和測試集數據進行後剪枝處理,不能按照訓練集進行後剪枝,由於建立決策樹時預剪枝操做中已經要求子樹偏差值小於根節點 #1.如果當測試集數據爲空,則不須要後面的子樹了,直接進行塌陷處理 if testData_X.shape[0] == 0: return getMean(tree) #2.若是當前測試集不爲空,並且決策樹含有左/右子樹,則須要進入子樹中進行剪枝操做---這裏咱們先將測試集數據劃分 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']) #3.根據子樹進行下一步剪枝 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) #注意:這裏是賦值操做,對樹進行剪枝 #4.若是兩個是葉子節點,咱們開始計算偏差,進行合併 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']) #進行偏差比較 #4-1.先獲取沒有合併的偏差 errorNoMerge = np.sum(np.power(TestDataGT_Y-tree['left'],2)) + np.sum(np.power(TestDataLG_Y-tree['right'],2)) #4-2.再獲取合併後的偏差 treemean = (tree['left'] + tree['right'])/2 #由於是葉子節點,能夠直接計算 errorMerge = np.sum(np.power(testData_Y- treemean,2)) #4-3.進行判斷 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[0]),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,1)) 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=(1,20)) #訓練集數據建決策模型樹 print(myTree) testData_X,testData_Y = loadDataSet('bikeSpeedVsIq_test.txt') #測試集數據 yPred = createForecast(myTree,testData_X) #使用模型樹預測 print(np.corrcoef(yPred,testData_Y,rowvar=0)[0,1]) 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=(1,20)) #訓練集數據建決策模型樹 print(myTree) testData_X,testData_Y = loadDataSet('bikeSpeedVsIq_test.txt') #測試集數據 yPred = createForecast(myTree,testData_X) #使用模型樹預測 print(np.corrcoef(yPred,testData_Y,rowvar=0)[0,1]) plt.figure() plt.scatter(data_X.flatten(),data_Y.flatten()) plt.show()
import numpy as np import matplotlib.pyplot as plt def loadDataSet(filename): dataSet = np.loadtxt(filename) m,n = dataSet.shape data_X = dataSet[:,0:n-1] data_Y = dataSet[:,n-1] 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[0]), data_X] XTX = X.T @ X if np.linalg.det(XTX) == 0: 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,2)) 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=(1,4)): """ 選取的最好切分方式,使用回調方式調用葉節點計算和偏差計算,函數中含有預剪枝操做 :param data_X: 傳入數據集 :param data_Y: 傳入標籤值 :param leafType: 要調用計算的葉節點值 --- 雖然靈活,可是不必 :param errType: 要計算偏差的函數,這裏是均方偏差 --- 雖然靈活,可是不必 :param ops: 包含了兩個重要信息, tolS tolN用於控制函數的中止時機,tolS是允許的偏差降低值,偏差小於則再也不切分,tosN是切分的最少樣本數 :return: """ m,n = data_X.shape tolS = ops[0] tolN = ops[1] #以前都是將判斷是否繼續劃分子樹放入createTree方法中,這裏能夠提到chooseBestSplit中進行判別。 #固然能夠放入createTree方法中處理 if np.unique(data_Y).size == 1: #1.若是標籤值所有相同,則返回特徵None表示不須要進行下一步劃分,返回葉節點 return None,leafType(data_X,data_Y) #遍歷獲取最優特徵和特徵值 TosErr = errType(data_X,data_Y) #獲取所有數據集的偏差,後面計算劃分後兩個子集的總偏差,若是偏差降低小於tolS,則不進行劃分,返回該葉子節點便可(預剪枝操做) bestErr = np.inf bestFeaIdx = 0 #注意:這裏兩個咱們設置爲0,而不是-1,由於咱們必須保證能夠取到一個特徵(後面循環可能一直continue),咱們須要在後面進行額外處理 bestFeaVal = 0 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[0] < tolN or dataLg_X.shape[0] < tolN: #不符合最小數據集,不進行計算 continue concErr = errType(dataLg_X,dataLg_Y)+errType(dataGt_X,dataGt_Y) # print(concErr) if concErr < bestErr: bestFeaIdx = i bestFeaVal = feaval bestErr = concErr #2.若是最後求解的偏差,小於咱們要求的偏差距離,則不進行下一步劃分數據集(預剪枝) if (TosErr - bestErr) < tolS: return None,leafType(data_X,data_Y) #3.若是咱們上面的數據集自己較小,則不管如何切分,數據集都<tolN,咱們就須要在這裏再處理一遍,進行一下判斷 dataGt_X, dataGt_Y, dataLg_X, dataLg_Y = binSplitDataSet(data_X, data_Y, bestFeaIdx, bestFeaVal) # 數據集劃分 if dataGt_X.shape[0] < tolN or dataLg_X.shape[0] < tolN: # 不符合最小數據集,不進行計算 return None,leafType(data_X,data_Y) return bestFeaIdx,bestFeaVal #正常狀況下的返回結果 def createTree(data_X,data_Y,leafType=regLeaf,errType=regErr,ops=(1,4)): #創建迴歸樹 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'])/2 #返回均值 def prune(tree,testData_X,testData_Y): #根據決策樹和測試集數據進行後剪枝處理,不能按照訓練集進行後剪枝,由於建立決策樹時預剪枝操做中已經要求子樹偏差值小於根節點 #1.如果當測試集數據爲空,則不須要後面的子樹了,直接進行塌陷處理 if testData_X.shape[0] == 0: return getMean(tree) #2.若是當前測試集不爲空,並且決策樹含有左/右子樹,則須要進入子樹中進行剪枝操做---這裏咱們先將測試集數據劃分 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']) #3.根據子樹進行下一步剪枝 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) #注意:這裏是賦值操做,對樹進行剪枝 #4.若是兩個是葉子節點,咱們開始計算偏差,進行合併 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']) #進行偏差比較 #4-1.先獲取沒有合併的偏差 errorNoMerge = np.sum(np.power(TestDataGT_Y-tree['left'],2)) + np.sum(np.power(TestDataLG_Y-tree['right'],2)) #4-2.再獲取合併後的偏差 treemean = (tree['left'] + tree['right'])/2 #由於是葉子節點,能夠直接計算 errorMerge = np.sum(np.power(testData_Y- treemean,2)) #4-3.進行判斷 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[0]),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,1)) 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=(1,20)) #訓練集數據建決策模型樹 print(myTree) testData_X,testData_Y = loadDataSet('bikeSpeedVsIq_test.txt') #測試集數據 yPred = createForecast(myTree,testData_X,modelTreeEval) #使用模型樹預測 print(np.corrcoef(yPred,testData_Y,rowvar=0)[0,1]) 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=(1,20)) #訓練集數據建決策模型樹 print(myTree) testData_X,testData_Y = loadDataSet('bikeSpeedVsIq_test.txt') #測試集數據 yPred = createForecast(myTree,testData_X,modelTreeEval) #使用模型樹預測 print(np.corrcoef(yPred,testData_Y,rowvar=0)[0,1]) plt.figure() plt.scatter(data_X.flatten(),data_Y.flatten()) plt.show()
能夠看到模型樹優於迴歸樹
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[0],1)) testDX = np.c_[np.ones(testData_X.shape[0]),testData_X] for i in range(testData_X.shape[0]): yPred2[i] = testDX[i]@W print(np.corrcoef(yPred2,testData_Y,rowvar=0)[0,1])
因此,樹迴歸方法在預測複雜數據時,會比簡單的線性模型更加有效