1. 程式人生 > >《統計學習方法》——從零實現決策樹

《統計學習方法》——從零實現決策樹

## 決策樹 決策樹是一種樹形結構,其中每個內部節點表示一個屬性上的判斷,每個分支代表一個判斷結果的輸出,最後每個葉子節點代表一種分類結果。 ## 決策樹學習的三個步驟: - 特徵選擇 通常使用資訊增益最大、資訊增益比最大或基尼指數最小作為特徵選擇的準則。 - 樹的生成 決策樹的生成往往通過計算資訊增益或其他指標,從根結點開始,遞迴地產生決策樹。這相當於用資訊增益或其他準則不斷地選取區域性最優的特徵,或將訓練集分割為能夠基本正確分類的子集。 - 樹的剪枝 由於生成的決策樹存在過擬合問題,需要對它進行剪枝,以簡化學到的決策樹。決策樹的剪枝,往往從已生成的樹上剪掉一些葉結點或葉結點以上的子樹,並將其父結點或根結點作為新的葉結點,從而簡化生成的決策樹。 ## 常用的特徵選擇準則: (1)資訊增益(ID3) 樣本集合$D$對特徵$A$的資訊增益定義為: $$g(D, A)=H(D)-H(D|A)$$ $$H(D)=-\sum_{k=1}^{K} \frac{\left|C_{k}\right|}{|D|} \log _{2} \frac{\left|C_{k}\right|}{|D|}$$ $$H(D | A)=\sum_{i=1}^{n} \frac{\left|D_{i}\right|}{|D|} H\left(D_{i}\right)$$ (2)資訊增益比(C4.5) 樣本集合$D$對特徵$A$的資訊增益比定義為: $$g_{R}(D, A)=\frac{g(D, A)}{H_A(D)}$$ $$H_A(D)=-\sum_{i=1}^{n} \frac{\left|D_{i}\right|}{|D|}log_2 \frac{\left|D_{i}\right|}{|D|}$$ 其中,$g(D,A)$是資訊增益,$H(D_A)$是資料集$D$關於特徵值A的熵,n是特徵A取值的個數。 (3)基尼指數(CART) 樣本集合$D$的基尼指數(CART) $$\operatorname{Gini}(D)=1-\sum_{k=1}^{K}\left(\frac{\left|C_{k}\right|}{|D|}\right)^{2}$$ 特徵$A$條件下集合$D$的基尼指數: $$\operatorname{Gini}(D, A)=\frac{\left|D_{1}\right|}{|D|} \operatorname{Gini}\left(D_{1}\right)+\frac{\left|D_{2}\right|}{|D|} \operatorname{Gini}\left(D_{2}\right)$$ ## ID3、C4.5和CART的區別 (1)適用範圍: - ID3和C4.5只能用於分類,CART還可以用於迴歸任務。 (2)樣本資料: - ID3只能處理離散的特徵,C4.5和CART可以處理連續變數的特徵(通過對資料排序之後找到類別不同的分割線作為切分點,根據切分點把連續屬性轉換為布林型, 從而將連續型變數轉換多個取值區間的離散型變數) - ID3對特徵的缺失值沒有考慮,C4.5和CART增加了對缺失值的處理(主要是兩個問題:樣本某些特徵缺失的情況下選擇劃分的屬性;選定了劃分屬性,對於在該屬性上缺失特徵的樣本的處理) - 從效率角度考慮,小樣本C4.5,大樣本CART。因為C4.5涉及到多次排序和對數運算,CART採用了簡化的二叉樹模型,在計算機中二叉樹模型會比多叉樹運算效率高,同時特徵選擇採用了近似的基尼係數來簡化計算。 (3)節點特徵選擇: - 在每個內部節點的特徵選擇上,ID3選擇資訊增益最大的特徵,C4.5選擇資訊增益比最大的特徵,CART選擇基尼指數最小的特徵及其切分點作為最優特徵和最優切分點。 - ID3和C4.5節點上可以產出多叉,而CART節點上永遠是二叉 - 特徵變數的使用中,對具有多個分類值的特徵ID3和C4.5在層級之間只單次使用,CART可多次重複使用 (4)剪枝 - C4.5是通過剪枝(PEP)來減小模型複雜度增加泛化能力,而CART是對所有子樹中選取最優子樹(CCP) ## 用numpy實現ID3決策樹的程式碼如下: ```python import numpy as np import pandas as pd import matplotlib.pyplot as plt %matplotlib inline from collections import defaultdict from math import log # 生成書上的資料 def create_data(): datasets = [['青年', '否', '否', '一般', '否'], ['青年', '否', '否', '好', '否'], ['青年', '是', '否', '好', '是'], ['青年', '是', '是', '一般', '是'], ['青年', '否', '否', '一般', '否'], ['中年', '否', '否', '一般', '否'], ['中年', '否', '否', '好', '否'], ['中年', '是', '是', '好', '是'], ['中年', '否', '是', '非常好', '是'], ['中年', '否', '是', '非常好', '是'], ['老年', '否', '是', '非常好', '是'], ['老年', '否', '是', '好', '是'], ['老年', '是', '否', '好', '是'], ['老年', '是', '否', '非常好', '是'], ['老年', '否', '否', '一般', '否'], ] df = pd.DataFrame(datasets, columns=[u'年齡', u'有工作', u'有自己的房子', u'信貸情況', u'類別']) return df # 計算資料集的熵,需D的最後一列為標籤 def entropy(D): total_num = len(D) label_cnt = defaultdict(int) for i in range(total_num): label = D[i][-1] label_cnt[label] += 1 ent = -sum([(cnt/total_num) * log(cnt/total_num, 2) for cnt in label_cnt.values()]) return ent # 計算列索引為index的屬性對集合D的條件熵 def cond_entropy(D, index): total_num = len(D) feature_sets = defaultdict(list) for i in range(total_num): feature = D[i][index] feature_sets[feature].append(D[i]) cond_ent = sum([(len(d)/total_num) * entropy(d) for d in feature_sets.values()]) return cond_ent ``` ```python # 決策樹節點類 class Node: def __init__(self, is_leaf, label=None, feature_idx=None, feature_name=None): self.is_leaf = is_leaf self.label = label # 僅針對於葉子節點 self.feature_idx = feature_idx # 該節點特徵對應的列索引,僅針對於非葉子節點 self.feature_name = feature_name # 該節點特徵名,僅針對於非葉子節點 self.sons = {} def add_son(self, feature_value, node): self.sons[feature_value] = node def predict(self, x): if self.is_leaf: return self.label return self.sons[x[self.feature_idx]].predict(x) def __repr__(self): s = { 'feature:': self.feature_name, 'label:': self.label, 'sons:': self.sons } return '{}'.format(s) # ID3決策樹 class ID3DTree: def __init__(self, epsilon=0.1): """ epsilon: 決策樹停止生長的資訊增益閾值 """ self.epsilon = epsilon self.decision_tree = None def fit(self, data): """data為dataframe格式""" self.decision_tree = self._train(data) return self.decision_tree def predict(self, x): if self.decision_tree: return self.decision_tree.predict(x) def _get_max_gain_feature(self, D): num_features = len(D[0]) - 1 ent_D = entropy(D) index, max_gain = 0, 0 for i in range(num_features): cond_ent = cond_entropy(D, i) gain = ent_D - cond_ent # 資訊增益 = H(D) - H(D|A) if gain > max_gain: max_gain = gain index = i return index, max_gain def _train(self, data): """遞迴構建決策樹,data為dataframe格式""" y, feature_names = data.iloc[:, -1], data.columns[:-1] # 1.資料集中所有樣本均屬於同一類別,則停止生長 if len(y.value_counts()) == 1: return Node(is_leaf=True, label=y.iloc[0]) # 2.資料集中特徵數量為空,則將包含例項數量最多的類作為該葉子節點的標籤 if len(feature_names) == 0: label = y.value_counts().sort_values(ascending=False).index[0] return Node(is_leaf=True, label=label) # 計算資訊增益最大的特徵 idx, max_gain = self._get_max_gain_feature(np.array(data)) # 3. 如果最大的資訊增益小於設定的閾值,則停止生長 if max_gain < self.epsilon: label = y.value_counts().sort_values(ascending=False).index[0] return Node(is_leaf=True, label=label) target_feature = feature_names[idx] # 當前節點特徵 curr_node = Node(is_leaf=False, feature_idx=idx, feature_name=target_feature) value_sets = data[target_feature].value_counts().index # 該特徵取值集合 for value in value_sets: sub_data = data.loc[data[target_feature]==value].drop([target_feature], axis=1) # 4.遞迴生成子樹 sub_tree = self._train(sub_data) curr_node.add_son(value, sub_tree) return curr_node ``` ### 測試程式碼: ```python df_data = create_data() dt = ID3DTree() tree = dt.fit(df_data) print('決策樹:{}'.format(tree)) y_test = dt.predict(['老年', '否', '是', '非常好']) print('樣本 [老年, 否, 是, 非常好] 的預測結果:{}'.format(y_test)) ``` ### 輸出為: 決策樹:{'feature:': '有自己的房子', 'label:': None, 'sons:': {'否': {'feature:': '有工作', 'label:': None, 'sons:': {'否': {'feature:': None, 'label:': '否', 'sons:': {}}, '是': {'feature:': None, 'label:': '是', 'sons:': {}}}}, '是': {'feature:': None, 'label:': '是', 'sons:': {}}}} 樣本 [老年, 否, 是, 非常好] 的預測結