機器學習-初級入門(分類算法-決策樹)

一、信息增益

  1. 在信息論中,熵(entropy)是隨機變量不確定性的度量,也就是熵越大,則隨機變量的不確定性越大。設X是一個取有限個值得離散隨機變量,其概率分佈爲:
    在這裏插入圖片描述
    則隨機變量X的熵定義爲:
    在這裏插入圖片描述

  2. 條件熵

    設有隨機變量(X, Y),其聯合概率分佈爲:
    在這裏插入圖片描述
    條件熵H(Y|X)表示在已知隨機變量X的條件下,隨機變量Y的不確定性。隨機變量X給定的條件下隨機變量Y的條件熵H(Y|X),定義爲X給定條件下Y的條件概率分佈的熵對X的數學期望:
    在這裏插入圖片描述
    當熵和條件熵中的概率由數據估計得到時(如極大似然估計),所對應的熵與條件熵分別稱爲經驗熵和經驗條件熵。

  3. 信息增益

    信息增益表示由於得知特徵A的信息後兒時的數據集D的分類不確定性減少的程度,定義爲:
    在這裏插入圖片描述
    即集合D的經驗熵H(D)與特徵A給定條件下D的經驗條件熵H(H|A)之差。

    理解:選擇劃分後信息增益大的作爲劃分特徵,說明使用該特徵後劃分得到的子集純度越高,即不確定性越小。因此我們總是選擇當前使得信息增益最大的特徵來劃分數據集。
    缺點:信息增益偏向取值較多的特徵(原因:當特徵的取值較多時,根據此特徵劃分更容易得到純度更高的子集,因此劃分後的熵更低,即不確定性更低,因此信息增益更大)
    

二、ID3算法

  1. 輸入輸出

    輸入:訓練數據集D,特徵集A,閾值ε
    輸出:決策樹T

  2. 算法實現步驟

    Step1:若D中所有實例屬於同一類CkC_k,則T爲單結點樹,並將CkC_k類作爲該節點的類標記,返回T;

    Step2:若A=Ø,則T爲單結點樹,並將D中實例數最大的類CkC_k作爲該節點的類標記,返回T;

    Step3:否則,計算A中個特徵對D的信息增益,選擇信息增益最大的特徵AkA_k

    Step4:如果AgA_g的信息增益小於閾值ε,則T爲單節點樹,並將D中實例數最大的類CkC_k作爲該節點的類標記,返回T

    Step5:否則,對AgA_g的每一種可能值aia_i,依AgA_g=aia_i將D分割爲若干非空子集DiD_i,將DiD_i中實例數最大的類作爲標記,構建子結點,由結點及其子樹構成樹T,返回T;

    Step6:對第i個子節點,以DiD_i爲訓練集,以A - {AgA_g}爲特徵集合,遞歸調用Step1~step5,得到子樹TiT_i,返回TiT_i

三、 C4.5算法

  1. 原理
    C4.5算法與ID3算法很相似,C4.5算法是對ID3算法做了改進,在生成決策樹過程中採用信息增益比來選擇特徵。

  2. 定義
    我們知道信息增益會偏向取值較多的特徵,使用信息增益比可以對這一問題進行校正。

    定義:特徵A對訓練數據集D的信息增益比GainRatio(D,A)定義爲其信息增益Gain(D,A)與訓練數據集D的經驗熵H(D)之比:
    在這裏插入圖片描述

  3. 算法實現步驟

    C4.5算法過程跟ID3算法一樣,只是選擇特徵的方法由信息增益改成信息增益比。

四、CART算法

  1. Gini指數

    分類問題中,假設有K個類,樣本點屬於第k類的概率爲PkP_k,則概率分佈的基尼指數定義爲:
    在這裏插入圖片描述
    PkP_k表示選中的樣本屬於k類別的概率,則這個樣本被分錯的概率爲(1 - PkP_k)對於給定的樣本集合D,其基尼指數爲:
    在這裏插入圖片描述
    這裏CkC_k是D中屬於第k類的樣本個數,K是類的個數。
    如果樣本集合D根據特徵A是否取某一可能值a被分割成D1和D2兩部分,即:
    在這裏插入圖片描述
    則在特徵A的條件下,集合D的基尼指數定義爲:
    在這裏插入圖片描述
    基尼指數Gini(D)表示集合D的不確定性,基尼指數Gini(D,A)表示經A=a分割後集合D的不確定性。基尼指數值越大,樣本集合的不確定性也就越大,這一點跟熵相似。

  2. 舉例計算Gini係數

    一個包含30個學生的樣本,其包含三種特徵,分別是:性別(男/女)、班級(IX/X)和高度(5到6ft)。其中30個學生裏面有15個學生喜歡在閒暇時間玩板球。那麼要如何選擇第一個要劃分的特徵呢,我們通過上面的公式來進行計算。
    在這裏插入圖片描述
    在這裏插入圖片描述
    Gini(D,Gender)最小,所以選擇性別作爲最優特徵。

  3. CART算法步驟
    輸入輸出:

    輸入:訓練數據集D,停止計算的條件
    輸出:CART決策樹
    

    實現步驟:

    根據訓練數據集,從根結點開始,遞歸地對每個結點進行以下操作,構建二叉樹:

    Step1:設結點的訓練數據集爲D,計算現有特徵對該數據集的基尼指數。此時,對每一個特徵A,對其可能取的每個值a,根據樣本點A=a的測試爲“是”或“否”將D分割爲D1和D2兩部分,利用上式Gini(D,A)來計算A=a時的基尼指數。

    Step2:在所有可能的特徵A以及他們所有可能的切分點a中,選擇基尼指數最小的特徵及其對應可能的切分點作爲最有特徵與最優切分點。依最優特徵與最有切分點,從現結點生成兩個子節點,將訓練數據集依特徵分配到兩個子節點中去。

    Step3:對兩個子結點遞歸地調用Step1、Step2,直至滿足條件。

    Step4:生成CART決策樹。

    算法停止計算的條件是節點中的樣本個數小於預定閾值,或樣本集的基尼指數小於預定閾值,或者沒有更多特徵。

五、實現CART算法代碼(別的分割函數可以此爲類)

  1. 數據

    User ID,Gender,Age,EstimatedSalary,Purchased
    15624510,Male,19,19000,0
    15810944,Male,35,20000,0
    15668575,Female,26,43000,0
    15603246,Female,27,57000,0
    15804002,Male,19,76000,0
    15728773,Male,27,58000,0
    15598044,Female,27,84000,0
    15694829,Female,32,150000,1
    15600575,Male,25,33000,0
    15727311,Female,35,65000,0
    ....
    
  2. 代碼實現

    import numpy as np
    import pandas as pd
    from collections import Counter
    from sklearn.metrics import confusion_matrix, accuracy_score	
    
    def dataset_format(filename):
        """
        劃分訓練測試集
        :param filename:文件路徑
        :return:
        """
        dataset = pd.read_csv(filename)
        train_data = dataset.sample(frac=0.75, random_state=1, axis=0)
        test_data = dataset[~dataset.index.isin(train_data.index)]
        X_train = train_data.iloc[:, 1:-1].values
        y_train = train_data.iloc[:, -1].values
        X_test = test_data.iloc[:, 1:-1].values
        y_test = test_data.iloc[:, -1].values
        return X_train, y_train, X_test, y_test
    
    
    def one_hot_pre(X, n_c):
        """
    
        :param X: 特徵向量
        :param n_c: 第n列要進行one_hot
        :return: 進行one_hot後的數據
        """
        lenth_x, f = X.shape
        catch_encode = dict()  # 記錄編碼對應類別
        data = X[:, n_c]
        data_set = list(set(data))
        lenth_class = len(data_set)
        if lenth_class == 1:
            X[:, n_c] = 1
            return X
        for index in range(lenth_class):
            catch_encode[index] = data_set[index]
        temp_m = np.zeros((lenth_x, lenth_class))
        for k, v in catch_encode.items():
            index = np.where(X == v)
            temp_m[index[0], k] = 1
        temp_m = temp_m[:, 1:]
        for i in range(lenth_class - 1):
            X = np.insert(X, n_c + i, values=temp_m[:, i], axis=1)
        X = np.delete(X, n_c + lenth_class - 1, axis=1)
        return X
    
    def gini(y):
        """
        :param y:
        :return:
        """
        lenth = y.shape[0]
        counter = Counter(y)  # 計數字典
        res = 0.0
        for num in counter.values():
            p = num / lenth
            res += p ** 2
        return 1 - res
    
    class Tree(object):
        """
        二叉樹
        """
        def __init__(
                self,
                value=None,
                true_branch=None,
                false_branch=None,
                results=None,
                col=-1,
                summary=None,
                data=None
        ):
            """
            :param value: 節點劃分數據
            :param true_branch: 左分支
            :param false_branch: 右分支
            :param results: 葉子節點的分類結果
            :param col: 對數據某一列特徵分割
            :param summary: 簡要
            :param data: 葉節點數據
            """
            self.value = value
            self.true_branch = true_branch
            self.false_branch = false_branch
            self.results = results,
            self.col = col
            self.summary = summary
            self.data = data
    
        def __str__(self):
            # print(self.col, self.value)
            # print(self.results)
            # print(self.summary)
            return ""
    
    
    def split_datas(X, y, feature_value, feature_index):
        """
        :param X: 待分割特徵
        :param y: 待分割標籤
        :param feature_value: 分割值
        :param feature_index: 分割列序號
        :return: 分割成左右分支
        """
        temp = X[:, feature_index]
        if isinstance(feature_value, int) or isinstance(feature_value, float):
            right_list = (X[temp > feature_value], y[temp > feature_value])
            left_list = (X[temp <= feature_value], y[temp <= feature_value])
        else:
            right_list = (X[temp == feature_value], y[temp == feature_value])
            left_list = (X[temp != feature_value], y[temp != feature_value])
        return right_list, left_list
    
    
    def build_decision_tree(X, y, evaluation_function=gini):
        """
        構建二叉樹的過程
        :param X: 訓練集特徵數據
        :param y: 訓練集標籤
        :param evaluation_function: 選擇分割函數
        :return: 訓練好的二叉樹
        """
        m, n = X.shape
        best_gain = 0.0
        best_val = None
        best_set = None
        current_gain = evaluation_function(y)
        for feature_index in range(n):
            feature_set = set(X[:, feature_index])
            for feature_value in feature_set:
                right_split, left_split = split_datas(X, y, feature_value, feature_index)
                p = right_split[0].shape[0] / m
                gini_d_a = p * evaluation_function(right_split[1]) + (1 - p) * evaluation_function(left_split[1])
                gain = current_gain - gini_d_a
                if gain > best_gain:
                    best_gain = gain
                    best_val = (feature_index, feature_value)
                    best_set = (right_split, left_split)
        dc_y = {
            'impurity': '%.3f' % current_gain,
            'sample': '%d' % m
        }
        if best_gain > 0:
            true_branch = build_decision_tree(best_set[0][0], best_set[0][1], evaluation_function)
            false_branch = build_decision_tree(best_set[1][0], best_set[1][1], evaluation_function)
            return Tree(col=best_val[0], value=best_val[1], true_branch=true_branch, false_branch=false_branch, summary=dc_y)
        else:
            return Tree(results=Counter(y), summary=dc_y, data=(X, y))
    
    
    def classify(data, tree):
        if tree.results[0]:
            return tree.results[0]
        else:
            v = data[tree.col]
            if isinstance(v, int) or isinstance(v, float):
                if v >= tree.value:
                    branch = tree.true_branch
                else:
                    branch = tree.false_branch
            else:
                if v == tree.value:
                    branch = tree.true_branch
                else:
                    branch = tree.false_branch
            return classify(data, branch)
    
    
    def prune(tree, mini_gain, evaluation_function=gini):
        """
        剪支
        :param tree:
        :param mini_gain:
        :param evaluation_function:
        :return:
        """
        if not tree.true_branch.results[0]:
            prune(tree.true_branch, mini_gain, evaluation_function)
        if not tree.false_branch.results[0]:
            prune(tree.false_branch, mini_gain, evaluation_function)
        if tree.true_branch.results[0] and tree.false_branch.results[0]:
            true_branch_lenth = tree.true_branch.data[0].shape[0]
            false_branch_lenth = tree.false_branch.data[0].shape[0]
            p = true_branch_lenth / (true_branch_lenth + false_branch_lenth)
            conn_true_false = np.concatenate((tree.true_branch.data[1], tree.false_branch.data[1]), axis=0)
            true_false_branch_gini = evaluation_function(conn_true_false)
            true_branch_gini = evaluation_function(tree.true_branch.data[1])
            false_branch_gini = evaluation_function(tree.false_branch.data[1])
            gain = true_false_branch_gini - p * true_branch_gini - (1 - p) * false_branch_gini
            if gain < mini_gain:
                # 當節點的gain小於給定的 mini Gain時則合併這兩個節點
                tree.data = (
                    np.concatenate((tree.true_branch.data[0], tree.false_branch.data[0]), axis=0),
                    np.concatenate((tree.true_branch.data[1], tree.false_branch.data[1]), axis=0)
                )
                tree.results = Counter(tree.data[1])
                tree.true_branch = None
                tree.false_branch = None
    
    
    def peride(X, model):
        m, n = X.shape
        y_hat = np.zeros((m, 1))
        for i in range(m):
            try:
                y_hat[i] = list(classify(X[i, :], model).keys())
            except Exception:
                pass
        return y_hat
    
    
    decistion_tree = build_decision_tree(X_train, y_train, evaluation_function=gini)
    # prune(decistion_tree, 0.4)
    y_hat = peride(X_test, decistion_tree)
    cm = confusion_matrix(y_test, y_hat)
    accuracy = accuracy_score(y_test, y_hat)
    print("混淆矩陣:\n", cm)
    print("準確度:\n", accuracy)
    

    輸出結果:

    混淆矩陣:
     [[56  7]
     [ 6 31]]
    準確度:
     0.87
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章