1. 程式人生 > >機器學習中的那些樹——決策樹(三、CART 樹)

機器學習中的那些樹——決策樹(三、CART 樹)

前言

距上篇文章已經過了9個月 orz。。趁著期末複習,把部落格補一補。。

在前面的文章中介紹了決策樹的 ID3,C4.5 演算法。我們知道了 ID3 演算法是基於各節點的資訊增益的大小 \(\operatorname{Gain}(D, a)=\operatorname{Ent}(D)-\sum_{v} \frac{\left|D^{v}\right|}{|D|} \operatorname{Ent}\left(D^{v}\right)\) 進行劃分,但是存在偏向選取特徵值較多的特徵的問題,因此提出了 C4.5 演算法,即以資訊增益比為標準進行劃分 \(\operatorname{Gain}_{-} \operatorname{ratio}(D, a)=\frac{\operatorname{Gain}(D, a)}{I V(a)}\) 其中 \(I V(a)=-\sum_{v=1}^{V} \frac{\left|D^{v}\right|}{|D|} \log \frac{\left|D^{v}\right|}{|D|}\) 。但是,你可能注意到了,ID3 和 C4.5 演算法都不能用來做迴歸問題。這篇文章,將介紹 CART(Classification and Regression Tree) 樹的原理,及其實現。

CART 樹

基尼係數

與前面介紹的決策樹不同,CART 樹為二叉樹,其劃分是基於基尼係數(Gini Index)進行。

先來看看基尼值
\[ \operatorname{Gini}(D)=\sum_{k=1}^{K} \sum_{k^{\prime} \neq k} p_{k} ( 1 - p_{k})=1-\sum_{k=1}^{K} p_{k}^{2} \]
上式從直觀上反應了從資料集中任取2個樣本,其類別不一致的概率,其值越小,純度越高。

基尼係數
\[ Gini\_Index(D,a)=\sum_{v=1}^{V}\frac{|D^v|}{|D|}Gini(D^v) \]

劃分方式

離散值

也許你已經發現,CART 樹在對離散值做劃分的時候,若該特徵只有兩個屬性值,那很容易,一邊一種就好,但是當屬性值大於等於 3 的時候呢?比如 ['青年', '中年', '老年'],這時候應該如何做劃分?當然是把所有的方式都遍歷一遍啦,存在以下三種情況 [(('青年'), ('中年', '老年')), (('中年'), ('青年', '老年')), (('老年'), ('中年', '青年'))]。到這裡我想到了這幾個問題:

  1. 在做資料探勘競賽時,大佬們常說做交叉特徵能夠幫助決策樹更好地做劃分,是不是因為這種劃分方式的原因。
  2. 這種劃分方式是不是有些不太適合具有高基數類別變數的資料?所以有些時候採用對這些變數做 count 等統計特徵的時候也會有較大的提升

連續值

之前介紹的都是離散值的處理,那麼,當遇到連續值的時候,CART 樹又是怎麼處理的呢?因為是二叉樹,所以肯定是選取一個值,大於這個值的分到一個節點中去,小於的分到另一節點中。

那麼,這裡就涉及到具體的操作了,一般會在劃分時先將這一列特徵值進行排序,如果有 N 個樣本,那麼最多會有 N - 1 種情況,從頭到尾遍歷,每次選擇兩個值的中點作為劃分點,然後計算基尼係數,最後選擇值最小的做劃分。

如果你關注演算法複雜度的話,會發現 CART 樹每次做劃分的時候都需要遍歷所有情況,速度就會很慢。在 XGBoost 和 LightGBM 中,好像是採用了策略對這一計算進行了加速(挖個坑,後面看 XGBoost 和 LightGBM 的時候補上)。

CART 迴歸樹

用 CART 來做分類問題相信有了 C4.5 與 ID3 的基礎,再加上面的介紹,肯定也很容易就知道怎麼做了。這裡我來講講如何用 CART 樹來做迴歸問題。

思考一個問題,樹模型並不像線性模型那樣,可以算出一個 y 值,那麼我們如何確定每個葉子節點的預測值呢?在數學上,迴歸樹可以看作一個分段函式,每個葉子節點確定一個分段區間,葉子節點的輸出為函式在該節點上的值,且該值為一個定值。

假設 CART 樹 T 將特徵空間劃分為 |T| 個區域 \(R_i\) ,並且在每個區域對應的值為 \(b_i\) ,對應的假設函式為
\[ h(x)=\sum_{i=1}^{|T|} b_{i} \mathbb{I}\left(x \in R_{i}\right) \]
那麼,問題在這裡就變成了如何劃分區域 \(R_i\) 和如何確定每個區域 \(R_i\) 上對應的值 \(b_i\)。

假設區域 \(R_i\) 已知,那我們可以使用最小平方損失 \(\sum_{x^{(i)} \in R_j}(y^{(i)}-h(x^{i}))^2 = \sum_{x^{(i)} \in R_j}(y^{(i)}-b_j)^2\) ,來求對應的 \(b_j\) ,顯然有 \(b_j=avg(y^{(i)}|x^{(i)} \in R_j)\) 。

為了劃分區域,可採用啟發式的方法,選擇第 \(u\) 個屬性和對應的值 \(v\),作為劃分屬性和劃分閾值,定義兩個區域 \(R_1(u,v)=\{x|x_u\le v\}\) 和 \(R_2=\{x|x_u>v\}\) ,然後通過求解下式尋找最優的劃分屬性和劃分閾值
\[ \min _{u, v}\left[\min _{b_{1}} \sum_{x^{(i)} \in R_{1}(u, v)}\left(y^{(i)}-b_{1}\right)^{2}+\min _{b_{2}} \sum_{x^{(i)} \in R_{2}(u, v)}\left(y^{(i)}-b_{2}\right)^{2}\right] \\ b_i=avg(y^{(i)}|x^{(i)} \in R_i) \]
再對兩個區域重複上述劃分,直到滿足條件停止。

實現

下面又到了愉快的程式碼時間,這裡我只寫了分類的情況,迴歸樹只需將裡面使用的基尼係數改成上面最小化的式子即可。

def createDataSetIris():
    '''
    函式:獲取鳶尾花資料集,以及預處理
    返回:
        Data:構建決策樹的資料集(因打亂有一定隨機性)
        Data_test:手動劃分的測試集
        featrues:特徵名列表
        labels:標籤名列表
    '''
    labels = ["setosa","versicolor","virginica"]
    with open('iris.csv','r') as f:
        rawData = np.array(list(csv.reader(f)))
        features = np.array(rawData[0,1:-1]) 
        dataSet = np.array(rawData[1:,1:]) #去除序號和特徵列
        np.random.shuffle(dataSet) #打亂(之前如果不加array()得到的會是引用,rawData會被一併打亂)
        data = dataSet[0:,1:] 
    return rawData[1:,1:], data, features, labels

rawData, data, features, labels = createDataSetIris()

def calcGiniIndex(dataSet):
    '''
    函式:計算資料集基尼值
    引數:dataSet:資料集
    返回: Gini值
    ''' 
    counts = [] #每個標籤在資料集中出現的次數
    count = len(dataSet) #資料集長度
    for label in labels:
        counts.append([d[-1] == label for d in dataSet].count(True))
    
    gini = 0
    for value in counts:
        gini += (value / count) ** 2
    
    return 1 - gini

def binarySplitDataSet(dataSet, feature, value):
    '''
    函式:將資料集按特徵列的某一取值換分為左右兩個子資料集
    引數:dataSet:資料集
        feature:資料集中某一特徵列
        value:該特徵列中的某個取值
    返回:左右子資料集
    '''
    matLeft = [d for d in dataSet if d[feature] <= value]
    matRight = [d for d in dataSet if d[feature] > value]
    return matLeft,matRight

def classifyLeaf(dataSet, labels):
    '''
    函式:求資料集最多的標籤,用於結點分類
    引數:dataSet:資料集
        labels:標籤名列表
    返回:該標籤的index
    '''
    counts = [] 
    for label in labels:
        counts.append([d[-1] == label for d in dataSet].count(True))
    return np.argmax(counts) #argmax:使counts取最大值的下標

def chooseBestSplit(dataSet, labels, leafType=classifyLeaf, errType=calcGiniIndex, threshold=(0.01,4)):
    '''
    函式:利用基尼係數選擇最佳劃分特徵及相應的劃分點
    引數:dataSet:資料集
        leafType:葉結點輸出函式(當前實驗為分類)
        errType:損失函式,選擇劃分的依據(分類問題用的就是GiniIndex)
        threshold: Gini閾值,樣本閾值(結點Gini或樣本數低於閾值時停止)
    返回:bestFeatureIndex:劃分特徵
        bestFeatureValue:最優特徵劃分點
    '''
    thresholdErr = threshold[0] #Gini閾值
    thresholdSamples = threshold[1] #樣本閾值
    err = errType(dataSet)
    bestErr = np.inf
    bestFeatureIndex = 0 #最優特徵的index
    bestFeatureValue = 0 #最優特徵劃分點

    #當資料中輸出值都相等時,返回葉結點(即feature=None,value=結點分類)
    if err == 0:
        return None, dataSet[0][-1]
    #檢驗資料集的樣本數是否小於2倍閾值,若是則不再劃分,返回葉結點
    if len(dataSet) < 2 * thresholdSamples:
        return None, labels[leafType(dataSet, labels)] #dataSet[0][-1]
    #嘗試所有特徵的所有取值,二分資料集,計算err(本實驗為Gini),保留bestErr
    for i in range(len(dataSet[0]) - 1):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList) #第i個特徵的可能取值
        for value in uniqueVals:
            leftSet,rightSet = binarySplitDataSet(dataSet, i, value)
            if len(leftSet) < thresholdSamples or len(rightSet) < thresholdSamples:
                continue
#             print(len(leftSet), len(rightSet))
            gini = (len(leftSet) * calcGiniIndex(leftSet) + len(rightSet) * calcGiniIndex(rightSet)) / (len(leftSet) + len(rightSet))
            if gini < bestErr:
                bestErr = gini
                bestFeatureIndex = i
                bestFeatureValue = value
    #檢驗Gini閾值,若是則不再劃分,返回葉結點
    
    if err - bestErr < thresholdErr:
                return None, labels[leafType(dataSet, labels)] 
    
    return bestFeatureIndex,bestFeatureValue

def createTree_CART(dataSet, labels, leafType=classifyLeaf, errType=calcGiniIndex, threshold=(0.01,4)):

    '''
    函式:建立CART樹
    引數:同上
    返回:CART樹
    '''
    feature,value = chooseBestSplit(dataSet, labels, leafType, errType, threshold)
#     print(features[feature])
    #是葉結點則返回決策分類(chooseBestSplit返回None時表明這裡是葉結點)
    if feature is None:
        return value
    #否則建立分支,遞迴生成子樹
#     print(feature, value, len(dataSet))
    leftSet,rightSet = binarySplitDataSet(dataSet, feature, value)   
    myTree = {}
    myTree[features[feature]] = {}
    myTree[features[feature]]['<=' + str(value) + ' contains' + str(len(leftSet))] = createTree_CART(leftSet, np.array(leftSet)[:,-1], leafType, errType,threshold)
    myTree[features[feature]]['>' + str(value) + ' contains' + str(len(rightSet))] = createTree_CART(rightSet, np.array(rightSet)[:,-1], leafType, errType,threshold)
    
    return myTree

CARTTree = createTree_CART(data, labels, classifyLeaf, calcGiniIndex, (0.01,4))
treePlotter.createPlot(CARTTree)

相關推薦

機器學習那些——決策CART

前言 距上篇文章已經過了9個月 orz。。趁著期末複習,把部落格補一補。。 在前面的文章中介紹了決策樹的 ID3,C4.5 演算法。我們知道了 ID3 演算法是基於各節點的資訊增益的大小 \(\operatorname{Gain}(D, a)=\operatorname{Ent}(D)-\sum_{v} \f

python機器學習案例系列教程——關聯分析AprioriFP-growth

關聯分析的基本概念 關聯分析(Association Analysis):在大規模資料集中尋找有趣的關係。 頻繁項集(Frequent Item Sets):經常出現在一塊的物品的集合,即包含0個或者多個項的集合稱為項集。 支援度(Support)

機器學習那些必要又困惑的數學知識

現在很多想從事於機器學習的朋友都存在很多困惑,主要是很多相關的書看不懂,尤其是數學部分,機器學習的基礎是數學。數學並非是一個可選可不選的理論方法,而是不可或缺的支柱。對於機器學習演算法工程師而言,微積分、線性代數、概率論毫無疑問是需要掌握的。   為什麼要強調數學? 毫無

機器學習的超平面理解SVM開篇之超平面詳解

目錄 一、什麼是超平面 二、點到超平面的距離  三、 判斷超平面的正反 一、什麼是超平面 以上是三維為例子。  通過查閱資料對超平面有了一定的認識, n 維空間中的超平面由下面的方程確定:  其中,w&nb

機器學習的正則化Regularization

參考知乎回答:https://www.zhihu.com/question/20924039  以及部落格  https://blog.csdn.net/jinping_shi/article/details/52433975 定義&用途 經常能在L

機器學習的線性代數知識

關於作者 作者小碩一枚,研究方向為機器學習與自然語言處理,歡迎大家關注我的個人部落格https://wangjie-users.github.io/,相互交流,一起學習成長。 前言 在機器學習中的線性代數知識(上)一文中,主要講解了矩陣的本質,以及對映視角下的特

機器學習常見的問題整理

1.KNN演算法有哪些缺點? (1)計算代價很大 ①由於KNN必須對分類資料計算與每一個訓練資料的距離,非常耗時; ②KNN演算法必須儲存全部的資料集,如果訓練資料集很大,那麼就需要耗費大量的儲存空間; (2)無法處理categorical變數

機器學習的資料預處理sklearn preprocessing

Standardization即標準化,儘量將資料轉化為均值為零,方差為一的資料,形如標準正態分佈(高斯分佈)。實際中我們會忽略資料的分佈情況,僅僅是通過改變均值來集中資料,然後將非連續特徵除以他們的標準差。sklearn中 scale函式提供了簡單快速的singlearr

機器學習:過擬合overfitting和欠擬合underfitting

Underfitting is easy to check as long as you know what the cost function measures. The definition of the cost function in linear regression is half the me

機器學習regularization正則化加入weight_decay的作用

Regularization in Linear Regression 轉載自:http://blog.sina.com.cn/s/blog_a18c98e5010115ta.html Regularization是Linear Regression中很重要的一步。

機器學習數學基礎之矩陣理論

gis 引入 定義 增加 2017年 理論值 nbsp 得到 正數 矩陣求導 目錄 一、 矩陣求導的基本概念 1. 一階導定義 2. 二階導數 二、 梯度下降 1. 方向導數. 1.1 定義 1.2 方向導數的計算公式. 1.3 梯度下降最快的方向 1.

機器學習之支持向量機:核函數和KKT條件的理解

麻煩 ron 現在 調整 所有 核函數 多項式 err ges 註:關於支持向量機系列文章是借鑒大神的神作,加以自己的理解寫成的;若對原作者有損請告知,我會及時處理。轉載請標明來源。 序: 我在支持向量機系列中主要講支持向量機的公式推導,第一部分講到推出拉格朗日對偶函數的對

Spark2.0機器學習系列之7: MLPC多層神經網絡

element nbsp hid 隨機梯度下降 support file dict 分類器 希望 Spark2.0 MLPC(多層神經網絡分類器)算法概述 MultilayerPerceptronClassifier(MLPC)這是一個基於前饋神經網絡的分類器,它是一種在

吳恩達機器學習第5周Neural NetworksCost Function and Backpropagation

and div bsp 關於 邏輯回歸 info src clas 分享 5.1 Cost Function 假設訓練樣本為:{(x1),y(1)),(x(2),y(2)),...(x(m),y(m))} L = total no.of layers in network

機器學習之貝葉斯網路

引言   貝葉斯網路是機器學習中非常經典的演算法之一,它能夠根據已知的條件來估算出不確定的知識,應用範圍非常的廣泛。貝葉斯網路以貝葉斯公式為理論接觸構建成了一個有向無環圖,我們可以通過貝葉斯網路構建的圖清晰的根據已有資訊預測未來資訊。貝葉斯網路適用於表達和分析不確定性和概率性的事件,應用於有條件地依賴多種控

機器學習 吳恩達 課程筆記自用,持續更新

機器學習 吳恩達 簡介 本筆記為自用筆記,因此只記錄了自己覺得重要的部分,所以不建議想要系統學習的人閱讀此筆記。 緒論 監督學習 我們給演算法一個數據集,其中包含了正確的答案,目的為給出更多的正確答案 “迴歸問題”:regression “分類問題”:cla

評估機器學習模型的幾種方法驗證集的重要性

評估機器學習模型的幾種方法(驗證集的重要性) 什麼是評估機器學習模型       機器學習的目的是得到可以泛化(generalize)的模型,即在前所未見的資料上表現很好的模型,而過擬合則是核心難點。你只能控制可以觀察的事情,所以能夠可靠地衡量模型的泛化能力非常

機器學習之樸素貝葉斯附垃圾郵件分類

樸素貝葉斯分類器介紹概述  樸素貝葉斯分類器技術基於貝葉斯定理,特別適用於輸入維數較高的情況。儘管樸素貝葉斯方法簡單,但它通常比更復雜的分類方法更勝一籌。                  

機器學習】k-fold cross validationk-摺疊交叉驗證

交叉驗證的目的:在實際訓練中,模型通常對訓練資料好,但是對訓練資料之外的資料擬合程度差。用於評價模型的泛化能力,從而進行模型選擇。 交叉驗證的基本思想:把在某種意義下將原始資料(dataset)進行分組,一部分做為訓練集(train set),另一部分做為驗證集(valid

周志華《機器學習》課後習題解答系列:Ch2

本章概要 本章講述了模型評估與選擇(model evaluation and selection)的相關知識: 2.1 經驗誤差與過擬合(empirical error & overfitting) 精度accuracy、訓練誤差(經驗誤差)