決策樹分類回歸,ID3,c4.5,CART,及其Python代碼


決策樹模型

內部節點表示一個特征或者屬性,葉子結點表示一個類。決策樹工作時,從根節點開始,對實例的每個特征進行測試,根據測試結果,將實例分配到其子節點中,這時的每一個子節點對應着特征的一個取值,如此遞歸的對實例進行測試並分配,直到達到葉節點,最后將實例分配到葉節點所對應的類中。
決策樹具有一個重要的性質:互斥並且完備。每一個實例都被一條路徑或一條規則所覆蓋,而且只被一條路徑或一條規則所覆蓋,這里所謂覆蓋是指實例的特征與路徑上的特征一致或實例滿足規則的條件。

決策樹與條件概率分布

決策樹將特種空間划分為互不相交的單元或區域,在每個單元上定義了一個類的概率分布,則構成了條件概率分布。分類時,將該節點的實例強行分到條件概率大的那一類中。
決策樹學習就是由訓練數據集估計條件概率模型的過程。一個數據集可能對應不想矛盾的多個決策樹,通常選擇使損失函數最小的決策樹。通常現實中決策樹學習算法采用啟發式方法,近似求解這一優化問題,這樣得到的決策樹是次優的。
學習算法通常是遞歸的選擇最優特征。首先開始構建根節點,然后將所有訓練數據放到根節點中,選擇一個最優特征,按照這一特征對數據集分割成子集,使得各個子集有一個在當前條件下最好的分類,如果這個子集已經能夠被基本正確分類,則構建葉節點,如果還有子集不能正確分類,則對這些子集選擇新的最優特征,繼續對其分割構建新的節點。
但是這樣的方法可能能夠對訓練數據具有好的分類能力,但是不具有好的泛化能力,容易出現過擬合,因此我們需要對樹進行減枝,讓樹變得簡單,復雜度降低,使其具有很好的泛化能力,方法為去掉過於細分的葉節點,使其退回到父節點,甚至更高的節點,將父節點或更高節點改為新的葉節點。如果特征數量很多,也可以在開始時對特征進行選擇,只留下對訓練數據具有足夠分類能力的特征。
決策樹的生成對應於模型的局部選擇,決策樹的減枝對應於模型的全局選擇。決策樹的生成只考慮局部最優,決策樹的減枝則考慮全局最優。

信息增益

信息增益是用來選擇最優划分特征的指標。首先給出熵的概念。
在信息論與概率統計中,熵是表示隨機變量不確定性的度量。X為取值有限的離散隨機變量。概率分布為

P ( X = x i ) = p i         i = 1 , 2 , . . . , n

則X的熵為
H ( X ) = i = 1 n p i log p i

p i = 0 0 log 0 = 0 。對數以2為底定義單位為比特(bit),以e為底定義單位為納特(nat),熵只依賴於X的分布,不依賴於X的取值。熵越大,信息的不確定性越大。
條件熵
H ( Y | X ) = i = 1 n p i H ( Y | X = x i )

如果概率由數據估計得到,所對應的熵稱為經驗熵。
信息增益:得知特征X的信息而使得類Y的信息的不確定性減少的程度。特征A對訓練數據集D的信息增益g(D,A),定義為
g ( D , A ) = H ( D ) H ( D | A )

一般熵H(Y)與條件熵H(Y|X)之差稱為互信息。信息增益大的特征具有更強的分類能力。
特征選擇的方法為:隊訓練數據集D,計算其每個特征的信息增益,並比較他們的大小,選擇信息增益最大的特征。

信息增益比

信息增益作為划分訓練數據集的特征,存在偏向於選擇取值較多的特征的問題,用信息增益比可以很好的解決。
信息增益比:特征A對訓練數據集D的信息增益比 g R ( D , A ) 定義為其信息增益與訓練數據集D關於特征A的值的熵 H A ( D ) 之比

g R ( D , A ) = g ( D , A ) H A ( D )

其中, H A ( D ) = i = 1 n | D i | | D | log 2 | D i | | D | ,n為特征A取值的個數。


ID3算法

ID3算法的核心是在決策樹各個節點上應用信息增益准則選擇特征,遞歸的構建決策樹。
輸入:數據集D,特征集A,閾值 σ
輸出:決策樹T
(1).若D中的所有實例均屬於同一類 C k ,則T為單節點樹,並將類 C k 作為該節點的類標記,返回T。
(2).若 A = ,則T為單節點樹,選擇對多的特征作為該節點的類標記,返回T。
(3).計算A中各個特征對D的信息增益,選擇信息增益最大的特征 A g
(4).若 A g 的信息增益小於閾值 σ ,則T為單節點樹,選擇對多的特征作為該節點的類標記,返回T。
(5).否則,對 A g 按其每一個可能取值將數據集D划分為非空子集 D i ,將 D i 中實例數最大的類作為標記,構建子節點,由節點及其子節點構成樹T,返回T。
(6).對第i個子節點,以 D i 為訓練集,以 A A g 為特征集,遞歸的調用(1)~(5),得到樹 T i ,返回 T i

# 實驗數據
ID,年齡,有工作,有自己的房子,信貸情況,類別
1,青年,否,否,一般,否
2,青年,否,否,好,否
3,青年,是,否,好,是
4,青年,是,是,一般,是
5,青年,否,否,一般,否
6,中年,否,否,一般,否
7,中年,否,否,好,否
8,中年,是,是,好,是
9,中年,否,是,非常好,是
10,中年,否,是,非常好,是
11,老年,否,是,非常好,是
12,老年,否,是,好,是
13,老年,是,否,好,是
14,老年,是,否,非常好,是
15,老年,否,否,一般,否

ID3, C4.5代碼實現

# classification with Decision Tree by ID3 and C4.5 algorithm

import  numpy as np
import pandas as pd
from sklearn.model_selection import  train_test_split

class DecisionTree():
    def __init__(self, feature_names, eps=0.03, depth=10, min_leaf_size=1, method="gain"):
        '''
        eps:精度
        depth:深度
        min_leaf_size:最小葉子大小
        method:方法gain或者ratio
        '''
        self.feature_names = feature_names
        self.eps = eps
        self.depth = depth
        self.min_leaf_size=min_leaf_size
        self.method = method


    def gain_entropy(self, x):
        """
        計算信息增益
        x: 數據集的某個特征
        :return ent: H(D)=-\sum_{k=1}^K\frac{|C_k|}{|D|}\log_2\frac{|C_k|}{|D|}
        """
        entropy = 0
        for x_value in set(x):
            p = x[x == x_value].shape[0] / x.shape[0]
            entropy -= p * np.log2(p)
        return entropy

    def gain_condition_entropy(self, x, y):
        entropy = 0
        for x_value in set(x):
            sub_y = y[x == x_value]
            tmp_ent = self.gain_entropy(sub_y)
            p = sub_y.shape[0] / y.shape[0]
            entropy += p * tmp_ent
        return entropy


    def Gain(self, x, y):
        if self.method == "gain":
            return self.gain_entropy(x) - self.gain_condition_entropy(x, y)
        else:
            return 1 - self.gain_condition_entropy(x, y) / self.gain_entropy(x)

    def fit(self, X, y):
       self.tree = self._built_tree(X, y)

    def _built_tree(self, X, y, depth=1):
        if len(set(y)) == 1:
            return y[0]



        label_1, label_2 = set(y)
        max_label = label_1 if np.sum(y == label_1) > np.sum(y == label_2) else label_2
        # print("max_label", max_label)

        if len(X[0]) == 0:
            return max_label
        if depth > self.depth:
            return max_label

        if len(y) < self.min_leaf_size:
            return max_label


        best_feature_index = 0
        max_gain = 0
        for feature_index in range(len(X[0])):
            gain = self.Gain(X[:, feature_index], y)
            if max_gain < gain:
                max_gain = gain
                best_feature_index = feature_index

        # print(max_gain)

        if max_gain < self.eps:
            return max_label

        T = {}
        sub_T = {}
        for best_feature in set(X[:, best_feature_index]):
            '''
            best_feature:某個特征下的特征類別
            '''
            sub_y = y[X[:, best_feature_index] == best_feature]
            sub_X = X[X[:, best_feature_index] == best_feature]
            sub_X = np.delete(sub_X, best_feature_index, 1) # 刪除最佳特征列

            sub_T[best_feature+"__"+str(len(sub_X))] = self._built_tree(sub_X, sub_y, depth+1) # 關鍵代碼

        T[self.feature_names[best_feature_index]+"__"+str(len(X))] = sub_T # 關鍵代碼

        return T

    def predict(self, x, tree=None):
        if x.ndim == 2:
            res = []
            for x_ in x:

                res.append(self.predict(x_))
            return res

        if not tree:
            tree = self.tree

        tree_key = list(tree.keys())[0]

        x_feature = tree_key.split("__")[0]

        x_index = self.feature_names.index(x_feature) # 從列表中定位索引
        x_tree = tree[tree_key]
        for key in x_tree.keys():
            if key.split("__")[0] == x[x_index]:
                tree_key = key
                x_tree = x_tree[tree_key]

        if type(x_tree) == dict:
            return self.predict(x, x_tree)
        else:
            return x_tree


if __name__ == "__main__":
    df = pd.read_csv("/mnt/hgfs/Ubuntu/統計學習方法筆記/CH05/Input/mdata_5-1.txt", index_col=0)

    cols = df.columns.tolist()  # 特征名稱列表
    X = df[cols[:-1]].values
    y = df[cols[-1]].values
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
    clf = DecisionTree(feature_names=cols, eps=0.03, depth=5, min_leaf_size=1, method='gain')
    clf.fit(X_train, y_train)

    print(clf.tree)



    print(cols)
    # print(X_test)
    print("Result:")
    print("predict:")
    print(clf.predict(X_test))
    print("test:")
    print(y_test)

```

C4.5

C4.5就是講ID3中的信息增益換為信息增益比。

決策樹的減枝

由於決策樹生成后有過擬合現象,缺乏泛化能力,因此需要進行減枝達到全局最優。
減枝通過極小化決策樹整體的損失函數或代價函數來實現。 | T | 表示樹 T 的葉節點個數。t為一個葉節點,該葉節點上有 N t 個樣本,其中k類樣本點有 N t k 個。 H t ( T ) 為葉節點t上的經驗熵。則損失函數可以定義為

C α = t = 1 | T | N t H t ( T ) + α | T | = C ( T ) + α | T |

D t ( T ) = k N t k N t log N t k N t

C(T)表示模型對訓練數據的預測誤差,即模型與訓練數據的擬合程度,|T|表示模型復雜度, α 用來控制占比。

減枝算法

輸入:樹T, α
輸出:修建好的樹 T α
(1)計算每個節點的經驗熵
(2)遞歸的從樹的葉節點向上回縮
設一組葉節點回縮前后對應的損失函數為 C α ( T B ) C α ( T A ) 。如果 C α ( T A ) C α ( T B ) 則進行減枝,將父節點變為新的葉節點。
(3)返回(2)。直至不能繼續為止。

CART算法

CART假定決策樹是二叉樹,內部節點特征的取值為“是”或“否”。也包含決策樹的生成與減枝兩個過程。回歸樹用平方誤差最小化准則,分類樹用基尼指數最小化准則。

CART回歸樹的生成

回歸樹將輸入空間(特征空間)划分為多個單元,每個單元有一個固定的輸出值 c m ,當輸入空間的划分確定時,可以用平方誤差 x i R m 來表示回歸樹的預測誤差。單元輸出最優值 c ^ m 是單元上所有實例輸出值的平均值。則問題可以變為使用啟發式的方法確定切分變量以及切分點,使誤差最小。可以固定一個輸入變量,然后啟發式的尋找最優切分點,通過這樣的方法遍歷所有輸入變量,確定最優的切分變量與切分點。

最小二乘回歸樹生成算法

輸入:訓練數據集D
輸出:回歸樹
在訓練數據集上遞歸的將每個區域划分為兩個子區域並決定每個子區域的輸出值,構建二叉決策樹:
(1)選擇最優切變量與切分點。遍歷變量,對固定的切分變量掃描切分點,選擇最優結果。
(2)通過選擇的最優結果划分數據區域並確定相應的輸出值。
(3)連續對兩個子區域調用(1)(2),直至滿足停止條件。
(4)得到決策樹

 
        
 
        

CART分類樹的生成

首先介紹基尼指數:分類問題中,假設有K個類,樣本屬於第k類的概率為 p k ,則概率分布的基尼指數為

G i n i ( p ) = k = 1 K p k ( 1 p k ) = 1 k = 1 K p k 2

對於給定樣本集其基尼指數為
G i n i ( D ) = 1 k = 1 K ( | C k | | D | ) 2

在特征A的條件下,集合D的基尼指數為
G i n i ( D , A ) = | D 1 | | D | G i n i ( D 1 ) + | D 2 | | D | G i n i ( D 2 )

基尼指數可以表示數據的不確定性,指數越大表示集合的不確定性越大。

基尼指數分類樹生成算法

輸入:訓練數據集,停止條件
輸出:分類樹
從根節點開始,遞歸的對每個節點進行一下操作,構建二叉決策樹。
(1)計算所有特征的所有可能取值的基尼指數
(2)選擇最優特征與最優取值,將數據集切分為兩個子數據集
(3)遞歸調用(1)(2)直到滿足停止條件
(4)生成CART分類樹
停止條件為:節點中的樣本個數小於預定閾值,或樣本集的基尼指數小於閾值,或沒有更多的特征。


CART減枝

CART減枝步驟:首先從生成算法產生的決策樹 T 0 底端開始不斷減枝,直到 T 0 的根節點,形成子樹序列 T 0 , T 1 , . . . , T n ;然后通過交叉驗證算法在獨立的驗證數據集上對子樹進行測試,從中選擇最優子樹。
減枝過程中損失函數為 C α ( T ) = C ( T ) + α | T | C ( T ) 可以是基尼指數或是誤差平方和。當 α 固定時,最優子樹唯一。可以通過將 α 從小增大,確定一系列 T 0 , T 1 , . . . , T n ,然后從中通過交叉驗證選擇最優子樹。具體可以參看Breiman算法。
在對 T 0 中每一內部節點t,計算

g ( t ) = C ( t ) C ( T t ) | T t | 1

C ( t ) 為以t為單節點樹的損失函數, C ( T t ) 為以t為根節點的子樹 T t 的損失函數。 g ( t ) 表示減枝后損失函數減少的程度。在 T 0 中減去 g ( t ) 最小的 T t ,將得到的子樹作為 T 1 ,同時將最小的 g ( t ) 設為 α 1 T 1 為區間 [ α 1 , α 2 ) 的最優子樹。不斷增加 α ,產生新的區間。
然后利用獨立的驗證數據,交叉驗證子樹序列 T 0 , T 1 , . . . , T n ,平方誤差或基尼指數最小的決策樹被認為是最優的。

CART減枝算法

輸入:原完整的生成樹
輸出:減枝后的生成樹
(1)設 k = 0 , T = T 0
(2)設 α inf
(3)自上而下的對各內部節點t計算

g ( t ) = C ( t ) C ( T t ) | T t | 1

α = min ( α , g ( t ) )

(4)對 g ( t ) = α 的內部節點t進行減枝,並對節點t以多數表決法確定類,得T。
(5)k=k+1, α k = α T k = T
(6)如果 T k 不是右根節點及其兩個葉子組成的樹,則回到(3),否則令 T k = T n
(7)交叉驗證子樹序列,得到最優子樹

CRAT

'''
Implement the decision_tree to adjust more than 1 dimension
'''

import  numpy as np
import  matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

class DecisionTreeRegression():
    def __init__(self, depth = 5, min_leaf_size = 5):
        self.depth = depth
        self.min_leaf_size = min_leaf_size
        self.left = None
        self.right = None
        self.prediction = None

        self.j = None
        self.s = None


    def mean_squared_error(self, labels, prediction):

        if labels.ndim != 1:
            print("Error: Input labels must be one dimensional")

        return np.mean((labels - prediction) ** 2)

    def train(self, X, y):
        if X.ndim == 1:
            X = X.reshape(-1, 1)

        if y.ndim != 1:
            print("Error: Data set labels must be one dimensional")
            return

        #控制最小葉子
        if len(X) < 2 * self.min_leaf_size:
            self.prediction = np.mean(y)
            return

        #深度為一
        if self.depth == 1:
            self.prediction = np.mean(y)
            return

        j, s, best_split, _ = self.split(X, y)

        if s != None:
            left_X = X[:best_split]
            left_y = y[:best_split]
            right_X = X[best_split:]
            right_y = y[best_split:]

            self.s = s
            self.j = j
            self.left = DecisionTreeRegression(depth = self.depth - 1, min_leaf_size = self.min_leaf_size)
            self.right = DecisionTreeRegression(depth = self.depth - 1, min_leaf_size = self.min_leaf_size)

            #左右遞歸
            self.left.train(left_X, left_y)
            self.right.train(right_X, right_y)

        else:
            self.prediction = np.mean(y)

        # 出口
        return

    def squaErr(self, X, y, j, s):
        mask_left = X[:, j] < s
        mask_right = X[:, j] >= s
        X_left = X[mask_left, j]
        X_right = X[mask_right, j]

        c_left = np.mean(y[mask_left])
        c_right = np.mean((y[mask_right]))

        error_left = np.sum((X_left - c_left) ** 2)
        error_right = np.sum((X_right - c_right) ** 2)
        return error_left + error_right


    def split(self, X, y):
        min_j = 0
        min_error = np.inf

        for j in range(len(X[0])):
            X_sorted = np.sort(X[:, min_j])  # 不改變X
            slice_value = (X_sorted[1:] + X_sorted[:-1]) / 2
            min_s = X[0, min_j] + X[-1, min_j]
            min_s_index = slice_value[0]
            for s_index in range(len(slice_value)):
                error = self.squaErr(X, y, j, slice_value[s_index])
                if error < min_error:
                    min_error = error
                    min_j = j
                    min_s = slice_value[s_index]
                    min_s_index = s_index

            return  min_j, min_s, s_index, min_error

    def predict(self, x):
        # to avoid a number
        if x.ndim == 0:
            x = np.array([x])

        if self.prediction is not None:

            return self.prediction

        elif self.left or self.right is not None:

            if x[self.j] >= self.s:
                return self.right.predict(x)
            else:
                return self.left.predict(x)
        else:
            print("Error: Decision tree not yet trained")
            return None

def main():

    X = np.array([[1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 6], [7, 7], [8, 8], [9, 9], [10, 10]])
    y = np.array([5.56, 5.70, 5.91, 6.40, 6.80, 7.05, 8.90, 8.70, 9.00, 9.05])



    tree = DecisionTreeRegression(depth = 5, min_leaf_size = 2)
    tree.train(X,y)


    test_cases = np.array([np.arange(0.0, 10.0, 0.01), np.arange(0.0, 10.0, 0.01)]).T
    predictions = np.array([tree.predict(x) for x in test_cases])

    #繪圖
    fig = plt.figure(figsize=(10, 8))
    ax = fig.add_subplot(111, projection="3d")
    ax.scatter(X[:, 0], X[:, 1], y, s=20, edgecolor="black",
               c="darkorange", label="data")
    ax.plot(test_cases[:, 0], test_cases[:, 1], predictions, c='r')

    # plt.scatter(X[:, 0], y, s=20, edgecolor="black", c="darkorange", label="data")
    # plt.plot(test_cases[:, 1], predictions, c="r")

    plt.show()



if __name__ == '__main__':
    main()

對比

  1. ID3與C4.5區別:
    ID3采用信息增益進行特征選擇,C4.5采用信息增益比進行特征選擇。信息增益存在偏向於選擇取值較多的特征。

  2. ID3,C4.5為多叉樹,CART為二叉樹。

  3. CART在一條通路上可以對一個特征進行多次划分,ID3或C4.5在一個通路上只能對一個特征進行一次划分。

  4. ID3,C4.5無法直接處理離散問題,CART可以直接處理離散問題。

  5. 信息增益、基尼指數、方差都能表示數據的不確定性。
    信息增益通常用在有限離散特征有限離散類型的情況,即特征離散的分類情況,信息增益需要各個特征概率的極大似然估計以及類型概率的極大似然估計。
    基尼指數用於有限離散類型的情況,即特征連續或離散的分類情況。基尼指數只需要知道類型概率的極大似然估計。
    方差不根據分布概率確定不確定性,方差根據特征的取值情況度量信息的離散程度。需要知道具體的值 。可以用於連續型數據。用於KDTree選擇切分特征,在主成分分析(PCA)對數據進行降維降噪。

  6. 減枝區別
    CART減枝的方法為交叉驗證,需要測試數據集,不需要事先確定好 α 值的大小, T 0 , T 1 , . . . , T n 由訓練數據集生成。
    ID3,C4.5需要事先確定 α 值的大小,但是不需要測試數據。
    兩種減枝方法可以互用。


免責聲明!

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



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