樹回歸(Tree Regression)


一、分類樹構建存在的問題

1. 切分過於迅速

假定當前葉子節點選擇特征A來分割數據,那么數據A將不再后續的葉子節點中起作用,這樣就會造成切分過於迅速

2. 不能處理連續數據

想要處理連續型數據,必須先將連續性數據轉化成離散型數據。CART就是特別有名的利用二分法來處理連續性變量的樹形算法。將度量方法稍作修改,就可以實現回歸樹。

二、混亂度

分類樹中我們用信息熵和信息增益來決定最優划分屬性,實際上信息增益的意義就是,選擇這個划分屬性能夠達相對最好的分類效果,也就是給定節點時區計算數據的混亂度。

對於連續型數據來說,混亂度的衡量方法很簡單,計算所有數據的均值,統計出所有數據到均值點的差值,為了將正負同等看待,一般使用平方值或者是絕對值來代替。

三、偽代碼

for 遍歷 特征

  for 遍歷 特征值(我們可以選擇連續兩個數的平均值)

    將數據切分成兩部分

    計算切分后的誤差(混亂度)

    if 判斷 誤差和歷史最小誤差比較

      該誤差較小 存儲特征和特征值並更新歷史最小誤差

if 判斷 誤差減少較少 or 數據可供分割項較少(當誤差設定為0,可供分割項選1的時候,可以生成最大樹)

  結束回歸樹構建

結束所有遍歷,返回最佳特征和特征值(切分目標特征和閾值)

四、數值划分代碼

#-*-coding:utf-8-*-
## 引入numpy包
from numpy import *

## 加載數據
def loadDataSet(fileName):
    # 按照數據應該是3
    numFeat=len(open(fileName).readline().split('\t'))
    dataMat=[]
    fr=open(fileName)
    # for line in fr.readlines():
        # curLine=line.strip().split('\t')
        # # map(float,curLine)用來將每行的內容保存成一組浮點數,map形式
        # fltLine=map(float,curLine)
        # dataMat.append(fltLine)
    # return dataMat
    for line in fr.readlines():
        lineArr=[]
        curLine=line.strip().split('\t')
        for i in range(numFeat):
            lineArr.append(float(curLine[i]))
        dataMat.append(lineArr)
    return dataMat

## 通過數組過濾的方式來集合且分為兩個子集合 參數:數據集合,待切分的特征,該特征的某個值
def binSplitDataSet(dataSet,feature,value):
    # 大於小於得到的是一個布爾值,nonzero是根據布爾值得到相應的坐標,[0]指的是橫坐標,仔細想想就知道了
    mat0=dataSet[nonzero(dataSet[:,feature]>value)[0],:]
    mat1=dataSet[nonzero(dataSet[:,feature]<=value)[0],:]
    return mat0,mat1

## 均值函數和方差函數mean&var
# 葉子節點的確定 其實簡單來說就是求均值嘛
def regLeaf(dataSet):
    return mean(dataSet[:,-1])
# 計算目標變量的平方誤差,因為這里用到的是總誤差,所以乘上了樣本個數
def regErr(dataSet):
    return var(dataSet[:,-1])*shape(dataSet)[0]
    
## 用最佳方式分割數據集,並生成相應的葉子節點
# 第一個返回值為None的時候不會再進行切分
def chooseBestSplit(dataSet,leafType=regLeaf,errType=regErr,ops=(0,4)):
    # 用戶指定 控制函數停止時機 (容許的誤差下降值,切分的最少樣本數)
    tolS=ops[0]
    tolN=ops[1]
    # 如果目標變量是相同值,則返回變量
    if len(set(dataSet[:,-1].T.tolist()[0]))==1:
        print ("aaa")
        print (len(set(dataSet[:,-1].T.tolist()[0])))
        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].T.tolist()[0]):
            # 調用函數binSplitDataSet 將數據按照上一層結果進行簡單分類
            mat0,mat1=binSplitDataSet(dataSet,featIndex,splitVal)
            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
    # 根據運行結果找到合適的葉子節點 feat & val
    # 當S和bestS之間差距滿足最小下降值  則不再進行切分
    if (S-bestS)<tolS:
        print ("bbb")
        print (S,bestS,tolS)
        return None,leafType(dataSet)
    # 當兩邊的切分樣本  有一方小於4個  則不再進行切分
    if(shape(mat0)[0]<tolN) or (shape(mat1)[0]<tolN):
        print ("ccc")
        print (shape(mat0)[0])
        print (shape(mat1)[0])
        print (tolN)
        return None,leafType(dataSet)
    return bestIndex,bestValue
        
## 創建樹形結構 參數(數據集,創建葉子節點的函數,計算誤差的函數,(用戶定義)樹構建所需元祖)
def createTree(dataSet,leafType=regLeaf,errType=regErr,ops=(0,4)):
    feat,val=chooseBestSplit(dataSet,leafType,errType,ops)
    # 直到無法區分為止
    if feat==None:
        return val
    # 定義返回值--二叉樹
    retTree={}
    # 定義分支節點的屬性和值
    retTree['spInd']=feat
    retTree['spVal']=val
    # 用binSplitDataSet將數據分割成兩個部分
    lSet,rSet=binSplitDataSet(dataSet,feat,val)
    # 遞歸方法,重復創建二叉樹,直到無法區分位置
    retTree['left']=createTree(lSet,leafType,errType,ops)
    retTree['right']=createTree(rSet,leafType,errType,ops)
    return retTree

五、剪枝

為了解決過多的復雜度而帶來的過擬合問題,預剪枝和后剪枝的存在可以極大地避免決策樹陷入過擬合。

離散數據(連續數據)的預剪枝和后剪枝和標稱型數據的剪枝處理不太一樣,因為標稱型的數據分類絕對會到達分支,並且存在錯誤節點。但是由於離散數據在某個節點的某個特征分割之后,還可以在這個特征進一步分割,導致永遠的划分都是正確的,永遠都不會停止,所以它的預剪枝和后剪枝並不一樣。

離散數據的預剪枝可以采用終止條件,這個終止條件可以是判斷誤差、也可以是判斷當前節點達到什么程度,都可以。但是值得注意的是,當當前所有結點都是同一類數據的時候,直接跳出就成,沒必要進行比較喲。

因為每次划分都會比原先的准確率更高,所以后剪枝通常伴隨着訓練集和測試集來進行,通過測試機將訓練集訓練出來的足夠大的樹進行剪枝。從上置下的尋找葉節點(但實際上,程序運行時從下向上進行合並),如果合並后會降低誤差,那么就將葉節點進行合並。

六、后剪枝代碼(相對多出的代碼)

## 利用字典屬性來判斷這個有沒有子節點
def isTree(obj):
    # 是的話,就返回true,表明這個是個有葉子節點的節點
    return (type(obj).__name__=='dict')

## 遞歸方式獲取當前值
def getMean(tree):
    if isTree(tree['left']):
        tree['left']=getMean(tree['left'])
    if isTree(tree['right']):
        tree['right']=getMean(tree['right'])
    return (tree['left']+tree['right'])/2.0
## 剪枝處理
def prune(tree, testData):
    # 如果test的目標值為0,那么根據數結構,獲取左右數據
    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'],rSet)
    # 找到可以合並的點,即左右都存在的時刻,如果拆分后的比拆分前的准確率還要低,那么合並
    if not isTree(tree['left']) and not isTree(tree['right']):
        # 拆分數據,然后進行對比
        lSet,rSet=binSplitDataSet(testData,tree['spInd'],tree['spVal'])
        # 合並后的節點存儲值
        treeMean=(tree['left']+tree['right'])/2.0
        # 重新生成合並前的和合並后的誤差值,進行判斷
        errorNoMerge=sum(power(lSet[:,-1]-tree['left'],2))+sum(power(rSet[:,-1]-tree['right'],2))
        errorMerge=sum(power(testData[:,-1]-treeMean,2))
        #
        if errorMerge<errorNoMerge:
            print "merging"
            return treeMean
        else:
            return tree
    else:
        return tree

 七、模型樹

回歸樹就是之前提到的用節點來進行划分,模型樹是用線性函數來進行划分。回歸樹的葉節點是節點數據標簽值的平均值,而模型樹的節點數據是一個線性模型(可用最簡單的最小二乘法來構建線性模型)。

把一個葉節點定義為線性函數能夠更容易的將樹型結構縮減,其解釋性也是由於回歸樹的特點之一。

回歸樹種用的是均值記錄葉子節點信息,方差和記錄誤差。在模型樹種使用不一樣的概念。

## 線性回歸解決器
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] xTx=X.T*X # 矩陣的逆不存在會造成異常
    if linalg.det(xTx)==0.0: raise NameError("abc") 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))

哇,可是線性回歸又是怎么回事呢,別急,代碼中給到的,一會兒會整理。

補充:線性回歸(當然會單列專題來說這個事情)

利用線性模型可以嘗試去搞一個線性組合來進行預測,函數形式如下:

為了讓線性模型盡可能的完善,我們需要盡可能的減少均方誤差:

在線性回歸中,最小二乘法就是嘗試去找一條直線,讓所有的樣本到這個直線上的歐式距離之和最小,也就是上述公式,為了讓其得到最小值,我們通常需要對式子對未知數w向量和求導並設置為0可以進行求解。

為了方便討論我們通常將w向量和b放在一起進行討論,設定一個全新的矩陣D,D的前n-1列恆定為屬性值,最后一列設定初始值為1,實際上就是wx+b的形式,因為b的參數是1嘛,因此得到的誤差函數為:

求導並另其為0,得到線性回歸公式:

這個也就是上面公式的由來,嘖嘖

 

 

 

 

 

 

 

    


免責聲明!

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



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