深刻學習高級非線性迴歸算法 --- 樹迴歸系列算法

前言python

  前文討論的迴歸算法都是全局且針對線性問題的迴歸,即便是其中的局部加權線性迴歸法,也有其弊端(具體請參考前文)算法

  採用全局模型會致使模型很是的臃腫,由於須要計算全部的樣本點,並且現實生活中不少樣本都有大量的特徵信息。編程

  另外一方面,實際生活中更多的問題都是非線性問題小程序

  針對這些問題,有了樹迴歸系列算法。app

迴歸樹函數

  在先前決策樹的學習中,構建樹是採用的 ID3 算法。在迴歸領域,該算法就有個問題,就是派生子樹是按照全部可能值來進行派生。性能

  所以 ID3 算法沒法處理連續性數據。學習

  故可以使用二元切分法,以某個特定值爲界進行切分。在這種切分法下,子樹個數小於等於2。測試

  除此以外,再修改擇優原則香農熵 (由於數據變爲連續型的了),即可將樹構建成一棵可用於迴歸的樹,這樣一棵樹便叫作迴歸樹優化

  構建迴歸樹的僞代碼:

1 找到最佳的待切分特徵:
2     若是該節點不能再分,將此節點存爲葉節點。
3     執行二元切分
4     左右子樹分別遞歸調用此函數

  二元切分的僞代碼:

1 對每一個特徵:
2     對每一個特徵值:
3         將數據集切成兩份
4         計算切分偏差
5         若是當前偏差小於最小偏差,則更新最佳切分以及最小偏差。

  特別說明,終止劃分 (並直接創建葉節點)有三種狀況:

  1. 特徵值劃分完畢

  2. 劃分子集過小

  3. 劃分後偏差改進不大

  這幾個操做被稱作 "預剪枝"。

  下面給出一個完整的迴歸樹的小程序:

  1 #!/usr/bin/env python
  2 # -*- coding:UTF-8 -*-
  3 
  4 '''
  5 Created on 2015-01-05
  6 
  7 @author: fangmeng
  8 '''
  9 
 10 from numpy import *
 11 
 12 def loadDataSet(fileName):
 13     '載入測試數據'
 14     
 15     dataMat = []
 16     fr = open(fileName)
 17     for line in fr.readlines():
 18         curLine = line.strip().split('\t')
 19         # 全部元素轉換爲浮點類型(函數編程)
 20         fltLine = map(float,curLine)
 21         dataMat.append(fltLine)
 22     return dataMat
 23 
 24 #============================
 25 # 輸入:
 26 #        dataSet: 待切分數據集
 27 #        feature: 切分特徵序號
 28 #        value:    切分值
 29 # 輸出:
 30 #        mat0,mat1: 切分結果
 31 #============================
 32 def binSplitDataSet(dataSet, feature, value):
 33     '切分數據集'
 34     
 35     mat0 = dataSet[nonzero(dataSet[:,feature] > value)[0],:][0]
 36     mat1 = dataSet[nonzero(dataSet[:,feature] <= value)[0],:][0]
 37     return mat0,mat1
 38 
 39 #========================================
 40 # 輸入:
 41 #        dataSet: 數據集
 42 # 輸出:
 43 #        mean(dataSet[:,-1]): 均值(也就是葉節點的內容)
 44 #========================================
 45 def regLeaf(dataSet):
 46     '生成葉節點'
 47     
 48     return mean(dataSet[:,-1])
 49 
 50 #========================================
 51 # 輸入:
 52 #        dataSet: 數據集
 53 # 輸出:
 54 #        var(dataSet[:,-1]) * shape(dataSet)[0]: 平方偏差
 55 #========================================
 56 def regErr(dataSet):
 57     '計算平方偏差'
 58     
 59     return var(dataSet[:,-1]) * shape(dataSet)[0]
 60 
 61 #========================================
 62 # 輸入:
 63 #        dataSet: 數據集
 64 #        leafType: 葉子節點生成器
 65 #        errType: 偏差統計器
 66 #        ops: 相關參數
 67 # 輸出:
 68 #        bestIndex: 最佳劃分特徵 
 69 #        bestValue: 最佳劃分特徵值
 70 #========================================
 71 def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):
 72     '選擇最優劃分'
 73     
 74     # 得到相關參數中的最大樣本數和最小偏差效果提高值
 75     tolS = ops[0]; 
 76     tolN = ops[1]
 77     
 78     # 若是全部樣本點的值一致,那麼直接創建葉子節點。
 79     if len(set(dataSet[:,-1].T.tolist()[0])) == 1: 
 80         return None, leafType(dataSet)
 81     
 82     m,n = shape(dataSet)
 83     # 當前偏差
 84     S = errType(dataSet)
 85     # 最小偏差
 86     bestS = inf; 
 87     # 最小偏差對應的劃分方式
 88     bestIndex = 0; 
 89     bestValue = 0
 90     
 91     # 對於全部特徵
 92     for featIndex in range(n-1):
 93         # 對於某個特徵的全部特徵值
 94         for splitVal in set(dataSet[:,featIndex]):
 95             # 劃分
 96             mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)
 97             # 若是劃分後某個子集中的個數不達標
 98             if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): continue
 99             # 當前劃分方式的偏差
100             newS = errType(mat0) + errType(mat1)
101             # 若是這種劃分方式的偏差小於最小偏差
102             if newS < bestS: 
103                 bestIndex = featIndex
104                 bestValue = splitVal
105                 bestS = newS
106     
107     # 若是當前劃分方式還不如不劃分時候的偏差效果
108     if (S - bestS) < tolS: 
109         return None, leafType(dataSet)
110     # 按照最優劃分方式進行劃分
111     mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
112     # 若是劃分後某個子集中的個數不達標
113     if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN):
114         return None, leafType(dataSet)
115     
116     return bestIndex,bestValue
117 
118 #========================================
119 # 輸入:
120 #        dataSet: 數據集
121 #        leafType: 葉子節點生成器
122 #        errType: 偏差統計器
123 #        ops: 相關參數
124 # 輸出:
125 #        retTree: 迴歸樹
126 #========================================
127 def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):
128     '構建迴歸樹'
129     
130     # 選擇最佳劃分方式
131     feat, val = chooseBestSplit(dataSet, leafType, errType, ops)
132     # feat爲None的時候無需劃分返回葉子節點
133     if feat == None: return val #if the splitting hit a stop condition return val
134     
135     # 遞歸調用構建函數並更新樹
136     retTree = {}
137     retTree['spInd'] = feat
138     retTree['spVal'] = val
139     lSet, rSet = binSplitDataSet(dataSet, feat, val)
140     retTree['left'] = createTree(lSet, leafType, errType, ops)
141     retTree['right'] = createTree(rSet, leafType, errType, ops)
142     
143     return retTree  
144 
145 def test():
146     '展現結果'
147     
148     # 載入數據
149     myDat = loadDataSet('/home/fangmeng/ex0.txt')
150     # 構建迴歸樹
151     myDat = mat(myDat)
152     
153     print createTree(myDat)
154     
155     
156 if __name__ == '__main__':
157     test()

  測試結果:

  

迴歸樹的優化工做 - 剪枝

  在上面的代碼中,終止遞歸的條件中已經加入了重重的 "剪枝" 工做。

  這些在建樹的時候的剪枝操做一般被成爲預剪枝。這是頗有頗有必要的,通過預剪枝的樹幾乎就是沒有預剪枝樹的大小的百分之一甚至更小,而性能相差無幾。

  而在樹創建完畢以後,基於訓練集和測試集能作更多更高效的剪枝工做,這些工做叫作 "後剪枝"。

  可見,剪枝是一項較大的工做量,是對樹很是關鍵的優化過程

  後剪枝過程的僞代碼以下:

1 基於已有的樹切分測試數據:
2     若是存在任一子集是一棵樹,則在該子集上遞歸該過程。
3     計算將當前兩個葉節點合併後的偏差
4     計算不合並的偏差
5     若是合併會下降偏差,則將葉節點合併。

  具體實現函數以下:

 1 #===================================
 2 # 輸入:
 3 #        obj: 判斷對象
 4 # 輸出:
 5 #        (type(obj).__name__=='dict'): 判斷結果
 6 #===================================
 7 def isTree(obj):
 8     '判斷對象是否爲樹類型'
 9     
10     return (type(obj).__name__=='dict')
11 
12 #===================================
13 # 輸入:
14 #        tree: 處理對象
15 # 輸出:
16 #        (tree['left']+tree['right'])/2.0: 坍塌後的替代值
17 #===================================
18 def getMean(tree):
19     '坍塌處理'
20     
21     if isTree(tree['right']): tree['right'] = getMean(tree['right'])
22     if isTree(tree['left']): tree['left'] = getMean(tree['left'])
23     
24     return (tree['left']+tree['right'])/2.0
25   
26 #===================================
27 # 輸入:
28 #        tree: 處理對象
29 #        testData: 測試數據集
30 # 輸出:
31 #        tree: 剪枝後的樹
32 #===================================  
33 def prune(tree, testData):
34     '後剪枝'
35     
36     # 無測試數據則坍塌此樹
37     if shape(testData)[0] == 0: 
38         return getMean(tree)
39     
40     # 若左/右子集爲樹類型
41     if (isTree(tree['right']) or isTree(tree['left'])):
42         # 劃分測試集
43         lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
44     # 在新樹新測試集上遞歸進行剪枝
45     if isTree(tree['left']): tree['left'] = prune(tree['left'], lSet)
46     if isTree(tree['right']): tree['right'] =  prune(tree['right'], rSet)
47     
48     # 若是兩個子集都是葉子的話,則在進行偏差評估後決定是否進行合併。
49     if not isTree(tree['left']) and not isTree(tree['right']):
50         lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
51         errorNoMerge = sum(power(lSet[:,-1] - tree['left'],2)) +sum(power(rSet[:,-1] - tree['right'],2))
52         treeMean = (tree['left']+tree['right'])/2.0
53         errorMerge = sum(power(testData[:,-1] - treeMean,2))
54         if errorMerge < errorNoMerge: 
55             return treeMean
56         else: return tree
57     else: return tree

模型樹

  這也是一種很棒的樹迴歸算法。

  該算法將全部的葉子節點不是表述成一個值,而是對葉子部分節點創建線性模型。好比能夠是最小二乘法的基本線性迴歸模型。

  這樣在葉子節點裏存放的就是一組線性迴歸係數了。非葉子節點部分構造就和迴歸樹同樣。

  這個是上面創建迴歸樹算法的函數頭:

  createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):

  對於模型樹,只須要修改修改 leafType(葉節點構造器) 和 errType(偏差分析器) 的實現便可,分別對應以下modelLeaf 函數和 modelErr 函數:

 1 #=========================
 2 # 輸入:
 3 #        dataSet: 測試集
 4 # 輸出:
 5 #        ws,X,Y: 迴歸模型
 6 #=========================
 7 def linearSolve(dataSet):
 8     '輔助函數,用於構建線性迴歸模型。'
 9     
10     m,n = shape(dataSet)
11     X = mat(ones((m,n))); 
12     Y = mat(ones((m,1)))
13     X[:,1:n] = dataSet[:,0:n-1]; 
14     Y = dataSet[:,-1]
15     xTx = X.T*X
16     if linalg.det(xTx) == 0.0:
17         raise NameError('係數矩陣不可逆')
18     ws = xTx.I * (X.T * Y)
19     return ws,X,Y
20 
21 #=======================
22 # 輸入:
23 #       dataSet: 數據集
24 # 輸出:
25 #        ws: 迴歸係數
26 #=======================
27 def modelLeaf(dataSet):
28     '葉節點構造器'
29     
30     ws,X,Y = linearSolve(dataSet)
31     return ws
32 
33 #=======================================
34 # 輸入:
35 #       dataSet: 數據集
36 # 輸出:
37 #        sum(power(Y - yHat,2)): 平方偏差
38 #=======================================
39 def modelErr(dataSet):
40     '偏差分析器'
41     
42     ws,X,Y = linearSolve(dataSet)
43     yHat = X * ws
44     return sum(power(Y - yHat,2))

迴歸樹 / 模型樹的使用

  前面的工做主要介紹了兩種樹 - 迴歸樹,模型樹的構建,下面進一步學習如何利用這些樹來進行預測。

  固然,本質也就是遞歸遍歷樹。

  下爲遍歷代碼,經過修改參數設置要使用並傳遞進來的是迴歸樹仍是模型樹:

#==============================
# 輸入:
#       model: 葉子
#       inDat: 測試數據
# 輸出:
#        float(model): 葉子值
#==============================
def regTreeEval(model, inDat):
    '迴歸樹預測'
    
    return float(model)

#==============================
# 輸入:
#       model: 葉子
#       inDat: 測試數據
# 輸出:
#        float(X*model): 葉子值
#==============================
def modelTreeEval(model, inDat):
    '模型樹預測'
    n = shape(inDat)[1]
    X = mat(ones((1,n+1)))
    X[:,1:n+1]=inDat
    return float(X*model)

#==============================
# 輸入:
#        tree: 待遍歷樹
#        inDat: 測試數據
#        modelEval: 葉子值獲取器
# 輸出:
#        分類結果
#==============================
def treeForeCast(tree, inData, modelEval=regTreeEval):
    '使用迴歸/模型樹進行預測 (modelEval參數指定)'
    
    # 若是非樹類型,返回值。
    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)

  使用方法很是簡單,將樹和要分類的樣本傳遞進去就能夠了。若是是模型樹就將分類函數 treeForeCast 的第三個參數改成modelTreeEval便可。

  這裏就再也不演示實驗具體過程了。

小結

  1. 選擇哪一個迴歸方法,得看哪一個方法的相關係數高。(可以使用 corrcoef 函數計算)

  2. 樹的迴歸和分類算法其實本質上都屬於貪心算法,不斷去尋找局部最優解。

  3. 關於迴歸的討論就先告一段落,接下來將進入到無監督學習部分。

相關文章
相關標籤/搜索