第9章 樹回歸
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=default"></script>

樹回歸 概述
我們本章介紹 CART(Classification And Regression Trees, 分類回歸樹) 的樹構(gòu)建算法。該算法既可以用于分類還可以用于回歸。
樹回歸 場景
我們在第 8 章中介紹了線性回歸的一些強大的方法,但這些方法創(chuàng)建的模型需要擬合所有的樣本點(局部加權(quán)線性回歸除外)。當數(shù)據(jù)擁有眾多特征并且特征之間關系十分復雜時,構(gòu)建全局模型的想法就顯得太難了,也略顯笨拙。而且,實際生活中很多問題都是非線性的,不可能使用全局線性模型來擬合任何數(shù)據(jù)。
一種可行的方法是將數(shù)據(jù)集切分成很多份易建模的數(shù)據(jù),然后利用我們的線性回歸技術來建模。如果首次切分后仍然難以擬合線性模型就繼續(xù)切分。在這種切分方式下,樹回歸和回歸法就相當有用。
除了我們在 第3章 中介紹的 決策樹算法,我們介紹一個新的叫做 CART(Classification And Regression Trees, 分類回歸樹) 的樹構(gòu)建算法。該算法既可以用于分類還可以用于回歸。
1、樹回歸 原理
1.1、樹回歸 原理概述
為成功構(gòu)建以分段常數(shù)為葉節(jié)點的樹,需要度量出數(shù)據(jù)的一致性。第3章使用樹進行分類,會在給定節(jié)點時計算數(shù)據(jù)的混亂度。那么如何計算連續(xù)型數(shù)值的混亂度呢?
在這里,計算連續(xù)型數(shù)值的混亂度是非常簡單的。首先計算所有數(shù)據(jù)的均值,然后計算每條數(shù)據(jù)的值到均值的差值。為了對正負差值同等看待,一般使用絕對值或平方值來代替上述差值。
上述做法有點類似于前面介紹過的統(tǒng)計學中常用的方差計算。唯一不同就是,方差是平方誤差的均值(均方差),而這里需要的是平方誤差的總值(總方差)??偡讲羁梢酝ㄟ^均方差乘以數(shù)據(jù)集中樣本點的個數(shù)來得到。
1.2、樹構(gòu)建算法 比較
我們在 第3章 中使用的樹構(gòu)建算法是 ID3 。ID3 的做法是每次選取當前最佳的特征來分割數(shù)據(jù),并按照該特征的所有可能取值來切分。也就是說,如果一個特征有 4 種取值,那么數(shù)據(jù)將被切分成 4 份。一旦按照某特征切分后,該特征在之后的算法執(zhí)行過程中將不會再起作用,所以有觀點認為這種切分方式過于迅速。另外一種方法是二分切分法,即每次把數(shù)據(jù)集切分成兩份。如果數(shù)據(jù)的某特征值等于切分所要求的值,那么這些數(shù)據(jù)就進入樹的左子樹,反之則進入樹的右子樹。
除了切分過于迅速外, ID3 算法還存在另一個問題,它不能直接處理連續(xù)型特征。只有事先將連續(xù)型特征轉(zhuǎn)換成離散型,才能在 ID3 算法中使用。但這種轉(zhuǎn)換過程會破壞連續(xù)型變量的內(nèi)在性質(zhì)。而使用二元切分法則易于對樹構(gòu)造過程進行調(diào)整以處理連續(xù)型特征。具體的處理方法是: 如果特征值大于給定值就走左子樹,否則就走右子樹。另外,二分切分法也節(jié)省了樹的構(gòu)建時間,但這點意義也不是特別大,因為這些樹構(gòu)建一般是離線完成,時間并非需要重點關注的因素。
CART 是十分著名且廣泛記載的樹構(gòu)建算法,它使用二元切分來處理連續(xù)型變量。對 CART 稍作修改就可以處理回歸問題。第 3 章中使用香農(nóng)熵來度量集合的無組織程度。如果選用其他方法來代替香農(nóng)熵,就可以使用樹構(gòu)建算法來完成回歸。
回歸樹與分類樹的思路類似,但是葉節(jié)點的數(shù)據(jù)類型不是離散型,而是連續(xù)型。
1.2.1、附加 各常見樹構(gòu)造算法的劃分分支方式
還有一點要說明,構(gòu)建決策樹算法,常用到的是三個方法: ID3, C4.5, CART.
三種方法區(qū)別是劃分樹的分支的方式:
- ID3 是信息增益分支
- C4.5 是信息增益率分支
- CART 是 GINI 系數(shù)分支
工程上總的來說:
CART 和 C4.5 之間主要差異在于分類結(jié)果上,CART 可以回歸分析也可以分類,C4.5 只能做分類;C4.5 子節(jié)點是可以多分的,而 CART 是無數(shù)個二叉子節(jié)點;
以此拓展出以 CART 為基礎的 “樹群” Random forest , 以 回歸樹 為基礎的 “樹群” GBDT 。
1.3、樹回歸 工作原理
1、找到數(shù)據(jù)集切分的最佳位置,函數(shù) chooseBestSplit() 偽代碼大致如下:
對每個特征:
對每個特征值:
將數(shù)據(jù)集切分成兩份(小于該特征值的數(shù)據(jù)樣本放在左子樹,否則放在右子樹)
計算切分的誤差
如果當前誤差小于當前最小誤差,那么將當前切分設定為最佳切分并更新最小誤差
返回最佳切分的特征和閾值
2、樹構(gòu)建算法,函數(shù) createTree() 偽代碼大致如下:
找到最佳的待切分特征:
如果該節(jié)點不能再分,將該節(jié)點存為葉節(jié)點
執(zhí)行二元切分
在右子樹調(diào)用 createTree() 方法
在左子樹調(diào)用 createTree() 方法
1.4、樹回歸 開發(fā)流程
(1) 收集數(shù)據(jù):采用任意方法收集數(shù)據(jù)。
(2) 準備數(shù)據(jù):需要數(shù)值型數(shù)據(jù),標稱型數(shù)據(jù)應該映射成二值型數(shù)據(jù)。
(3) 分析數(shù)據(jù):繪出數(shù)據(jù)的二維可視化顯示結(jié)果,以字典方式生成樹。
(4) 訓練算法:大部分時間都花費在葉節(jié)點樹模型的構(gòu)建上。
(5) 測試算法:使用測試數(shù)據(jù)上的R^2值來分析模型的效果。
(6) 使用算法:使用訓練處的樹做預測,預測結(jié)果還可以用來做很多事情。
1.5、樹回歸 算法特點
優(yōu)點:可以對復雜和非線性的數(shù)據(jù)建模。
缺點:結(jié)果不易理解。
適用數(shù)據(jù)類型:數(shù)值型和標稱型數(shù)據(jù)。
1.6、回歸樹 項目案例
1.6.1、項目概述
在簡單數(shù)據(jù)集上生成一棵回歸樹。
1.6.2、開發(fā)流程
收集數(shù)據(jù):采用任意方法收集數(shù)據(jù)
準備數(shù)據(jù):需要數(shù)值型數(shù)據(jù),標稱型數(shù)據(jù)應該映射成二值型數(shù)據(jù)
分析數(shù)據(jù):繪出數(shù)據(jù)的二維可視化顯示結(jié)果,以字典方式生成樹
訓練算法:大部分時間都花費在葉節(jié)點樹模型的構(gòu)建上
測試算法:使用測試數(shù)據(jù)上的R^2值來分析模型的效果
使用算法:使用訓練出的樹做預測,預測結(jié)果還可以用來做很多事情
收集數(shù)據(jù):采用任意方法收集數(shù)據(jù)
data1.txt 文件中存儲的數(shù)據(jù)格式如下:
0.036098 0.155096
0.993349 1.077553
0.530897 0.893462
0.712386 0.564858
0.343554 -0.371700
0.098016 -0.332760
準備數(shù)據(jù):需要數(shù)值型數(shù)據(jù),標稱型數(shù)據(jù)應該映射成二值型數(shù)據(jù)
分析數(shù)據(jù):繪出數(shù)據(jù)的二維可視化顯示結(jié)果,以字典方式生成樹
基于 CART 算法構(gòu)建回歸樹的簡單數(shù)據(jù)集

用于測試回歸樹的分段常數(shù)數(shù)據(jù)集

訓練算法: 構(gòu)造樹的數(shù)據(jù)結(jié)構(gòu)
def binSplitDataSet(dataSet, feature, value):
"""binSplitDataSet(將數(shù)據(jù)集,按照feature列的value進行 二元切分)
Description:在給定特征和特征值的情況下,該函數(shù)通過數(shù)組過濾方式將上述數(shù)據(jù)集合切分得到兩個子集并返回。
Args:
dataMat 數(shù)據(jù)集
feature 待切分的特征列
value 特征列要比較的值
Returns:
mat0 小于等于 value 的數(shù)據(jù)集在左邊
mat1 大于 value 的數(shù)據(jù)集在右邊
Raises:
"""
# # 測試案例
# print 'dataSet[:, feature]=', dataSet[:, feature]
# print 'nonzero(dataSet[:, feature] > value)[0]=', nonzero(dataSet[:, feature] > value)[0]
# print 'nonzero(dataSet[:, feature] <= value)[0]=', nonzero(dataSet[:, feature] <= value)[0]
# dataSet[:, feature] 取去每一行中,第1列的值(從0開始算)
# nonzero(dataSet[:, feature] > value) 返回結(jié)果為true行的index下標
mat0 = dataSet[nonzero(dataSet[:, feature] <= value)[0], :]
mat1 = dataSet[nonzero(dataSet[:, feature] > value)[0], :]
return mat0, mat1
# 1.用最佳方式切分數(shù)據(jù)集
# 2.生成相應的葉節(jié)點
def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)):
"""chooseBestSplit(用最佳方式切分數(shù)據(jù)集 和 生成相應的葉節(jié)點)
Args:
dataSet 加載的原始數(shù)據(jù)集
leafType 建立葉子點的函數(shù)
errType 誤差計算函數(shù)(求總方差)
ops [容許誤差下降值,切分的最少樣本數(shù)]。
Returns:
bestIndex feature的index坐標
bestValue 切分的最優(yōu)值
Raises:
"""
# ops=(1,4),非常重要,因為它決定了決策樹劃分停止的threshold值,被稱為預剪枝(prepruning),其實也就是用于控制函數(shù)的停止時機。
# 之所以這樣說,是因為它防止決策樹的過擬合,所以當誤差的下降值小于tolS,或劃分后的集合size小于tolN時,選擇停止繼續(xù)劃分。
# 最小誤差下降值,劃分后的誤差減小小于這個差值,就不用繼續(xù)劃分
tolS = ops[0]
# 劃分最小 size 小于,就不繼續(xù)劃分了
tolN = ops[1]
# 如果結(jié)果集(最后一列為1個變量),就返回退出
# .T 對數(shù)據(jù)集進行轉(zhuǎn)置
# .tolist()[0] 轉(zhuǎn)化為數(shù)組并取第0列
if len(set(dataSet[:, -1].T.tolist()[0])) == 1: # 如果集合size為1,不用繼續(xù)劃分。
# exit cond 1
return None, leafType(dataSet)
# 計算行列值
m, n = shape(dataSet)
# 無分類誤差的總方差和
# the choice of the best feature is driven by Reduction in RSS error from mean
S = errType(dataSet)
# inf 正無窮大
bestS, bestIndex, bestValue = inf, 0, 0
# 循環(huán)處理每一列對應的feature值
for featIndex in range(n-1): # 對于每個特征
# [0]表示這一列的[所有行],不要[0]就是一個array[[所有行]]
for splitVal in set(dataSet[:, featIndex].T.tolist()[0]):
# 對該列進行分組,然后組內(nèi)的成員的val值進行 二元切分
mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)
# 判斷二元切分的方式的元素數(shù)量是否符合預期
if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN):
continue
newS = errType(mat0) + errType(mat1)
# 如果二元切分,算出來的誤差在可接受范圍內(nèi),那么就記錄切分點,并記錄最小誤差
# 如果劃分后誤差小于 bestS,則說明找到了新的bestS
if newS < bestS:
bestIndex = featIndex
bestValue = splitVal
bestS = newS
# 判斷二元切分的方式的元素誤差是否符合預期
# if the decrease (S-bestS) is less than a threshold don't do the split
if (S - bestS) < tolS:
return None, leafType(dataSet)
mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
# 對整體的成員進行判斷,是否符合預期
# 如果集合的 size 小于 tolN
if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): # 當最佳劃分后,集合過小,也不劃分,產(chǎn)生葉節(jié)點
return None, leafType(dataSet)
return bestIndex, bestValue
# assume dataSet is NumPy Mat so we can array filtering
# 假設 dataSet 是 NumPy Mat 類型的,那么我們可以進行 array 過濾
def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)):
"""createTree(獲取回歸樹)
Description:遞歸函數(shù):如果構(gòu)建的是回歸樹,該模型是一個常數(shù),如果是模型樹,其模型師一個線性方程。
Args:
dataSet 加載的原始數(shù)據(jù)集
leafType 建立葉子點的函數(shù)
errType 誤差計算函數(shù)
ops=(1, 4) [容許誤差下降值,切分的最少樣本數(shù)]
Returns:
retTree 決策樹最后的結(jié)果
"""
# 選擇最好的切分方式: feature索引值,最優(yōu)切分值
# choose the best split
feat, val = chooseBestSplit(dataSet, leafType, errType, ops)
# if the splitting hit a stop condition return val
# 如果 splitting 達到一個停止條件,那么返回 val
if feat is None:
return val
retTree = {}
retTree['spInd'] = feat
retTree['spVal'] = val
# 大于在右邊,小于在左邊,分為2個數(shù)據(jù)集
lSet, rSet = binSplitDataSet(dataSet, feat, val)
# 遞歸的進行調(diào)用,在左右子樹中繼續(xù)遞歸生成樹
retTree['left'] = createTree(lSet, leafType, errType, ops)
retTree['right'] = createTree(rSet, leafType, errType, ops)
return retTree
完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/9.RegTrees/regTrees.py
測試算法:使用測試數(shù)據(jù)上的R^2值來分析模型的效果
使用算法:使用訓練出的樹做預測,預測結(jié)果還可以用來做很多事情
2、樹剪枝
一棵樹如果節(jié)點過多,表明該模型可能對數(shù)據(jù)進行了 “過擬合”。
通過降低決策樹的復雜度來避免過擬合的過程稱為 剪枝(pruning)。在函數(shù) chooseBestSplit() 中提前終止條件,實際上是在進行一種所謂的 預剪枝(prepruning)操作。另一個形式的剪枝需要使用測試集和訓練集,稱作 后剪枝(postpruning)。
2.1、預剪枝(prepruning)
顧名思義,預剪枝就是及早的停止樹增長,在構(gòu)造決策樹的同時進行剪枝。
所有決策樹的構(gòu)建方法,都是在無法進一步降低熵的情況下才會停止創(chuàng)建分支的過程,為了避免過擬合,可以設定一個閾值,熵減小的數(shù)量小于這個閾值,即使還可以繼續(xù)降低熵,也停止繼續(xù)創(chuàng)建分支。但是這種方法實際中的效果并不好。
2.2、后剪枝(postpruning)
決策樹構(gòu)造完成后進行剪枝。剪枝的過程是對擁有同樣父節(jié)點的一組節(jié)點進行檢查,判斷如果將其合并,熵的增加量是否小于某一閾值。如果確實小,則這一組節(jié)點可以合并一個節(jié)點,其中包含了所有可能的結(jié)果。合并也被稱作 塌陷處理 ,在回歸樹中一般采用取需要合并的所有子樹的平均值。后剪枝是目前最普遍的做法。
后剪枝 prune() 的偽代碼如下:
基于已有的樹切分測試數(shù)據(jù):
如果存在任一子集是一棵樹,則在該子集遞歸剪枝過程
計算將當前兩個葉節(jié)點合并后的誤差
計算不合并的誤差
如果合并會降低誤差的話,就將葉節(jié)點合并
2.3、剪枝 代碼
回歸樹剪枝函數(shù)
# 判斷節(jié)點是否是一個字典
def isTree(obj):
"""
Desc:
測試輸入變量是否是一棵樹,即是否是一個字典
Args:
obj -- 輸入變量
Returns:
返回布爾類型的結(jié)果。如果 obj 是一個字典,返回true,否則返回 false
"""
return (type(obj).__name__ == 'dict')
# 計算左右枝丫的均值
def getMean(tree):
"""
Desc:
從上往下遍歷樹直到葉節(jié)點為止,如果找到兩個葉節(jié)點則計算它們的平均值。
對 tree 進行塌陷處理,即返回樹平均值。
Args:
tree -- 輸入的樹
Returns:
返回 tree 節(jié)點的平均值
"""
if isTree(tree['right']):
tree['right'] = getMean(tree['right'])
if isTree(tree['left']):
tree['left'] = getMean(tree['left'])
return (tree['left']+tree['right'])/2.0
# 檢查是否適合合并分枝
def prune(tree, testData):
"""
Desc:
從上而下找到葉節(jié)點,用測試數(shù)據(jù)集來判斷將這些葉節(jié)點合并是否能降低測試誤差
Args:
tree -- 待剪枝的樹
testData -- 剪枝所需要的測試數(shù)據(jù) testData
Returns:
tree -- 剪枝完成的樹
"""
# 判斷是否測試數(shù)據(jù)集沒有數(shù)據(jù),如果沒有,就直接返回tree本身的均值
if shape(testData)[0] == 0:
return getMean(tree)
# 判斷分枝是否是dict字典,如果是就將測試數(shù)據(jù)集進行切分
if (isTree(tree['right']) or isTree(tree['left'])):
lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
# 如果是左邊分枝是字典,就傳入左邊的數(shù)據(jù)集和左邊的分枝,進行遞歸
if isTree(tree['left']):
tree['left'] = prune(tree['left'], lSet)
# 如果是右邊分枝是字典,就傳入左邊的數(shù)據(jù)集和左邊的分枝,進行遞歸
if isTree(tree['right']):
tree['right'] = prune(tree['right'], rSet)
# 上面的一系列操作本質(zhì)上就是將測試數(shù)據(jù)集按照訓練完成的樹拆分好,對應的值放到對應的節(jié)點
# 如果左右兩邊同時都不是dict字典,也就是左右兩邊都是葉節(jié)點,而不是子樹了,那么分割測試數(shù)據(jù)集。
# 1. 如果正確
# * 那么計算一下總方差 和 該結(jié)果集的本身不分枝的總方差比較
# * 如果 合并的總方差 < 不合并的總方差,那么就進行合并
# 注意返回的結(jié)果: 如果可以合并,原來的dict就變?yōu)榱?數(shù)值
if not isTree(tree['left']) and not isTree(tree['right']):
lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
# power(x, y)表示x的y次方
errorNoMerge = sum(power(lSet[:, -1] - tree['left'], 2)) + sum(power(rSet[:, -1] - tree['right'], 2))
treeMean = (tree['left'] + tree['right'])/2.0
errorMerge = sum(power(testData[:, -1] - treeMean, 2))
# 如果 合并的總方差 < 不合并的總方差,那么就進行合并
if errorMerge < errorNoMerge:
print "merging"
return treeMean
else:
return tree
else:
return tree
完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/9.RegTrees/regTrees.py
3、模型樹
3.1、模型樹 簡介
用樹來對數(shù)據(jù)建模,除了把葉節(jié)點簡單地設定為常數(shù)值之外,還有一種方法是把葉節(jié)點設定為分段線性函數(shù),這里所謂的 分段線性(piecewise linear) 是指模型由多個線性片段組成。
我們看一下圖 9-4 中的數(shù)據(jù),如果使用兩條直線擬合是否比使用一組常數(shù)來建模好呢?答案顯而易見??梢栽O計兩條分別從 0.0~0.3、從 0.3~1.0 的直線,于是就可以得到兩個線性模型。因為數(shù)據(jù)集里的一部分數(shù)據(jù)(0.00.3)以某個線性模型建模,而另一部分數(shù)據(jù)(0.31.0)則以另一個線性模型建模,因此我們說采用了所謂的分段線性模型。
決策樹相比于其他機器學習算法的優(yōu)勢之一在于結(jié)果更易理解。很顯然,兩條直線比很多節(jié)點組成一棵大樹更容易解釋。模型樹的可解釋性是它優(yōu)于回歸樹的特點之一。另外,模型樹也具有更高的預測準確度。

將之前的回歸樹的代碼稍作修改,就可以在葉節(jié)點生成線性模型而不是常數(shù)值。下面將利用樹生成算法對數(shù)據(jù)進行劃分,且每份切分數(shù)據(jù)都能很容易被線性模型所表示。這個算法的關鍵在于誤差的計算。
那么為了找到最佳切分,應該怎樣計算誤差呢?前面用于回歸樹的誤差計算方法這里不能再用。稍加變化,對于給定的數(shù)據(jù)集,應該先用模型來對它進行擬合,然后計算真實的目標值與模型預測值間的差值。最后將這些差值的平方求和就得到了所需的誤差。
3.2、模型樹 代碼
模型樹的葉節(jié)點生成函數(shù)
# 得到模型的ws系數(shù):f(x) = x0 + x1*featrue1+ x3*featrue2 ...
# create linear model and return coeficients
def modelLeaf(dataSet):
"""
Desc:
當數(shù)據(jù)不再需要切分的時候,生成葉節(jié)點的模型。
Args:
dataSet -- 輸入數(shù)據(jù)集
Returns:
調(diào)用 linearSolve 函數(shù),返回得到的 回歸系數(shù)ws
"""
ws, X, Y = linearSolve(dataSet)
return ws
# 計算線性模型的誤差值
def modelErr(dataSet):
"""
Desc:
在給定數(shù)據(jù)集上計算誤差。
Args:
dataSet -- 輸入數(shù)據(jù)集
Returns:
調(diào)用 linearSolve 函數(shù),返回 yHat 和 Y 之間的平方誤差。
"""
ws, X, Y = linearSolve(dataSet)
yHat = X * ws
# print corrcoef(yHat, Y, rowvar=0)
return sum(power(Y - yHat, 2))
# helper function used in two places
def linearSolve(dataSet):
"""
Desc:
將數(shù)據(jù)集格式化成目標變量Y和自變量X,執(zhí)行簡單的線性回歸,得到ws
Args:
dataSet -- 輸入數(shù)據(jù)
Returns:
ws -- 執(zhí)行線性回歸的回歸系數(shù)
X -- 格式化自變量X
Y -- 格式化目標變量Y
"""
m, n = shape(dataSet)
# 產(chǎn)生一個關于1的矩陣
X = mat(ones((m, n)))
Y = mat(ones((m, 1)))
# X的0列為1,常數(shù)項,用于計算平衡誤差
X[:, 1: n] = dataSet[:, 0: n-1]
Y = dataSet[:, -1]
# 轉(zhuǎn)置矩陣*矩陣
xTx = X.T * X
# 如果矩陣的逆不存在,會造成程序異常
if linalg.det(xTx) == 0.0:
raise NameError('This matrix is singular, cannot do inverse,\ntry increasing the second value of ops')
# 最小二乘法求最優(yōu)解: w0*1+w1*x1=y
ws = xTx.I * (X.T * Y)
return ws, X, Y
完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/9.RegTrees/regTrees.py
3.3、模型樹 運行結(jié)果

4、樹回歸 項目案例
4.1、項目案例1: 樹回歸與標準回歸的比較
4.1.1、項目概述
前面介紹了模型樹、回歸樹和一般的回歸方法,下面測試一下哪個模型最好。
這些模型將在某個數(shù)據(jù)上進行測試,該數(shù)據(jù)涉及人的智力水平和自行車的速度的關系。當然,數(shù)據(jù)是假的。
4.1.2、開發(fā)流程
收集數(shù)據(jù):采用任意方法收集數(shù)據(jù)
準備數(shù)據(jù):需要數(shù)值型數(shù)據(jù),標稱型數(shù)據(jù)應該映射成二值型數(shù)據(jù)
分析數(shù)據(jù):繪出數(shù)據(jù)的二維可視化顯示結(jié)果,以字典方式生成樹
訓練算法:模型樹的構(gòu)建
測試算法:使用測試數(shù)據(jù)上的R^2值來分析模型的效果
使用算法:使用訓練出的樹做預測,預測結(jié)果還可以用來做很多事情
收集數(shù)據(jù): 采用任意方法收集數(shù)據(jù)
準備數(shù)據(jù):需要數(shù)值型數(shù)據(jù),標稱型數(shù)據(jù)應該映射成二值型數(shù)據(jù)
數(shù)據(jù)存儲格式:
3.000000 46.852122
23.000000 178.676107
0.000000 86.154024
6.000000 68.707614
15.000000 139.737693
分析數(shù)據(jù):繪出數(shù)據(jù)的二維可視化顯示結(jié)果,以字典方式生成樹

訓練算法:模型樹的構(gòu)建
用樹回歸進行預測的代碼
# 回歸樹測試案例
# 為了和 modelTreeEval() 保持一致,保留兩個輸入?yún)?shù)
def regTreeEval(model, inDat):
"""
Desc:
對 回歸樹 進行預測
Args:
model -- 指定模型,可選值為 回歸樹模型 或者 模型樹模型,這里為回歸樹
inDat -- 輸入的測試數(shù)據(jù)
Returns:
float(model) -- 將輸入的模型數(shù)據(jù)轉(zhuǎn)換為 浮點數(shù) 返回
"""
return float(model)
# 模型樹測試案例
# 對輸入數(shù)據(jù)進行格式化處理,在原數(shù)據(jù)矩陣上增加第0列,元素的值都是1,
# 也就是增加偏移值,和我們之前的簡單線性回歸是一個套路,增加一個偏移量
def modelTreeEval(model, inDat):
"""
Desc:
對 模型樹 進行預測
Args:
model -- 輸入模型,可選值為 回歸樹模型 或者 模型樹模型,這里為模型樹模型
inDat -- 輸入的測試數(shù)據(jù)
Returns:
float(X * model) -- 將測試數(shù)據(jù)乘以 回歸系數(shù) 得到一個預測值 ,轉(zhuǎn)化為 浮點數(shù) 返回
"""
n = shape(inDat)[1]
X = mat(ones((1, n+1)))
X[:, 1: n+1] = inDat
# print X, model
return float(X * model)
# 計算預測的結(jié)果
# 在給定樹結(jié)構(gòu)的情況下,對于單個數(shù)據(jù)點,該函數(shù)會給出一個預測值。
# modelEval是對葉節(jié)點進行預測的函數(shù)引用,指定樹的類型,以便在葉節(jié)點上調(diào)用合適的模型。
# 此函數(shù)自頂向下遍歷整棵樹,直到命中葉節(jié)點為止,一旦到達葉節(jié)點,它就會在輸入數(shù)據(jù)上
# 調(diào)用modelEval()函數(shù),該函數(shù)的默認值為regTreeEval()
def treeForeCast(tree, inData, modelEval=regTreeEval):
"""
Desc:
對特定模型的樹進行預測,可以是 回歸樹 也可以是 模型樹
Args:
tree -- 已經(jīng)訓練好的樹的模型
inData -- 輸入的測試數(shù)據(jù)
modelEval -- 預測的樹的模型類型,可選值為 regTreeEval(回歸樹) 或 modelTreeEval(模型樹),默認為回歸樹
Returns:
返回預測值
"""
if not isTree(tree):
return modelEval(tree, inData)
if inData[tree['spInd']] <= tree['spVal']:
if isTree(tree['left']):
return treeForeCast(tree['left'], inData, modelEval)
else:
return modelEval(tree['left'], inData)
else:
if isTree(tree['right']):
return treeForeCast(tree['right'], inData, modelEval)
else:
return modelEval(tree['right'], inData)
# 預測結(jié)果
def createForeCast(tree, testData, modelEval=regTreeEval):
"""
Desc:
調(diào)用 treeForeCast ,對特定模型的樹進行預測,可以是 回歸樹 也可以是 模型樹
Args:
tree -- 已經(jīng)訓練好的樹的模型
inData -- 輸入的測試數(shù)據(jù)
modelEval -- 預測的樹的模型類型,可選值為 regTreeEval(回歸樹) 或 modelTreeEval(模型樹),默認為回歸樹
Returns:
返回預測值矩陣
"""
m = len(testData)
yHat = mat(zeros((m, 1)))
# print yHat
for i in range(m):
yHat[i, 0] = treeForeCast(tree, mat(testData[i]), modelEval)
# print "yHat==>", yHat[i, 0]
return yHat
完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/9.RegTrees/regTrees.py
測試算法:使用測試數(shù)據(jù)上的R^2值來分析模型的效果
R^2 判定系數(shù)就是擬合優(yōu)度判定系數(shù),它體現(xiàn)了回歸模型中自變量的變異在因變量的變異中所占的比例。如 R^2=0.99999 表示在因變量 y 的變異中有 99.999% 是由于變量 x 引起。當 R^2=1 時表示,所有觀測點都落在擬合的直線或曲線上;當 R^2=0 時,表示自變量與因變量不存在直線或曲線關系。
所以我們看出, R^2 的值越接近 1.0 越好。
使用算法:使用訓練出的樹做預測,預測結(jié)果還可以用來做很多事情
5、附加 Python 中 GUI 的使用
5.1、使用 Python 的 Tkinter 庫創(chuàng)建 GUI
如果能讓用戶不需要任何指令就可以按照他們自己的方式來分析數(shù)據(jù),就不需要對數(shù)據(jù)做出過多解釋。其中一個能同時支持數(shù)據(jù)呈現(xiàn)和用戶交互的方式就是構(gòu)建一個圖形用戶界面(GUI,Graphical User Interface),如圖9-7所示。

5.2、用 Tkinter 創(chuàng)建 GUI
Python 有很多 GUI 框架,其中一個易于使用的 Tkinter,是隨 Python 的標準版編譯版本發(fā)布的。Tkinter 可以在 Windows、Mac OS和大多數(shù)的 Linux 平臺上使用。
5.3、集成 Matplotlib 和 Tkinter
MatPlotlib 的構(gòu)建程序包含一個前端,也就是面向用戶的一些代碼,如 plot() 和 scatter() 方法等。事實上,它同時創(chuàng)建了一個后端,用于實現(xiàn)繪圖和不同應用之間接口。
通過改變后端可以將圖像繪制在PNG、PDF、SVG等格式的文件上。下面將設置后端為 TkAgg (Agg 是一個 C++ 的庫,可以從圖像創(chuàng)建光柵圖)。TkAgg可以在所選GUI框架上調(diào)用Agg,把 Agg 呈現(xiàn)在畫布上。我們可以在Tk的GUI上放置一個畫布,并用 .grid()來調(diào)整布局。
5.4、用treeExplore 的GUI構(gòu)建的模型樹示例圖

完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/9.RegTrees/treeExplore.py
6、樹回歸 小結(jié)
數(shù)據(jù)集中經(jīng)常包含一些復雜的相關關系,使得輸入數(shù)據(jù)和目標變量之間呈現(xiàn)非線性關系。對這些復雜的關系建模,一種可行的方式是使用樹來對預測值分段,包括分段常數(shù)或分段直線。一般采用樹結(jié)構(gòu)來對這種數(shù)據(jù)建模。相應地,若葉節(jié)點使用的模型是分段常數(shù)則稱為回歸樹,若葉節(jié)點使用的模型師線性回歸方程則稱為模型樹。
CART 算法可以用于構(gòu)建二元樹并處理離散型或連續(xù)型數(shù)據(jù)的切分。若使用不同的誤差準則,就可以通過CART 算法構(gòu)建模型樹和回歸樹。該算法構(gòu)建出的樹會傾向于對數(shù)據(jù)過擬合。一棵過擬合的樹常常十分復雜,剪枝技術的出現(xiàn)就是為了解決這個問題。兩種剪枝方法分別是預剪枝(在樹的構(gòu)建過程中就進行剪枝)和后剪枝(當樹構(gòu)建完畢再進行剪枝),預剪枝更有效但需要用戶定義一些參數(shù)。
Tkinter 是 Python 的一個 GUI 工具包。雖然并不是唯一的包,但它最常用。利用 Tkinter ,我們可以輕輕松松繪制各種部件并安排它們的位置。另外,可以為 Tkinter 構(gòu)造一個特殊的部件來顯示 Matplotlib 繪出的圖。所以,Matplotlib 和 Tkinter 的集成可以構(gòu)建出更強大的 GUI ,用戶可以以更自然的方式來探索機器學習算法的奧妙。
- 作者:片刻 小瑤
- GitHub地址: https://github.com/apachecn/MachineLearning
- 版權(quán)聲明:歡迎轉(zhuǎn)載學習 => 請標注信息來源于 ApacheCN