決策樹
決策樹是一種樹形結構,其中每個內部節點表示一個屬性上的判斷,每個分支代表一個判斷結果的輸出,最後每個葉子節點代表一種分類結果。
決策樹學習的三個步驟:
- 特徵選擇
通常使用資訊增益最大、資訊增益比最大或基尼指數最小作為特徵選擇的準則。
- 樹的生成
決策樹的生成往往通過計算資訊增益或其他指標,從根結點開始,遞迴地產生決策樹。這相當於用資訊增益或其他準則不斷地選取區域性最優的特徵,或將訓練集分割為能夠基本正確分類的子集。
- 樹的剪枝
由於生成的決策樹存在過擬合問題,需要對它進行剪枝,以簡化學到的決策樹。決策樹的剪枝,往往從已生成的樹上剪掉一些葉結點或葉結點以上的子樹,並將其父結點或根結點作為新的葉結點,從而簡化生成的決策樹。
常用的特徵選擇準則:
(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決策樹的程式碼如下:
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
# 決策樹節點類
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
測試程式碼:
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:': {}}}}
樣本 [老年, 否, 是, 非常好] 的預測結果:是