《機器學習實戰》筆記之九——樹迴歸

來源:互聯網
上載者:User

第九章 樹迴歸

CART演算法 迴歸與模型樹 樹減枝演算法 python中GUI的使用

線性迴歸需要擬合所有的樣本點(局部加權線性迴歸除外),當資料擁有眾多特徵並且特徵之間關係十分複雜時,就不可能使用全域線性模型來擬合任何資料。

將資料集切分成很多份易建模的資料,再用線性迴歸技術來建模可破。

本章介紹CART(Classification And Regression Trees, 分類迴歸樹)的樹構建演算法,可用於分類還可用於迴歸。


9.1 複雜資料的局部性建模

chap3的決策樹主要是不斷將資料切分成小資料集,直到所有目標變數完全相同,或者資料不能再切分為止。決策樹是一種貪心演算法,並不考慮能否達到全域最優。其構建演算法ID3演算法每次選取當前最佳的特徵來分割資料,並按照該特徵的所有可能取值來劃分,之後該特徵不會再起作用。另外一種方法是二元切分法,每次把資料集切成兩份,如果資料的某特徵等於切分所要求的值,那麼這些資料就進入樹的左子樹,反之右子樹。二元切分法可處理連續型特徵,節省樹的構建時間。

CART使用二元切分來處理連續型變數,應用廣泛。


9.2 連續型和離散型特徵的樹的構建

與chap3類似,用字典儲存樹的結構。包括4元素:

待切分的特徵 待切分的特徵值 左子樹。當不再需要切分的時候,可是個單個值。 右子樹。類似左子樹。

CART演算法可固定樹的資料結構,樹包含左鍵和右鍵,可以儲存 另一顆子樹或者單個值。

虛擬碼:

找到最佳的待切分特徵:

如果該節點不能再分,將該節點存為分葉節點

執行二元切分

在右子樹調用createTree()方法

在左子樹調用createTree()方法

coding:

#!/usr/bin/env python# coding=utf-8from numpy import *def loadDataSet(fileName):    dataMat = []    fr      = open(fileName)    for line in fr.readlines():        curLine = line.strip().split("\n")        fltLine = map(float, curLine)       #將每行的每個元素映射為浮點數        dataMat.append(fltLine)    return dataMatdef binSplitDataSet(dataSet, feature,value):#資料集合,待切分的特徵,特徵值,將資料集合切分得到兩個子集    mat0 = dataSet[nonzero(dataSet[:,feature] > value)[0], :][0]    mat1 = dataSet[nonzero(dataSet[:,feature] <= value)[0], :][0]    return mat0, mat1def createTree(dataSet, leafType = regLeaf, errType = regErr, ops=(1,4)):    feat, val = chooseBestSplit(dataSet, leafType, errType, ops)        #將資料集進行切分    if feat == None:        return val    else:        retTree = {}        retTree["spInd"] = feat        retTree["spVal"] = val        lSet, rSet       = binSplitDataSet(dataSet, feat, val)        retTree["left"]  = createTree(lSet, leafType, errType, ops)     #遞迴切分        retTree["right"] = createTree(rSet, leafType, errType, ops)        return retTreetestMat    = mat(eye(4))mat0, mat1 = binSplitDataSet(testMat, 1, 0.5)       #print testMatprint mat0print mat1

9.3 將CART演算法用於迴歸

迴歸樹假設分葉節點是常數值。這種策略認為資料中的複雜關係可以用樹結構來概括。

為成功構建以分段常數為分葉節點的樹,需要度量出資料的一致性。首先計算所有資料的均值,然後計算每條 資料的值到均值的差值,一般使用絕對值或平方差 來代替差值 ,類似方差計算,方差為平方誤差的均值(均方差),這裡需要計算平方誤差的總值(總方差),均方差(var函數)乘以資料集樣本點的個數可破。

構建樹

chooseBestSplit()函數目標是找到資料集切分的最佳位置,遍曆所有的特徵及其可能的取值來找到使誤差最小化的切分閾值。

虛擬碼:

對每個特徵:

對每個特徵值:

將資料集切分成兩份

計算切分的誤差

如果當前誤差小於當前最小誤差,那麼將當前切分設定為最佳切分並更新最小誤差 返回最佳切分的特徵和閾值

資料:


Figure 9-1: 實驗資料部分樣本資料

coding:

#==============迴歸樹的切分函數=============================def regLeaf(dataSet):    return mean(dataSet[:,-1])                      #返回分葉節點,迴歸樹中的目標變數的均值def regErr(dataSet):    return var(dataSet[:,-1])*shape(dataSet)[0]     #誤差估計,計算目標變數的平方誤差,需要返回總誤差def chooseBestSplit(dataSet, leafType = regLeaf, errType = regErr, ops=(1,4)):    tolS = ops[0]                                   #容許的誤差下降值    tolN = ops[1]                                   #切分的最少樣本數    if len(set(dataSet[:,-1].T.tolist()[0]))==1:    #如果目標值相等,退出        return None, leafType(dataSet)    else:        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)   #按照某個特徵的某個值將資料切分成兩個資料子集                if (shape(mat0)[0]<tolN) or (shape(mat1)[0]<tolN):           #如果某個子集行數不大於tolN,也不應該切分                    continue                newS = errType(mat0) + errType(mat1)            #新誤差由切分後的兩個資料子集組成的誤差                if newS < bestS:                                #判斷新切分能否降低誤差,                    bestIndex = featIndex                    bestValue = splitVal                    bestS     = newS        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        #=====================================================================myDat = loadDataSet("ex00.txt")myMat = mat(myDat)print createTree(myMat)myDat1 = loadDataSet("ex0.txt")myMat1 = mat(myDat1)print createTree(myMat1)

切分效果:

Figure 9-2: 切分效果 9.4 樹剪枝

一棵樹如果節點過多,表明該模型可能對資料進行了”過擬合“。可使用測試集上交叉驗證來發現過擬合。

剪枝(pruning):降低決策樹的複雜度來避免過擬合。分為預剪枝(prepruning)和後剪枝(postpruning),後者需要使用訓練集和測試集。

預剪枝

樹構建演算法對輸入參數tolS和tolN非常敏感,通過不斷地修改停止條件來得到合理結果並不是很好的辦法。

後剪枝

後剪枝需要使用測試集。首先指定參數,使得構建出的樹足夠大,足夠複雜,便於剪枝。從上而下找到分葉節點,用測試集來判斷這些分葉節點合并是否能降低測試誤差。是則合并。

虛擬碼:

基於已有的樹切分測試資料:

如果存在任一子集是一棵樹,則在該子集遞迴剪枝過程

計算將當前兩個分葉節點合并後的誤差

計算不合并的誤差

如果合并會降低誤差的化,就將分葉節點合并

coding:

#=============迴歸樹剪枝函數==========================================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.0def prune(tree, testData):    if shape(testData)[0] == 0: return getMean(tree) #if we have no test data collapse the tree    if (isTree(tree['right']) or isTree(tree['left'])):#if the branches are not trees try to prune them        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'], rSet)    #if they are now both leafs, see if we can merge them    if not isTree(tree['left']) and not isTree(tree['right']):        lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])        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 treemyTree = createTree(myMat2, ops=(0,1))myDatTest = loadDataSet("ex2test.txt")myMat2Test = mat(myDatTest)print prune(myTree, myMat2Test)

9.5 模型樹

用樹來對資料建模,除了把分葉節點設定為常數值外,還可以將其設定為分段線性函數,分段線性(piecewise linear)即模型由多個線性片段組成。 Figure 9-3: 用來測試模型樹構建函數的分段線性資料

可以設計兩條分別從0.0-0.3、從0.3~1.0的直線,得到兩個線性模型,即分段線性模型。

兩條直線比很多節點群組成一顆大樹更容易理解。模型樹的可解釋性是它優於迴歸樹的特點之一。模型樹也具有更高的預測準確度。利用樹產生演算法對資料進行切分,且每份切分資料都能很容易被線性模型所表示,關鍵在於找到最佳切分。

coding:

#==========模型樹的分葉節點產生函數=========def linearSolve(dataSet):           #執行簡單的線性迴歸    m,n       = shape(dataSet)    X         = mat(ones((m,n)))    Y         = mat(ones((m,1)))    X[:, 1:n] = dataSet[:, 0:n-1]    Y         = dataSet[:, -1]      #將X,Y中的資料格式化    xTx       = X.T*X    if linalg.det(xTx) == 0.0:        raise NameError("This matrix is singular, cannot do inverse")        ws = linalg.pinv(xTx)*(X.T*Y)    ws = xTx.I*(X.T*Y)    return ws, X, Ydef modelLeaf(dataSet):             #當資料不再需要切分的時候它負責產生分葉節點模型    ws, X, Y = linearSolve(dataSet)    return wsdef modelErr(dataSet):                  ws, X, Y = linearSolve(dataSet)    yHat     = X*ws    return sum(power(Y - yHat,2))       #計算平方誤差myMat2 = mat(loadDataSet("exp2.txt"))#print myMat2,type(myMat2)print createTree(myMat2, modelLeaf, modelErr, (1,10))

效果:

Figure 9-4: 切分結果

兩個線性模型分別為y=3.468+1.185x和y=0.00168+11.964x,實際資料是由模型y=3.5+1.0x和y=0+12x再加上高斯噪音產生,可以看出效果還是不錯。 9.6 樣本:樹迴歸與標準迴歸的比較

計算模型樹、迴歸樹及其他模型效果,比較客觀的方法是計算相關係數,R*R值,Numpy中corrcoef(yHat, y, rowvar = 0)也即皮爾遜相關係數。

coding:

#================用樹迴歸進行預測的代碼=============def regTreeEval(model, inDat):    return float(model)def modelTreeEval(model, inDat):    n = shape(inDat)[1]    X = mat(ones((1,n+1)))    X[:,1:n+1] = inDat    return float(X*model)def treeForeCast(tree, inData, modelEval=regTreeEval):    if not isTree(tree):        return modelEval(tree, inData)          #如果輸入單個資料或行向量,返回一個浮點值    else:        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)def createForeCast(tree, testData, modelEval=regTreeEval):    m    = len(testData)    yHat = mat(zeros((m,1)))    for i in range(m):        yHat[i,0] = treeForeCast(tree, mat(testData[i]), modelEval)#多次調用treeForeCast函數,將結果以列的形式放到yHat變數中    return yHattrainMat = mat(loadDataSet("bikeSpeedVsIq_train.txt"))testMat  = mat(loadDataSet("bikeSpeedVsIq_test.txt"))myTree   = createTree(trainMat, ops=(1,20))yHat     = createForeCast(myTree, testMat[:,0])print "迴歸樹的皮爾遜相關係數:",corrcoef(yHat, testMat[:,1], rowvar=0)[0,1]myTree   = createTree(trainMat, modelLeaf, modelErr,(1,20))yHat     = createForeCast(myTree, testMat[:,0], modelTreeEval)print "模型樹的皮爾遜相關係數:",corrcoef(yHat, testMat[:,1], rowvar=0)[0,1]ws, X, Y = linearSolve(trainMat)print "線性迴歸係數:",wsfor i in range(shape(testMat)[0]):    yHat[i] = testMat[i,0]*ws[1,0] + ws[0,0]print "線性迴歸模型的皮爾遜相關係數:",corrcoef(yHat, testMat[:,1], rowvar=0)[0,1]

效果:

Figure 9-5: 迴歸樹、模型樹、簡單線性迴歸的皮爾遜相關係數

可以看出模型樹的結果比迴歸樹效果好。


9.7 使用Python的Tkinter庫建立GUI

本小結用到python的一個圖形化使用者介面(GUI, Graphical User Interface)架構——Tkinter。

Tkinter的GUI由一些小組件組成(Widge)組成。小組件:文字框、按鈕、標籤和複選按鈕等對象。 Figure 9-6: Tkinter的hello world

myLabel調用grid()方法時,把myLabel的位置告訴了布局管理器,grid()函數會把小組件安排在一個二維表格中。

coding:

#!/usr/bin/env python# coding=utf-8#用於構建樹管理器介面的Tkinter小組件from numpy import *from Tkinter import *import regTreesdef reDraw(tolS, tolN):    passdef drawNewTree():    passroot = Tk()Label(root, text = "Plot Place Holder").grid(row=0, columnspan=3)    #設定文本,第0行,距0的行值為3,Label(root, text = "tolN").grid(row=1, column=0)tolNentry = Entry(root)                                              #Entry為允許單行文本輸入的文字框,設定文字框,再定位置第1行第1列,再插入數值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)#Botton按鈕,設定第1行第2列,列值為3chkBtnVar = IntVar()                                                               #IntVar為按鈕整數值小組件chkBtn    = Checkbutton(root, text = "Model Tree", variable = chkBtnVar)chkBtn.grid(row=3, column=0, columnspan = 2)reDraw.rawDat  = mat(regTrees.loadDataSet("sine.txt"))reDraw.testDat = arange(min(reDraw.rawDat[:,0]), max(reDraw.rawDat[:,0]),0.01)reDraw(1.0,10)root.mainloop()

效果: Figure 9-7: 使用多個Tkinter組件建立的樹管理器

整合Matplotlib和Tkinter

matplotlib繪製的映像可以放到GUI上。matplotlib構建程式時包含一個前端,如plot、scatter函數,也同時建立了一個後端,用於實現繪圖和不同應用之間的介面,改變後端可以將映像繪製在PNG,PDF,SVG等格式的檔案上。matplotlib將後端設定為TkAgg,TkAgg可以在所選GUI架構上調用Agg,把Agg呈現在畫布上。

coding:

import matplotlibmatplotlib.use("TkAgg")                         #設定後端為TkAggfrom matplotlib.backends.backend_tkagg import FigureCanvasTkAggfrom matplotlib.figure import Figuredef reDraw(tolS,tolN):    reDraw.f.clf()                              #清空之前的映像    reDraw.a = reDraw.f.add_subplot(111)        #重新添加子圖    if chkBtnVar.get():                         #檢查複選框是否選中,確定是模型樹還是迴歸樹        if tolN<2:            tolN=2        myTree = regTrees.createTree(reDraw.rawDat, regTrees.modelLeaf, regTrees.modelErr,(tolS,tolN))        yHat   = regTrees.createForeCast(myTree, reDraw.testDat, regTrees.modelTreeEval)    else:        myTree = regTrees.createTree(reDraw.rawDat, ops=(tolS,tolN))        yHat   = regTrees.createForeCast(myTree, reDraw.testDat)    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():                                #擷取使用者輸入的值,tolN期望得到整數值,tolS期望得到浮點數,    try:        tolN = int(tolNentry.get())             #在Entry組件調用get方法,    except:        tolN = 10        print "enter Integer for tolN"        tolNentry.delete(0,END)        tolNentry.insert(0,"10")    try:        tolS = float(tolSentry.get())    except:        tolS = 1.0        print "enter Integer for tolS"        tolSentry.delete(0,END)        tolSentry.insert(0,"1.0")    return tolN,tolSdef drawNewTree():                              #有人點擊ReDraw按鈕時就會調用該函數    tolN,tolS = getInputs()                     #得到輸入框的值    reDraw(tolS,tolN)                           #調用reDraw函數root = Tk()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)

效果:

Figure 9-8: 用treeExplore的GUI構建的迴歸樹


Figure 9-9: 模型樹。參數為tolN=1,tolS=0 9.8小結

資料集中輸入資料和目標變數呈非線性關係,可使用樹結構來對預測值分段,包括分段常數或分段直線。分葉節點使用分段常數則為迴歸樹,若為線性迴歸方程則為模型樹。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.