Chapter 5 決策樹算法

決策樹

決策樹定義:分類決策樹模型是一種描述實例進行分類的樹形結構,決策樹由節點和有向邊組成,結點有兩種類型:內部結點和葉結點。內部結點表示一個特徵或屬性,葉結點表示一個類。

決策樹示意圖如下

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dsYLzb8Y-1574596094495)(./imgs/決策樹.png)]

​   其中,紅色結點表示葉子結點(最終的分類情況),綠色節點表示分類的規則。決策樹學習,是在給訓練數據集的情況下,構建一個決策樹模型,使它能夠對實例進行正確的分類。而決策樹的本質是從訓練集中歸納出一組分類規則,與訓練數據不相互矛盾的決策樹(即能對訓練數據進行正確分類的決策樹)可能有多個,也可能一個也沒有。我們需要的是一個與訓練數據矛盾較小的決策樹,同時具有橫好的泛化能力(對於未知數據的預測能力)。決策樹的學習主要分爲以下三個步驟:

1. 特徵選擇

目的:如果利用一個特徵進行分類的結果與隨機分類的結果沒有很大差別,則稱這個特徵是沒有分類能力的。特徵選擇在於選取對訓練數據具有分類能力的特徵,這樣能夠提高決策樹學習的效率。通常的選擇標準爲信息增益和信息增益比。

信息增益

問題描述:以下兩個決策樹都可以從此延續下去,那麼究竟哪一個特徵具有更好的分類能力,

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zSaZIaRs-1574596094496)(./imgs/決策.png)]

直觀上如果一個特徵具有更好的分類能力,或者說,按照這一特徵將訓練數據集分割成子集,使得各個自己在當前條件下有最好的分類,那麼就選擇這個特徵。信息增益就能夠很好的表示這一直觀的準則。

:表示隨機變量的不確定性的度量,公式如下:
H(p)=H(X)=i=1npilogpi H(p)=H(X)=-\sum_{i=1}^{n} p_{i} \log p_{i}
熵只與XX的分佈有關,與XX取值無關,這句注意理解,定義0log0=00\log0=0,熵是非負的。

條件熵:

隨機變量(X,Y)(X,Y)的聯合概率分佈爲

P(X=xi,Y=yj)=pij,i=1,2,,n;j=1,2,,mP(X=x_i,Y=y_j)=p_{ij}, i=1,2,\dots ,n;j=1,2,\dots ,m

條件熵H(YX)H(Y|X)表示在已知隨機變量XX的條件下隨機變量YY的不確定性
H(YX)=i=1npiH(YX=xi) H(Y|X)=\sum_{i=1}^np_iH(Y|X=x_i)
其中pi=P(X=xi),i=1,2,,np_i=P(X=x_i),i=1,2,\dots ,n

經驗熵,經驗條件熵:當熵和條件熵中的概率由數據估計(特別是極大似然估計)得到時,所對應的熵與條件熵分別稱爲經驗熵和經驗條件熵,就是從已知的數據計算得到的結果。

信息增益:

特徵AA對訓練數據集DD的信息增益g(DA)g(D|A),定義爲集合DD的經驗熵H(D)H(D)與特徵AA給定的條件下DD的經驗條件熵H(DA)H(D|A)之差
g(D,A)=H(D)H(DA) g(D,A)=H(D)-H(D|A)

熵與條件熵的差稱爲互信息.

決策樹中的信息增益等價於訓練數據集中的類與特徵的互信息。

考慮ID這種特徵, 本身是唯一的。按照ID做劃分, 得到的經驗條件熵爲0, 會得到最大的信息增益。所以, 按照信息增益的準則來選擇特徵, 可能會傾向於取值比較多的特徵。

例1:

數據集:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
import math
from math import log
import pprint

# 書上題目5.1
def create_data():
    datasets = [['青年', '否', '否', '一般', '否'],
               ['青年', '否', '否', '好', '否'],
               ['青年', '是', '否', '好', '是'],
               ['青年', '是', '是', '一般', '是'],
               ['青年', '否', '否', '一般', '否'],
               ['中年', '否', '否', '一般', '否'],
               ['中年', '否', '否', '好', '否'],
               ['中年', '是', '是', '好', '是'],
               ['中年', '否', '是', '非常好', '是'],
               ['中年', '否', '是', '非常好', '是'],
               ['老年', '否', '是', '非常好', '是'],
               ['老年', '否', '是', '好', '是'],
               ['老年', '是', '否', '好', '是'],
               ['老年', '是', '否', '非常好', '是'],
               ['老年', '否', '否', '一般', '否'],
               ]
    labels = [u'年齡', u'有工作', u'有自己的房子', u'信貸情況', u'類別']
    # 返回數據集和每個維度的名稱
    return datasets, labels

原始數據:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CDWdUT67-1574596094497)(./imgs/數據.png)]

數據處理代碼:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
import math
from math import log
import pprint#頭文件

#計算熵

def calc_ent(datasets):
    data_length = len(datasets)
    label_count = {}
    for i in range(data_length):
        label = datasets[i][-1]
        if label not in label_count:
            label_count[label] = 0
        label_count[label] += 1
    ent = -sum([(p / data_length) * log(p / data_length, 2)
                for p in label_count.values()])
    return ent

#計算經驗條件熵
def cond_ent(datasets, axis=0):
    data_length = len(datasets)
    feature_sets = {}
    for i in range(data_length):
        feature = datasets[i][axis]#遍歷每一行數據 以每一列特徵爲標準
        if feature not in feature_sets:
            feature_sets[feature] = []
        feature_sets[feature].append(datasets[i])#取得每一個子數據集
    cond_ent = sum(
        [(len(p) / data_length) * calc_ent(p) for p in feature_sets.values()])
    return cond_ent

#計算信息增益
def info_gain(ent, cond_ent):
    return ent - cond_ent

#遍歷數據。計算第一次整體信息增益
def info_gain_train(datasets):
    count = len(datasets[0]) - 1 #總體的特徵
    ent = calc_ent(datasets)
    
    best_feture = []
    
    for c in range(count):
        c_info_gain = info_gain(ent , cond_ent(datasets, axis = c))
        best_feture.append((c, c_info_gain))
        print("特徵:%s, 信息增益%f" %(labels[c], c_info_gain))
        
    best_ = max(best_feture, key=lambda x : x[-1])
    
    return  '特徵({})的信息增益最大,選擇爲根節點特徵'.format(labels[best_[0]])
    
  
if __name__ == "__main__":
    datasets, labels = create_data()
    info_gain_train(np.array(datasets))

代碼結果:

特徵:年齡, 信息增益0.085491
特徵:有工作, 信息增益0.333333
特徵:有自己的房子, 信息增益0.432538
特徵:信貸情況, 信息增益0.373850
'特徵(有自己的房子)的信息增益最大,選擇爲根節點特徵'

信息增益缺點:

以信息增益作爲劃分訓練數據集的特徵,存在偏向與選擇取值比較多的特徵的問題。使用信息增益比可以對這一問題進行校正,這也是特徵選擇的另一個準則。

信息增益比:
gR(D,A)=g(D,A)HA(D)HA(D)=i=1nDiDlog2DiD \begin{array}{c}{g_{R}(D, A)=\frac{g(D, A)}{H_{A}(D)}} \\ {H_{A}(D)=-\sum_{i=1}^{n} \frac{D_{i}}{D} \log _{2} \frac{D_{i}}{D}}\end{array}

2. 決策樹的生成

ID3算法

輸入:訓練數據集DD, 特徵集AA,閾值ϵ\epsilon

輸出:決策樹TT

  1. 如果DD屬於同一類CkC_kTT爲單節點樹,類CkC_k作爲該節點的類標記,返回TT
  2. 如果AA是空集,置TT爲單節點樹,實例數最多的類作爲該節點類標記,返回TT(注:也即沒有特徵可以在劃分)
  3. 計算gg, 選擇信息增益最大的特徵AgA_g
  4. 如果AgA_g的信息增益小於ϵ\epsilonTT爲單節點樹,DD中實例數最大的類CkC_k作爲類標記,返回TT
  5. AgA_g劃分若干非空子集DiD_i
  6. DiD_i訓練集,AAgA-A_g爲特徵集,遞歸調用前面步驟,得到TiT_i,返回TiT_i(注:已經使用過的特徵將不在繼續下面結點的特徵分裂)

代碼

# 定義節點類 二叉樹
class Node:
    def __init__(self, root=True, label=None, feature_name=None, feature=None):
        self.root = root
        self.label = label
        self.feature_name = feature_name
        self.feature = feature
        self.tree = {}
        self.result = {
            'label:': self.label,
            'feature': self.feature,
            'tree': self.tree
        }

    def __repr__(self):
        return '{}'.format(self.result)

    def add_node(self, val, node):
        self.tree[val] = node

    def predict(self, features):
        if self.root is True:
            return self.label
        return self.tree[features[self.feature]].predict(features)
        
class DTree:
    def __init__(self, epsilon=0.1):
        self.epsilon = epsilon
        self._tree = {}

    # 熵
    @staticmethod
    def calc_ent(datasets):
        data_length = len(datasets)
        label_count = {}
        for i in range(data_length):
            label = datasets[i][-1]
            if label not in label_count:
                label_count[label] = 0
            label_count[label] += 1
        ent = -sum([(p / data_length) * log(p / data_length, 2)
                    for p in label_count.values()])
        return ent

    # 經驗條件熵
    def cond_ent(self, datasets, axis=0):
        data_length = len(datasets)
        feature_sets = {}
        for i in range(data_length):
            feature = datasets[i][axis]
            if feature not in feature_sets:
                feature_sets[feature] = []
            feature_sets[feature].append(datasets[i])
        cond_ent = sum([(len(p) / data_length) * self.calc_ent(p)
                        for p in feature_sets.values()])
        return cond_ent

    # 信息增益
    @staticmethod
    def info_gain(ent, cond_ent):
        return ent - cond_ent

    def info_gain_train(self, datasets):
        count = len(datasets[0]) - 1
        ent = self.calc_ent(datasets)
        best_feature = []
        for c in range(count):
            c_info_gain = self.info_gain(ent, self.cond_ent(datasets, axis=c))
            best_feature.append((c, c_info_gain))
        # 比較大小
        best_ = max(best_feature, key=lambda x: x[-1])
        return best_
    
    
    def train(self, train_data):
        """
        數據集的收集爲pd
        """
        _, y_train, features = train_data.iloc[:,
                                               :-1], train_data.iloc[:,                                                     							-1],train_data.columns[:-1]
        
        #1.判斷是否單個節點
        if len(y_train.value_counts()) == 1:
            return Node(root = True, label= y_train.iloc[0])
        #2.是否爲空節點
        if len(features) == 0:
            return Node(root = True,
            label = y_train.value_counts().sort_values(
            ascending = False).index[0])

        #3.創建節點,選擇信息增益最大的特徵作爲節點
        #3.1得到信息增益最大特徵信息
        max_feature, max_info_gain = self.info_gain_train(np.array(train_data))
        max_feature_name = features[max_feature]
        
        #4.判斷是否小於閥值
        if max_info_gain < self.epsilon:
            return Node(root = True,
                       label=y_train.value_counts().sort_values(
                       ascending=False).index[0])
        #5.構建Ag子集
        node_tree = Node(
            root=False, feature_name=max_feature_name, feature=max_feature)

        feature_list = train_data[max_feature_name].value_counts().index
        for f in feature_list:
            sub_train_df = train_data.loc[train_data[max_feature_name] ==
                                          f].drop([max_feature_name], axis=1)
        
            # 6, 遞歸生成樹
            sub_tree = self.train(sub_train_df)
            node_tree.add_node(f, sub_tree)

        return node_tree
    
    def fit(self, train_data):
        self._tree = self.train(train_data)
        return self._tree
    def predict(self, X_test):
        return self._tree.predict(X_test)     
    
    
if __name__ == "__main__":
    datasets, labels = create_data()
    data_df = pd.DataFrame(datasets, columns=labels)
    dt = DTree()
    tree = dt.fit(data_df)
    print("tree:\n",tree)
    print("測試結果:", end="")
    print(dt.predict(["老年","否", "否", "一般"]))

ID3算法結果

tree:
{'label:': None, 'feature': 2, 'tree': {'否': {'label:': None, 'feature': 1, 'tree': {'否': {'label:': '否', 'feature': None, 'tree': {}}, '是': {'label:': '是', 'feature': None, 'tree': {}}}}, '是': {'label:': '是', 'feature': None, 'tree': {}}}}
測試結果:
'否'

ID3算法缺點

ID3算法只有樹的生成,所以該算法生成的樹容易過擬合。

C4.5 算法

輸入:訓練數據集DD, 特徵集AA,閾值ϵ\epsilon
輸出:決策樹TT

  1. 如果DD屬於同一類CkC_kTT爲單節點樹,類CkC_k作爲該節點的類標記,返回TT
  2. 如果AA是空集, 置TT爲單節點樹,實例數最多的作爲該節點類標記,返回TT
    1. 計算gg, 選擇信息增益比最大的特徵AgA_g
  3. 如果AgA_g信息增益比小於ϵ\epsilonTT爲單節點樹,DD中實例數最大的類CkC_k作爲類標記,返回TT
  4. AgA_g劃分若干非空子集DiD_i
  5. DiD_i訓練集,AAgA-A_g爲特徵集,遞歸調用前面步驟,得到TiT_i,返回TiT_i
    Tip: ID3和C4.5在生成上,差異只在準則的差異。

C4.5 算法代碼:

# 定義節點類 二叉樹
class Node:
    def __init__(self, root=True, label=None, feature_name=None, feature=None):
        self.root = root
        self.label = label
        self.feature_name = feature_name
        self.feature = feature
        self.tree = {}
        self.result = {
            'label:': self.label,
            'feature': self.feature,
            'tree': self.tree
        }

    def __repr__(self):
        return '{}'.format(self.result)

    def add_node(self, val, node):
        self.tree[val] = node

    def predict(self, features):
        if self.root is True:
            return self.label
        return self.tree[features[self.feature]].predict(features)

class DTree:
    def __init__(self, epsilon=0.1):
        self.epsilon = epsilon
        self._tree = {}

    # 熵
    @staticmethod
    def calc_ent(datasets):
        data_length = len(datasets)
        label_count = {}
        for i in range(data_length):
            label = datasets[i][-1]
            if label not in label_count:
                label_count[label] = 0
            label_count[label] += 1
        ent = -sum([(p / data_length) * log(p / data_length, 2)
                    for p in label_count.values()])
        return ent

    # 經驗條件熵
    def cond_ent(self, datasets, axis=0):
        data_length = len(datasets)
        feature_sets = {}
        for i in range(data_length):
            feature = datasets[i][axis]
            if feature not in feature_sets:
                feature_sets[feature] = []
            feature_sets[feature].append(datasets[i])
        cond_ent = sum([(len(p) / data_length) * self.calc_ent(p)
                        for p in feature_sets.values()])
        # 劃分特徵的信息熵(信息增益比的分母)
        sub_ent = -sum([len(p) / data_length * log(len(p)/data_length
                                                  ,2) for p in feature_sets.values()])
        return cond_ent, sub_ent

    # 信息增益
    @staticmethod
    def info_gain(ent, cond_ent, sub_ent):
        return (ent - cond_ent)/sub_ent

    def info_gain_train(self, datasets):
        count = len(datasets[0]) - 1
        ent = self.calc_ent(datasets)
        best_feature = []
        for c in range(count):
            print(c)
            cond_ent, sub_ent = self.cond_ent(datasets, axis=c)
            c_info_gain = self.info_gain(ent, cond_ent, sub_ent)
            best_feature.append((c, c_info_gain))
            print("特徵:%s, 信息增益%f" %(labels[c], c_info_gain))
        # 比較大小
        best_ = max(best_feature, key=lambda x: x[-1])
        
        return best_
    
    
    def train(self, train_data):
        """
        數據集的收集爲pd
        """
        _, y_train, features = train_data.iloc[:,
                                               :-1], train_data.iloc[:,
                                                                 -1],train_data.columns[:-1]
        
        #1.判斷是否單個節點
        if len(y_train.value_counts()) == 1:
            return Node(root = True, label= y_train.iloc[0])
        
        #2.是否爲空節點
        if len(features) == 0:
            return Node(root = True,
            label = y_train.value_counts().sort_values(
            ascending = False).index[0])
       
        #3.創建節點,選擇信息增益最大的特徵作爲節點
        #3.1得到信息增益最大特徵信息
        max_feature, max_info_gain = self.info_gain_train(np.array(train_data))
        max_feature_name = features[max_feature]
        
        #4.判斷是否小於閥值
        if max_info_gain < self.epsilon:
            return Node(root = True,
                       label=y_train.value_counts().sort_values(
                       ascending=False).index[0])
        #5.構建Ag子集
        node_tree = Node(
            root=False, feature_name=max_feature_name, feature=max_feature)

        feature_list = train_data[max_feature_name].value_counts().index
        print("feature_list :",feature_list)
        for f in feature_list:
            sub_train_df = train_data.loc[train_data[max_feature_name] ==
                                          f].drop([max_feature_name], axis=1)
        
            # 6, 遞歸生成樹
            sub_tree = self.train(sub_train_df)
            node_tree.add_node(f, sub_tree)

        return node_tree
    
    def fit(self, train_data):
        self._tree = self.train(train_data)
        return self._tree
    def predict(self, X_test):
        return self._tree.predict(X_test)
    
    
    if __name__ == "__main__":
    datasets, labels = create_data()
    data_df = pd.DataFrame(datasets, columns=labels)
    dt = DTree()
    tree = dt.fit(data_df)
    print("tree:\n",tree)
    print("測試結果:")
    dt.predict(["老年","否", "否", "一般"])

3.決策樹的剪枝

目的:由決策樹算法生成的決策樹,往往對於訓練數據的分類很準確,但是對於未知的測試數據的分類卻沒有那麼準確,即出現過擬合,過擬合的原因在於學習時過多的考慮如何提高訓練數據的正確分類,從而構建過於複雜的決策樹,所以要考慮決策樹的複雜度,對已生成的決策樹進行簡化。而通常決策樹的剪枝往往是通過減少損失函數來體現的。如下:

​ 樹TT的葉結點個數爲T|T|tt是樹TT的葉結點,該結點有NtN_t個樣本點,其中kk類的樣本點有NtkN_{tk}個,Ht(T)H_t(T)爲葉結點tt上的經驗熵, α0\alpha\geqslant 0爲參數,決策樹學習的損失函數可以定義爲
Cα(T)=i=1TNtHt(T)+αT C_\alpha(T)=\sum_{i=1}^{|T|}N_tH_t(T)+\alpha|T|
其中
Ht(T)=kNtkNtlogNtkNt H_t(T)=-\sum_k\color{red}\frac{N_{tk}}{N_t}\color{black}\log \frac{N_{tk}}{N_t}

C(T)=t=1TNtHt(T)=t=1Tk=1KNtklogNtkNt C(T)=\sum_{t=1}^{|T|}\color{red}N_tH_t(T)\color{black}=-\sum_{t=1}^{|T|}\sum_{k=1}^K\color{red}N_{tk}\color{black}\log\frac{N_{tk}}{N_t}

這時有
Cα(T)=C(T)+αT C_\alpha(T)=C(T)+\alpha|T|
其中C(T)C(T)表示模型對訓練數據的誤差,T|T|表示模型複雜度,參數α0\alpha \geqslant 0控制兩者之間的影響。

算法描述:

輸入:生成算法生成的整個樹TT,參數α\alpha

輸出:修剪後的子樹TαT_\alpha

  1. $ \alpha(T_A)\leqslant C_\alpha(T_B)$計算每個結點的經驗熵
  2. 遞歸的從樹的葉結點向上回縮
    假設一組葉結點回縮到其父結點之前與之後的整體樹分別是TBT_BTAT_A,其對應的損失函數分別是Cα(TA)C_\alpha(T_A)Cα(TB)C_\alpha(T_B),如果Cα(TA)Cα(TB)C_\alpha(T_A)\leqslant C_\alpha(T_B)則進行剪枝,即將父結點變爲新的葉結點
  3. 返回2,直至不能繼續爲止,得到損失函數最小的子樹TαT_\alpha

CART(重點)

迴歸樹

算法描述

輸入:訓練數據集DD

輸出:迴歸樹f(x)f(x)

步驟:

  1. 遍歷變量jj,對固定的切分變量jj掃描切分點ss,得到滿足上面關係的(j,s)(j,s)
    minj,s[minc1xiR1(j,s)(yic1)2+minc2xiR2(j,s)(yic2)2] \min\limits_{j,s}\left[\min\limits_{c_1}\sum\limits_{x_i\in R_1(j,s)}(y_i-c_1)^2+\min\limits_{c_2}\sum\limits_{x_i\in R_2(j,s)}(y_i-c_2)^2\right]

  2. 用選定的(j,s)(j,s), 劃分區域並決定相應的輸出值
    R1(j,s)={xx(j)s},R2(j,s)={xx(j)>s}c^m=1NxiRm(j,s)yj,xRm,m=1,2 R_1(j,s)=\{x|x^{(j)}\leq s\}, R_2(j,s)=\{x|x^{(j)}> s\} \\ \hat{c}_m= \frac{1}{N}\sum\limits_{x_i\in R_m(j,s)} y_j, x\in R_m, m=1,2

  3. 對兩個子區域調用(1)(2)步驟, 直至滿足停止條件

  4. 將輸入空間劃分爲MM個區域R1,R2,,RMR_1, R_2,\dots,R_M,生成決策樹:
    f(x)=m=1Mc^mI(xRm) f(x)=\sum_{m=1}^M\hat{c}_mI(x\in R_m)

代碼

from numpy import *


def loadDataset():
    """加載本地數據"""
    
    dataset = []
    f = open("regData.txt")
    fr = f.readlines()
    for line in fr:
        line = line.strip().split("\t")
        linef = [float(li) for li in line]
        dataset.append(linef)
#         print(dataset)
    dataSetMat = mat(dataset)
    
    return dataSetMat

def calcErr(dataSetMat):
    
    error = var(dataSetMat[:,-1])*shape(dataSetMat)[0]
    return error

def chooseBestFeatureval2split(dataSetMat):
    """選擇分類節點"""
    
    # 如果預測值相同
    if(len(set(dataSetMat[:, -1].T.tolist()[0])) == 1): return None, None
    
    bestFeature = 0
    bestValue = 0
    lowestErr = 100000
    totalErr = calcErr(dataSetMat)
    
    #  遍歷特徵
    for feature in range(shape(dataSetMat)[1] - 1):
        allValues = [d[feature] for d in dataSetMat.tolist()]
        values = set(allValues)  #每一個維度的特徵
        for value in values:
            leftChild, rightChild = splitByFeatVal(feature, value, dataSetMat)
            if(shape(leftChild)[0] == 0 or shape(rightChild)[0] == 0): continue
            curErr = calcErr(leftChild) + calcErr(rightChild)
            if (curErr < lowestErr):
                bestFeature = feature
                bestValue = value
                lowestErr = curErr
        
    if (totalErr - lowestErr < 1): return None, None # 防止過擬合,並且作爲遞歸的終止條件
    
    return bestFeature, bestValue

def splitByFeatVal(feature, value, dataSetMat):
    """分裂節點"""
    
    leftChild = dataSetMat[nonzero(dataSetMat[:, feature] > value)[0],:]
    rightChild = dataSetMat[nonzero(dataSetMat[:, feature] <= value)[0],:]
    
    return leftChild, rightChild

def createRegTree(dataSetMat):
    """創建迴歸樹"""
    
    feature, value = chooseBestFeatureval2split(dataSetMat)
    
    if feature == None: return mean(dataSetMat[:, -1])#返回最終的預測結果
    
    regTree = {}
    regTree["feature"] = feature
    regTree["value"] = value
    leftChild, rightChild = splitByFeatVal(feature, value, dataSetMat)
    regTree["leftChild"] = createRegTree(leftChild)
    regTree["rightChild"] = createRegTree(rightChild)
    
    return regTree

if __name__ == "__main__":
    dataSetMat = loadDataset()
    regTree = createRegTree(dataSetMat)
    print(regTree)

分類樹

算法描述

分類樹用基尼指數選擇最優特徵,同時決定該特徵的最優二值切分點。

基尼指數:分類問題中,假設有KK個類,樣本點屬於第KK類的概率爲pkp_k,則概率分佈的基尼指數定義爲:
Gini(p)=k=1Kpk(1pk)=1k=1Kpk2 Gini(p) = \sum_{k=1}^Kp_k(1-p_k)=1-\sum_{k=1}^Kp_k^2
分類樹算法描述:

輸入: 訓練數據集D,停止計算條件

輸出:CART 分類樹

1 訓練數據集爲DD,計算現有特徵對訓練數據集的基尼指數,此時對於每一個特徵A,對其可能取得每一個值a,根據此值將訓練樣本切分爲D1D1D2D2兩部分,然後根據上式計算A=a基尼指數
Gini(D,A)=D1DGini(D1)+D2DGini(D2) \operatorname{Gini}(D, A)=\frac{|D 1|}{|D|} \operatorname{Gini}(D 1)+\frac{|D 2|}{|D|} \operatorname{Gini}(D 2)
2 在所有可能的特徵以及所有可能的值裏面選擇基尼指數最小的特徵及其切分點作爲最優的特徵及切分點,從結點生成兩個子結點,將訓練數據集分配到子結點中去

3 遞歸的調用1 2 直到滿足停止的條件

4 生成分類決策樹

代碼

from numpy import *

def loadDataSet():
    """加載數據"""

    dataSet = [[0, 0, 0],
               [0, 1, 1],
               [0, 2, 0],
               [1, 0, 1],
               [1, 1, 1],
               [1, 2, 1],
               [2, 0, 0],
               [2, 1, 1],
               [2, 2, 0]]
    labels = ['color','shape']
    return dataSet, labels

def calcGini(dataSet):
    """計算基尼指數"""
    
    totalNum = shape(dataSet)[0]
    
    labelNum = {}
    gini = 0
    
    for data in dataSet:
        label = data[-1]
        if label in labelNum:
            labelNum[label] += 1
        else:
            labelNum[label] = 1
            
    for key in labelNum:
        p = labelNum[key]/ totalNum
        gini += p*(1-p)
        
    return gini

def chooseBestFeatVal2Split(dataSet):
    """選擇最優切分點"""
    
    # 1.如果沒有切分的變量
    if(len(dataSet[0]) == 1): 
        print("dfadfafa")
        return None, None
    
    # 2.所有的目標變量的相等
    if(len(set([d[-1] for d in dataSet])) == 1): 

        return None, None
    
    # 3.尋找最佳切分點
    bestfeatur = 0
    bestValue = 0
    lowestGini = 1000000
    totalGini = calcGini(dataSet)
    totalNum = shape(dataSet)[0]
    
    # 4.遍歷特徵
    for feature in range(shape(dataSet)[1] - 1):
        allValues = [d[feature] for d in dataSet]
        values = set(allValues)
        
        # 5.遍歷每個特徵所可能要取的值
        for value in values:
            leftChild, rightChild = splitByFeature(feature, value, dataSet)
            # 6.如果左子樹或右子樹無節點
            if(shape(leftChild)[0] == 0 or shape(rightChild)[0] == 0): return None, None
            
            # 7.計算劃分後的基尼指數
            leftNum = shape(leftChild)[0]
            rightNum = shape(rightChild)[0]
            
            curGini = leftNum/totalNum *calcGini(leftChild) + rightNum/totalNum*calcGini(rightChild)
            
            if (curGini < lowestGini):
                bestfeatur = feature
                bestValue = value
                lowestGini = curGini
    
    if(totalNum - lowestGini < 0.00001): return None, None
    
    return bestfeatur, bestValue

def splitByFeature(feature, value, dataSet):
    
    dataSet = mat(dataSet)
    leftChild = dataSet[nonzero(dataSet[:, feature] == value)[0],:].tolist()
    rightChild = dataSet[nonzero(dataSet[:, feature] != value)[0],:].tolist()
    
    return leftChild, rightChild

def checkIsOneCateg(newDataSet):
    """是否劃分節點是唯一的節點"""

    flag = False
    categList = [data[-1] for data in newDataSet]
    
    category = set(categList)
    if len(category) == 1:
         flag = True
    
    return flag

def  majorityCateg(newDataSet):
    """主要的類別"""
    
    categCount = {}
    categList = [data[-1] for data in newDataSet]
    
    for c in categList:
        if c not in  categCount:
            categCount[c] = 1
            
        else:
            categCount[c] += 1
            
    sortedCateg = sorted(categCount.item(), key=lambda x:x[1], reverse=True)
    
    return sortedCateg

def createClassifTree(dataSet):
    """創建分類樹"""
    
    feature, value = chooseBestFeatVal2Split(dataSet)
    
    if feature == None and checkIsOneCateg(dataSet):
        return dataSet[0][-1]
    
    if feature == None and not checkIsOneCateg:
        return majorityCateg(dataSet)
    
    classifTree = {}
    classifTree["featIndex"] = feature
    classifTree["value"] = value
    leftChild, rightChild = splitByFeature(feature, value, dataSet)
    classifTree["leftChild"] = createClassifTree(leftChild)
    classifTree["rightChild"] = createClassifTree(rightChild)
    
    
    return classifTree
    
if __name__ == '__main__':
    dataSet, labels = loadDataSet()
    classifTree = createClassifTree(dataSet)
    print(classifTree)

CART剪枝

輸入:CART算法生成的決策樹T0

輸出: 最優的決策樹Ta

1 設k=0,T=T0

2 設=Na(正無窮)

3自下而上的對內部結點t進行計算C(Tt),|Tt|和g(t)=,=min(,g(t))

4 對g(t)=的內部結點進行剪枝,並對葉結點t以多數表決法決定其類,得到樹T

5 設k=k+1,k=,Tk=T

6 如果Tk不是由根結點及兩個葉結點構成的樹,回到步驟3,否則另Tk=Tn

參考

機器學習之手把手實現

信息熵 條件熵 信息增益 信息增益比 GINI係數

決策樹中的熵、條件熵、信息增益和Gini指數計算示例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章