機器學習實戰之樹回歸


一,引言

  盡管線性回歸包含了一些強大的方法,但這些方法創建的模型需要擬合所有的樣本數據。當數據擁有眾多特征並且特征之間的關系比較復雜時,構建全局線性模型就會非常困難。並且,在實際生活中很多問題都是非線性的,很難通過全局線性模型來擬合所有數據。

  解決上述非線性數據的擬合問題的一個可行的方法是,將數據集切分成很多份容易建模的數據,然后再利用線性回歸方法來對切分后的數據子集分別建模,如果切分后仍難以擬合線性模型就繼續切分。這樣,就可以比較好的擬合全局數據。

二,CART算法

  CART算法,即分類回歸樹算法,該算法既可以用於分類,也可以用於回歸。該算法數據的建模方法為二元切分法。

1 復雜數據的局部性建模--二元切分法

  ID3決策樹算法通過不斷將數據切分成小數據集,知道所有目標變量完全相同,或者數據不能再切分為止;決策樹是一種貪心算法,要在給定時間內找到最佳選擇,並並不關心全局最優問題。

  ID3決策樹算法存在以下兩個問題:

  (1)ID3算法每次選取最佳特征來分割數據,並按照該特征所有的取值來切分,即有幾種可能的取值,數據就會被切分成幾份。而一旦按該特征切分后,該特征在之后的算法執行過程中便不再起作用了,所以存在一些觀點認為該算法過於迅速。

  (2)ID3算法並不能處理連續型特征,只能事先將連續性特征轉化為離散型,才能使用。顯然,在轉換過程中會破壞連續型變量的內在性質。

  相比於ID3算法,顯然CART算法更有優勢,因為我們知道CART算法不僅可以用於分類,還可以用於回歸,這里的回歸即是處理連續型特征的體現。CART算法之所以能夠處理連續型特征,就在於采用了二元切分法,而使用二元切分法易於對樹構建過程進行調整以處理連續型特征。二元切分法的具體過程為:每次將數據集划分為兩份,如果數據的某特征值大於給定值就進入左子樹,否則就進入右子樹。

2 CART算法樹的構建

  CART算法既可以用於分類也可以用於回歸,回歸樹與分類樹思路類似,但葉節點的數據不是類型不是離散型,而是連續型。這里,我們將通過CART算法構建兩種樹,一種是回歸樹,其每個葉節點包含單個值;另外一種是模型樹,其每個葉節點包含一個線性方程。

  首先,這里采用跟ID3算法中使用相同的字典結果來存儲構建樹的數據結構。在這里,CART算法構建樹包含四個主要元素,待切分特征,待切分的特征值,左子樹(當數據不能再切分時,也可以是葉節點),右子樹。

  事實上,我們也可以像C++一樣,采用面向對象的方式來建立樹的數據結構,比如,建立如下樹節點結構:

class treeNode():
    def __init__(self,feat,val,right,left):
        featureToSplitOn=feat
        valueOfSplit=val
        rightBranch=right
        leftBranch=left

  這里,直接采用字典的數據結構,顯然我們無須定義一個類,從而有效的減少了代碼量。

  CART算法構建樹函數createTree()偽代碼如下:

找到最佳的切分特征:
    如果該節點不能再分,將該節點存為葉節點
    執行二元切分
    在左子樹遞歸調用createTree()方法
    在右子樹遞歸調用createTree()方法

  具體代碼如下:

#解析文本數據
def loadDatabase(filename):
    dataMat=[]
    fr=open(filename)
    for line in fr.readlines():
        curLine=line.strip().split('\t')
        #將每行數據映射為浮點數
        fltLine=map(float,curLine)
        dataMat.append(fltLine)
    return dataMat

#拆分數據集函數,二元拆分法    
#@dataSet:待拆分的數據集
#@feature:作為拆分點的特征索引
#@value:特征的某一取值作為分割值
def binSplitDataSet(dataSet,feature,value):
    #采用條件過濾的方法獲取數據集每個樣本目標特征的取值大於
    #value的樣本存入mat0
    #左子集列表的第一行
    #mat0=dataSet[nonzero(dataSet[:,feature]>value)[0],:][0]
    #左子集列表
    mat0=dataSet[nonzero(dataSet[:,feature]>value)[0],:]
    #同上,樣本目標特征取值不大於value的樣本存入mat1
    mat1=dataSet[nonzero(dataSet[:,feature]<=value)[0],:]
    #返回獲得的兩個列表
    return mat0,mat1

#創建樹函數
#@dataSet:數據集
#@leafType:生成葉節點的類型 1 回歸樹:葉節點為常數值 2 模型樹:葉節點為線性模型
#@errType:計算誤差的類型 1 回歸錯誤類型:總方差=均方差*樣本數
#                         2 模型錯誤類型:預測誤差(y-yHat)平方的累加和
#@ops:用戶指定的參數,包含tolS:容忍誤差的降低程度 tolN:切分的最少樣本數
def createTree(dataSet,leafType=regLeaf,errType=regErr,ops=(1,4)):
    #選取最佳分割特征和特征值
    feat,val=chooseBestSplit(dataSet,leafType,errType,ops)
    #如果特征為none,直接返回葉節點值
    if feat == None:return val
    #樹的類型是字典類型
    retTree={}
    #樹字典的一個元素是切分的最佳特征
    retTree['spInd']=feat
    #第二個元素是最佳特征對應的最佳切分特征值
    retTree['spval']=val
    #根據特征索引及特征值對數據集進行二元拆分,並返回拆分的兩個數據子集
    lSet,rSet=binSplitDataSet(dataSet,feat,val)
    #第三個元素是樹的左分支,通過lSet子集遞歸生成左子樹
    retTree['left']=createTree(lSet,leafType,errType,ops)
    #第四個元素是樹的右分支,通過rSet子集遞歸生成右子樹
    retTree['right']=createTree(rSet,leafType,errType,ops)
    #返回生成的數字典
    return retTree

  上面的第一個函數是我們熟知的文本文件的數據解析函數,該函數讀取一個以tab鍵位分隔符的文件,然后通過map(float,curLine)方法將每行內容保存為一組浮點數;

  第二個函數是數據切分函數,通過數組過濾的方法將數據集切分得到兩個子集並返回;

  最后一個函數為樹構建函數,是一個遞歸函數,只要數據集滿足切分條件,就會將數據集采取遞歸的形式繼續切分下去。它有四個參數:數據集和其他3個可選參數。三個可選函數決定了樹的類型:leafType給出建立葉節點的函樹;errType()代表誤差計算函數;而ops是一個包含樹構建所需參數的元組。具體地,函數首先將數據集分為兩個部分,通過chooseBestSplit()函數找出切分的最佳特征和特征值,如果滿足停止條件,則直接返回某類模型的值,當構建的是回歸樹時,葉節點值為一個常數,而構建模型樹時,葉節點模型則是一個線性方程。

 3 CART算法用於回歸

  前面我們已經有了樹的創建算法,也知道了樹構建算法中除了數據集,還需要另外三個可選參數,即葉節點生成類型,計算誤差方法,以及切分終止需要的參數元組。下面,分別一一解釋:

  (1)leafType:葉節點的生成類型有兩種,一種是對應於回歸樹的類型,即葉節點包含的是一個常數值;另一種對應模型樹類型,即葉節點包含一個線性方程

  (2)errType:誤差計算類型,該參數應用於最佳切分特征及特征值得選取中,通過選擇誤差最小的切分特征及特征值,來對數據集執行二元切分。這里,回歸樹和模型樹在計算誤差的方式上有所不同。回歸樹計算誤差的方法是,首先計算數據集目標變量值的均方差,再乘以數據集的樣本數;而模型樹的誤差計算方法為:首先計算數據集各個樣本目標變量的真實值與預測值得差值的平方,然后再進行累加,即類似於前面的平方損失函數

  (3)終止條件參數元組ops:兩個參數,tolS,即容忍誤差的下降值;tolN,最少的切分樣本數。這兩個參數決定了樹構建的終止條件,相當於在樹構建的過程中,邊構建邊剪枝,經常應用於預剪枝

  顯然,這里我們要構建的是回歸樹,所以需要選擇合適的葉節點生成類型,誤差計算方法。此外,還需要給出最佳切分特征及最佳切分特征值的函數,這樣,我們才能完整的構建回歸樹

  選取最佳特征及特征值函數的偽代碼如下:

對每個特征:
    對每個特征值:
        將數據集切分為兩份
        計算切分誤差
        如果當前誤差小於當前最小誤差,那么將當前切分設定為最佳切分並更新最小誤差
返回最佳切分的特征和閾值

  具體代碼為:

#回歸樹的切分函數

#葉節點生成函數
def regLeaf(dataSet):
    #數據集列表最后一列特征值的均值作為葉節點返回
    return mean(dataSet[:,-1])

#誤差計算函數    
def regErr(dataSet):
    #計算數據集最后一列特征值的均方差*數據集樣本數,得到總方差返回
    return var(dataSet[:,-1])*shape(dataSet)[0]

#選擇最佳切分特征和最佳特征取值函數
#@dataSet:數據集
#@leafType:生成葉節點的類型,默認為回歸樹類型
#@errType:計算誤差的類型,默認為總方差類型
#@ops:用戶指定的參數,默認tolS=1.0,tolN=4
def chooseBestSplit(dataSet,leafType=regLeaf,errType=regErr,ops=(1,4)):
    #容忍誤差下降值1,最少切分樣本數4
    tolS=ops[0];tolN=ops[1]
    #數據集最后一列所有的值都相同
    if len(set(dataSet[:,-1].T.tolist()[0])==1):
        #最優特征返回none,將該數據集最后一列計算均值作為葉節點值返回
        return none,leafType(dataSet))
    #數據集的行與列
    m,n=shape(dataSet)
    #計算未切分前數據集的誤差
    S=errType(dataSet)
    #初始化最小誤差;最佳切分特征索引;最佳切分特征值
    bestS=inf;bestIndex=0;bestValue=0
    #遍歷數據集所有的特征,除最后一列目標變量值
    for featIndex in range(n-1):
        #遍歷該特征的每一個可能取值
        for splitVal in set(dataSet[:,featIndex]):
            #以該特征,特征值作為參數對數據集進行切分為左右子集
            mat0,mat1=binSplitDataSet(dataSet,featIndex,splitVal)
            #如何左分支子集樣本數小於tolN或者右分支子集樣本數小於tolN,跳出本次循環
            if (shape(mat0)[0]<tolN) or (shape(mat1)[0]<tolN):continue
            #計算切分后的誤差,即均方差和
            newS=errType(mat0)+errType(mat1)
            #保留最小誤差及對應的特征及特征值
            if newS<bestS:
                bestIndex=featIndex
                bestValue=splitVal
                bestS=newS
    #如果切分后比切分前誤差下降值未達到tolS
    if (S-bestS)<tolS:
        #不需切分,直接返回目標變量均值作為葉節點
        return     None,leafType(dataSet)
    #檢查最佳特征及特征值是否滿足不切分條件
    mat0,mat1=binSplitDataSet(dataSet,bestIndex,bestValue)
    if(shape(mat0)[0]<tolN) or (shape(mat1)[0]<tolN):
        return None,leafType(dataSet)
    #返回最佳切分特征及最佳切分特征取值
    return bestIndex,bestValue

  從上述代碼中,我們不難看出,在選取最佳切分特征和特征值過程中,有三種情況不會對數據集進行切分,而是直接創建葉節點。

(1)如果數據集切分之前,該數據集樣本所有的目標變量值相同,那么不需要切分數據集,而直接將目標變量值作為葉節點返回

(2)當切分數據集后,誤差的減小程度不夠大(小於tolS),就不需要切分,而是直接求取數據集目標變量的均值作為葉節點值返回

(3)當數據集切分后如果某個子集的樣本個數小於tolN,也不需要切分,而直接生成葉節點

有了回歸樹的構建代碼,下面就來利用實際的數據集來測試一下回歸樹的效果,先看一下簡單的數據集構建的回歸樹:

  再看一下稍微復雜一點的數據集構建的回歸樹:

 

 

4 樹剪枝

  上面我們利用回歸樹構建算法構建了回歸樹,雖然看起來效果不錯,但是我們還要需要某種有效的措施來檢查構建過程是否得當。這就是樹剪枝技術,它通過對決策樹剪枝的方式來達到更好的預測效果

  一棵樹如果節點過多,表明該模型可能對數據進行了過擬合,一旦發生了過擬合,就表明該模型對訓練數據擬合效果非常好,而對其他的測試數據擬合效果很差的情況。所以,為避免過擬合,我們需要通過剪枝的方式來降低模型的復雜度。

  剪枝包括預剪枝和后剪枝兩種方法,而我們在之前的樹構建過程中,實際上就采用了預剪枝的方法,即通過設置合理的切分終止條件tolS,tolN,在樹構建的過程中進行剪枝過程,從而防止過擬合。而后剪枝過程,需要訓練集合測試集兩個數據集,首先利用訓練集來產生復雜度較大的回歸樹模型,然后利用測試集則來對決策樹進行剪枝,從而降低決策樹復雜度。

(1)預剪枝

  預剪枝的方法,即通過設置合理的切分終止條件tolS,tolN,在樹構建的過程中進行剪枝過程,從而防止過擬合。在預剪枝過程中,我們需要不斷的修改停止條件tolS,tolN來得到較好的結果,這顯然不能算作是一種好的辦法,因為,尋找到合適的停止條件意味着需要更多的時間損耗。而后剪枝則不需要用戶指定參數,是一種比較理想的剪枝方法

(2)后剪枝

  后剪枝的方法意味着我們需要兩個不同的數據集,一個作為訓練集,另外一個作為測試集。訓練集構建出來的樹需要足夠大,足夠復雜,這樣才能便於后面的剪枝。接下來,從上而下找到葉節點,用測試集判斷將這些葉節點合並是否能降低測試誤差。如果是那就將這些葉節點合並。

  函數后剪枝的偽代碼如下:

基於已有的樹切分測試數據:
    如果存在任一子集是一棵樹,則在該子集上遞歸剪枝過程
    計算將當前兩個葉節點合並后的誤差
    計算不合並的誤差
    如果合並會降低誤差,就將兩個葉節點合並

  再來看實際的代碼:

#后剪枝
#根據目標數據的存儲類型是否為字典型,是返回true,否則返回false
def isTree(obj):
    return (type(obj).__name__=='dict')

#獲取均值函數    
def getMean(tree):
    #樹字典的右分支為字典類型:遞歸獲得右子樹的均值
    if isTree(tree['right']):tree['right']=getMean(tree['right'])
    #樹字典的左分支為字典類型:遞歸獲得左子樹的均值
    if isTree(tree['left']):tree['left']=getMean(tree['left'])
    #遞歸直至找到兩個葉節點,求二者的均值返回
    return (tree['left']+tree['right'])/2.0

#剪枝函數
#@tree:樹字典    
#@testData:用於剪枝的測試集
def prune(tree,testData):
    #測試集為空,直接對樹相鄰葉子結點進行求均值操作
    if shape(testData)[0]==0:return getMean(tree)
    #左右分支中有非葉子結點類型
    if (isTree(tree['right']) or isTree(tree['left'])):
        #利用當前樹的最佳切分點和特征值對測試集進行樹構建過程
        lSet,rSet=binSplitDataSet(testData,tree['spInd'],tree['spval'])
    #左分支非葉子結點,遞歸利用測試數據的左子集對做分支剪枝
    if isTree(tree['left']):tree['left']=prune(tree['left'],lSet)
    #同理,右分支非葉子結點,遞歸利用測試數據的右子集對做分支剪枝
    if isTree(tree['right']):tree['right']=prune(tree['right'],lSet)
    #左右分支都是葉節點
    if not isTree(tree['left']) and ont isTree(tree['right']):
        #利用該子樹對應的切分點對測試數據進行切分(樹構建)
        lSet,rSet=binSplitDataSet(testData,tree['spInd'],tree['spval'])
        #如果這兩個葉節點不合並,計算誤差,即(實際值-預測值)的平方和
        errorNoMerge=sum(power(lSet[:,-1]-tree['left'],2))+\
                     sum(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

 

  上面代碼的第一個函數,用於判斷當前樹是否為葉節點。因為,樹的存儲結構是字典類型,所以對該樹的類型進行檢測,如果是字典類型那么就是非葉節點的樹,返回true,否則是葉節點返回false

  第二個函數是求兩個相鄰葉節點的均值函數,函數采用遞歸的方法,從根節點開始從上到下,找到葉節點,如果某棵樹的左分支和右分支都是葉節點,那么就將這兩個葉節點計算均值返回

  第三個函數就是具體的后剪枝過程,還是采用遞歸的方式,從上到下,找到葉節點,當找到兩個相鄰的葉節點,那么將其合並並計算誤差,如果合並后的誤差小於不合並的誤差,那么就將這兩個葉節點合並成一個,返回節點值為兩個葉節點值均值。否則,就不合並,直接返回樹

  我們能看到,很多的葉節點發生了合並,但沒有想預期的那樣剪枝成兩部分,所以預剪枝在構建樹的效果上要比后剪枝好。而后剪枝又比預剪枝更容易實現。所以在實際構建樹過程中可以采用二者結合的方法尋求最佳模型

5 模型樹構建

  上面的構建的回歸樹,是將葉節點設定為常數值來進行建模。還有一種方法是把葉節點設為分段線性函數,這里的分段線性是指模型由多個線性片段組成。因為,數據集不是線性的,那么我們很難通過全局線性函數來擬合數據,所以采用將數據集分段,分段后的數據都滿足線性要求,這樣就分別構建出相應的線性方程,再將分段線性模型組合起來就是全局的模型。比如,下圖分段線性數據,顯然使用兩條直線組合來擬合會比一條直線擬合效果要好。

  模型樹的構建,與回歸樹的構建,除了葉節點的類型不同外。選取最佳切分特征及特征值中計算誤差的方法也存在差別,對於給定的數據集,先用線性模型來對其進行擬合,然后計算真實目標值和模型預測值得差值。最后將這些差值的平方求和就得到了所需的誤差。

  模型樹葉節點生成函數

#模型樹葉節點生成函數
def linearSolve(dataSet):
    #獲取數據行與列數
    m,n=shape(dataSet)
    #構建大小為(m,n)和(m,1)的矩陣
    X=mat(ones((m,n)));Y=mat(ones((m,1)))
    #數據集矩陣的第一列初始化為1,偏置項;每個樣本目標變量值存入Y
    X[:,1:n]=dataSet[:,0:n-1];Y=dataSet[:,-1]
    #對數據集矩陣求內積
    xTx=X.T*X
    #計算行列式值是否為0,即判斷是否可逆
    if linalg.det(xTx)==0.0:
        #不可逆,打印信息
        print('This matrix is singular,cannot do inverse,\n\
                try increasing the second value if ops')
    #可逆,計算回歸系數
    ws=(xTx).I*(X.T*Y)
    #返回回顧系數;數據集矩陣;目標變量值矩陣
    return ws,X,Y

#模型樹的葉節點模型    
def modelLeaf(dataSet):
    #調用線性回歸函數生成葉節點模型
    ws,X,Y=linearSolve(dataSet)
    #返回該葉節點線性方程的回顧系數
    return ws

#模型樹的誤差計算函數
def modelErr(dataSet):
    #構建模型樹葉節點的線性方程,返回參數
    ws,X,Y=linearSolve(dataSet)
    #利用線性方程對數據集進行預測
    yHat=X*ws
    #返回誤差的平方和,平方損失
    return sum(power(y-yHat,2))

  上面的葉節點生成函數,就是之前用到的簡單線性回歸方法;模型樹的葉節點返回的是線性方程的回歸系數;

6 樹回歸和標准回歸的比較

  模型樹,回歸樹和前面的線性回歸方法構建的模型,具體哪一種更好呢?這就需要我們采用合適的度量標准,來客觀比較各個模型構建方法的優劣。顯然,前面用到的相關系數計算方法是一個不錯的選擇,模型擬合的結果和實際的結果相關系數越接近1.0,顯然擬合效果就越好

  下面看一下,用樹回歸進行預測的代碼:

#用樹回歸進行預測代碼

#回歸樹的葉節點為float型常量
def regTreeEval(model,inDat):
    return float(model)

#模型樹的葉節點浮點型參數的線性方程
def modelTreeEval(model,inDat):
    #獲取輸入數據的列數
    n=shape(inDat)[1]
    #構建n+1維的單列矩陣
    X=mat(ones((1,n+1)))
    #第一列設置為1,線性方程偏置項b
    X[:,1:n+1]=inDat
    #返回浮點型的回歸系數向量
    return float(X*model)

#樹預測    
#@tree;樹回歸模型
#@inData:輸入數據
#@modelEval:葉節點生成類型,需指定,默認回歸樹類型
def treeForeCast(tree,inData,modelEval=regTreeEval):
    #如果當前樹為葉節點,生成葉節點
    if not isTree(tree):return modelEval(tree,inData)
    #非葉節點,對該子樹對應的切分點對輸入數據進行切分
    if inData[tree['spInd']]>tree['spval']:
        #該樹的左分支為非葉節點類型
        if isTree(tree['left']):
            #遞歸調用treeForeCast函數繼續樹預測過程,直至找到葉節點
            return treeForeCast(tree['left'],inData,modelEval)
        #左分支為葉節點,生成葉節點
        else: return modelEval(tree['left'],inData)
    #小於切分點值的右分支
    else:
        #非葉節點類型
        if isTree(tree['right']):
            #繼續遞歸treeForeCast函數尋找葉節點
            return treeForeCast(tree['right'],inData,modelEval)
        #葉節點,生成葉節點類型
        else: return modelEval(tree['right'],inData)

#創建預測樹        
def createForeCast(tree,testData,modelEval=regTreeEval):
    #測試集樣本數
    m=len(testData)
    #初始化行向量各維度值為1
    yHat=mat(zeros((m,1)))
    #遍歷每個樣本
    for i in range(m):
        #利用樹預測函數對測試集進行樹構建過程,並計算模型預測值
        yHat[i,0]=treeForeCast(tree,mat(testData[i]),modelEval)
    #返回預測值
    return yHat

  上面代碼中回歸樹和模型樹計算模型預測值的方法有所不同,對於樹的一個葉節點,回歸樹得到的預測值是一個float型的常數值,而模型樹的葉節點返回的是線性方程的擬合輸出值。

  接下來利用實際的數據分別利用線性回歸,回歸樹,模型樹來擬合數據模型,然后分別計算模型預測目標值和目標真實值得相關系數;然后進行比較系數的大小

  回歸樹:

 

  模型樹:

  顯然,從上面的相關系數可以看出,模型樹的預測效果要比回歸樹的預測效果更好。實施上,我們利用線性回歸方法對數據進行擬合的模型的相關系數為:

  那么我們可以得到以下結論,當數據集比較復雜時,樹回歸方法要比簡單線性回歸模型更加有效,並且在樹回歸中,模型樹要比回歸樹的效果更好。

三,使用tikinfer創建GUI

    python中有很多GUI框架,而tkinder是其中易於使用的一個。tkinder的GUI有一些小部件(Widget)組成。包括文本框(Test Box),文本輸入框(Entry)按鈕(Button),標簽(Label)沒和復選按鈕(Check Button)等對象。此外,當對象調用grid()方法時,就等於把該對象告訴布局管理器,grid()方法將小部件安排在一個二維的表格中,用戶可以設定每個小部件所在的行列位置。

  下面就先來看一下用於構建樹管理器界面的tkinder小部件

from numpy import *
#3.0之前Tkinder,3.0之后(包括3.0)tkinder
from tkinter import *
import regTrees

def reDraw(tolS,tolN):
    pass
    
def drawNewTree():
    pass

#標簽部件    
Label(root,test="Plot Place Holder").grid(row=0,columnspan=3)
Label(root,text="tolN").grid(row=1,column=0)
#文本輸入框部件
tolNentry=Entry(root)
tolNentry.grid(row=1,column=1)
tolNentry.insert(0,'10')
Label(root,text="tols").grid(row=2,column=0)
tolSentry=Entry(root)
tolSentry.grid(row=2,column=1)
tolSentry.insert(0,'1.0')
#按鈕部件
Button(root,text="ReDraw",command=drawNewTree).grid(row=1,\                                    
                        column=2,rowspan=3)
#復選按鈕部件
chkBtnVal=IntVal()
chkBtn=checkbutton(root,text="Model Tree",variable=chkBtnVal)
chkBtn.grid(row=3,column=0,columnspan=2)
reDraw.rawDat=mat(regTrees.loadDataSet('sine.txt'))
reDraw.testDat=arrange(min(reDraw.rawDat[:,0]),\
                       max(reDraw.rawDat[:,0],0.01))
reDraw(1.0,10)
root.mainloop()

 

  接下來,就結合matplotlib和tkinder來將圖像直接放在GUI上,即通過修改Matplotlib后端,達到在tkinder的GUI上繪圖的目的。

  先用畫布來替換繪制占位符,刪除對應標簽並添加以下代碼:

reDraw.f=Figure(figsize=(5,4),dpi=100)
reDraw.canvas=FigureCanvasTkAgg(reDraw.f,master=root) reDraw.canvas.show() reDraw.canvas.get_tk_widget().grid(row=0,columnspan=3)

  再將matplotlib和tkinder代碼集成

#導入matplotlib工具
import matplotlib
#將matplotlib后端設置為TkAgg
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
#以用戶輸入的終止條件為參數繪圖
def reDraw(tolS,tolN):
    reDraw.f.clf()
    reDraw.a=reDraw.f.add_subplot(lll)
    if chkBtnVal.get():
        myTree=regTrees.createTree(reDraw.rawDat,regTrees.modelLeaf,\
            regTrees.modelEval,(tolS,tolN))
        yHat=regTrees.createForeCast(reDraw.rawDat,ops=(tolS,tolN))
    else:
        myTree=regTrees.createTree(reDraw.rawDat,ops=(tolS,tolN))
        yHat=regTrees.createForeCast(myTree,ops=(tolS,tolN))
    #繪制真實值
    reDraw.a.scatter(reDraw,rawDat[:,0],reDraw.rawDat[:,1],s=5)
    #繪制預測值
    reDraw.a.plot(reDraw.testDat,yHat,linewidth=2.0)
    reDraw.canvas.show()
#從文本輸入框中獲取樹創建終止條件,沒有則用默認值    
def getInputs():
    try:tolN=int(tolNentry.get())
    except:
        tolN=10
        print('enter Integer for tolN')
        tolNentry.delete(0,END)
        tolNentry.insert(0,'10')
    try:tolS=int(tolSentry.get())
    except:
        tolS=1.0
        print('enter Integer for tolS')
        tolSentry.delete(0,END)
        tolSentry.insert(0,'1.0')
    return tolS,tolN

def drawNewTree():
    tolN,tolS=getInputs()
    reDraw(tolS,tolN)

 

  上面代碼中,真實值采用scatter()方法繪制,預測值采用plot()方法繪制,因為scatter()方法構建的是離散型散點圖,而plot()方法則構建連續曲線。下面開看一下實際的效果:

四,小結

  在實際生活中,數據集經常會包含一些復雜的相互關系,輸入數據與目標變量之間呈現出非線性關系。對這些復雜數據建模,我們可以采用樹回歸的方法來對數據進行分段線性預測,分段線性函數包括分段常數(回歸樹模型)和分段直線方程(模型樹);二者的區別在於構建樹的葉節點模型上,若葉節點采用分段常數則是回歸樹,若使用的模型是線性回歸方程則稱為模型樹。

  上面用到的樹構建方法,是基於CART算法的二元切分法,通過計算最小誤差的方法得到切分該數據集的最佳特征以及最佳的特征取值,從而來對數據集進行二元划分。

  很顯然,CART算法是一種貪心算法,更多的是關心最佳切分特征及特征值的選取上,而不關注全局模型的最優解。所以,大多時候,該算法構建的樹會產生過擬合情況,即對訓練數據擬合很好,而對測試數據擬合效果較差

  預剪枝和后剪枝是兩種對決策樹剪枝從而降低模型復雜度的方法,前者在樹構建的過程中邊構建邊剪枝,后者則是先利用訓練集構建出樹,在對構建的樹利用測試集進行剪枝處理;從效果來看,預剪枝要比后剪枝要好;但是,預剪枝需要用戶提供合理的輸入,對用戶輸入的參數較為敏感,從這方面來看,后剪枝因為不需要用戶的參與而更加理想。在實際的構建樹過程中,往往采用兩種方法結合的方法對於尋求最佳模型效果較好

  tkinder是python的一個GUI工具包。利用tkinder我們可以輕松的繪制各種部件並靈活的安排他們的位置。此外,可以通過設置matplotlib后端為TkAgg的方法,來集成matplotlib和tkinder,從而在tkinder中顯示matplotlib繪出的圖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM