決策樹是一種常見的機器學習方法,通常一棵決策樹爲一顆多叉樹.
每個葉子節點就對應於一個決策結果.決策樹的生成過程相似於數據結構中的樹的生成過程.node
__
輸入:python
訓練集$D=\{(x_1,y_1),(x_2,y_2),...,(x_m,y_m)\}$git
屬性集$A=\{a_1,a_2,...,a_d\}$github
西瓜書基本算法: 過程: 函數TreeGenerate(D, A) 生成節點node if D中樣本全屬於同一類別C then 將node標記爲C類葉節點; return end if if A爲空 OR D中樣本在A上的取值相同 then 將node標記爲葉節點,其分類標記爲D中樣本數最多的類; return end if 從A中選擇最優的劃分屬性a; for a 的每個值av do 爲node生成一個分支;令Dv表示D中在a上取值爲av的樣本集; if Dv 爲空 then 將分支標記爲葉節點,其類別標記爲D中樣本最多的類; return else 以TreeGenerate(Dv, A-{a*})爲分支節點 end if end for
輸出: 以node爲根節點的一顆決策樹
__算法
如下爲python代碼的僞代碼, 參考<<機器學習實戰>>數據結構
def create_tree(data_set, labels): if D中樣本全輸入同一類別C: return 類別C if A爲空: return D中樣本數最多的類 從A中選取最優劃分屬性a node = {label: {}} for a的每個屬性值av: node[label][av] = {} 令Dv表示D在屬性a上取值av的樣本子集 if Dv爲空: node[label][av] = D中樣本最多的類 else: node[label][av] = create_tree(Dv, labels) return node
在<<西瓜書>>中有三種狀況致使遞歸返回:(1)當前節點包含的樣本全屬於同一類別, 無需劃分;(2)當前屬性集爲空,或是全部樣本在全部屬性上取值相同,沒法劃分;(3)當前節點包含的樣本集合爲空,不能劃分機器學習
在<<機器學習實戰>>沒有判斷"全部樣本在全部屬性上取值相同"這個條件,我的認爲緣由有兩個: 其一, 判斷的難度比較大,代碼複雜,耗費時間長; 其二, 在知足條件"全部樣本在全部屬性上取值相同"這個條件時, 其全部樣本類別有很大機率是屬於同一類, 再者繼續訓練也只會造成一個單叉樹.ide
在以上的算法流程中,最重要的步驟就是在屬性集A中選擇最優的劃分屬性a, 通常在劃分的過程當中,但願劃分出來的樣本子集儘可能屬於同一類別, 即節點的"純度"愈來愈高.函數
"信息熵")是度量樣本集合純度最經常使用的一種指標. 假定當前樣本集合D中第$k$類樣本的比例爲$p_k(k=1,2,...,|\mathcal{Y}|)$, 則$D$的信息熵定義爲post
$$Ent(D)= \sum ^{|\mathcal{Y}|}_{k=1}p_klog_2p_k$$
$Ent(D)$的值越小,則$D$的純度越高.
計算信息熵時約定:若$p=0$, 則$plog_2p=0$. $Ent(D)$的最小值爲0,最大值爲$log_2|\mathcal{Y}|$
下面給出信息增益的計算公式
$$Gain(D, a)=Ent(D)- \sum ^V_{v=1} \frac {|D|}{|D^v|}Ent(D^v)$$
$V\{{a^1,a^2,...,a^v}\}$爲屬性$a$的屬性值集合; $D^v$爲使用屬性$a$在$D$中進行劃分,$D$中屬性$a$的屬性值爲$a^v$的樣本子集; $\frac{|D|}{|D^v|}$爲每一個分支節點上的權重.
通常而言, 信息增益越大, 則意味着使用屬性$a$來進行劃分所得到的"純度提高"越大. 全部在算法中選擇屬性$a_*=arg_{a \in A} maxGain(D, a)$
python代碼tree_gain.py
信息增益準則對可取值較多的屬性有所偏好,爲減小這種偏好可能帶來的不利影響,可以使用"增益率"來選擇最優劃分屬性, 增益率定義爲
$$Gain_ratio(D,a)=\frac{Grain(D,a)}{IV(a)}$$
其中
$$IV(a)=-\sum_{v=1}^{V}\frac{|D^v|}{|D|}log_2\frac{|D^v|}{|D|}$$
$IV(a)$稱爲屬性$a$的"固有值"(intrinsic value). 屬性$a$的可能取值數目越多(即V越大), 則$IV(a)$的值一般會越大.
須要注意的是, 增益率準則對可取值數目較少的屬性全部偏好, 所以根據C4.5算法, 可先從候選劃分屬性中找出信息增益高於平均水平的屬性,再從中選擇增益率最高的.
python代碼tree_gain_ratio.py
CART決策樹使用"基尼指數"(Gini index)來選擇劃分屬性. 數據集$D$的純度能夠用基尼值來度量:
$$Gini(D)=\sum_{k=1}^{|\mathcal{Y}|}\sum_{k{'}\ne k}p_kp_{k^{'}}=1-\sum_{k=1}^{|\mathcal{Y}|}p_k^2$$
直觀來講,$Gini(D)$反映了從數據集$D$中隨機抽取兩個樣本,其類別不一致的機率. 所以, $Gini(D)$越小, 則數據集$D$的純度越高.
屬性$a$的基尼指數定義爲
$$Gini\_index(D,a)=\sum_{k=1}^{|\mathcal{Y}|}\frac{|D^v|}{|D|}Gini(D^v)$$
因而,在候選屬性集$A$中選取使劃分後基尼指數最小的屬性做爲最優劃分屬性,即$a_*=arg_{a\in A}min\ Gini\_index(D,a)$
python代碼tree_gini.py
剪枝(pruning)是決策樹學習算法中對付"過擬合"的主要手段. 決策樹剪枝的基本策略有"預剪枝"(prepruning)和"後剪枝"(postpruning).
預剪枝是指在決策樹生成過程當中,對每一個節點在劃分前先進行估計, 若當前節點的劃分不能帶來決策樹泛化性能提高, 則中止劃分並講當前節點標記爲頁節點.
預剪枝的決策樹生成過程相似於二叉樹的先序遍歷, 劃分前先進行判斷是否剪枝, 若是不須要剪枝再生成下一個節點.
預剪枝基於"貪心"本質禁止這些分支展開,給預剪枝決策樹帶來了欠擬合的風險.
預剪枝python僞代碼:
def verity_divide(train_data_set, train_data_set): # 驗證集爲空不進行劃分 if 驗證集爲空: return False 選取最優劃分屬性a 劃分後節點divide_node = {a: {}} for a的每個屬性值av: 令TDv表示訓練樣本train_data_set中屬性a上取值爲av的樣本子集 divide_node[a][av] = TDv中類別最多的類 divide_count表示劃分後驗證的正確數量 for train_data_set中的每個樣本: if 驗證正確: divide_cout += 1 not_divide_count表示train_data_set中樣本最多的類的數量 if divide_count > not_divide_count: return True else: return False def create_tree(train_data_set, verity_data_set, labels): if train_data_set中樣本全輸入同一類別C: return 類別C if A爲空: return train_data_set中樣本數最多的類 if not verity_divide(train_data_set, verity_data_set): return D中樣本最多的類 # 此處可優化, 可先獲取最優屬性後傳入verity_divide() 從A中選取最優劃分屬性a node = {label: {}} for a的每個屬性值av: node[label][av] = {} 令TDv表示train_data_set在屬性a上取值av的樣本子集 令TVv表示verity_data_set在屬性a上取值爲av的樣本子集 if TDv爲空: node[label][av] = train_data_set中樣本最多的類 else: node[label][av] = create_tree(TDv, TVv, labels) return node
後剪枝是先從訓練集生成一顆完整的決策樹, 而後自底向上地對非葉子節點進行考察, 若將該節點對應的子樹替換爲葉節點能帶來決策樹的泛化性能提高,則將該子樹替換爲葉節點.
後剪枝決策樹的生成過程相似於二叉樹的後續遍歷; 即先生成決策樹, 在判斷是否須要剪枝, 若是須要剪枝則放棄子樹, 直接將節點標記爲葉節點.
後剪枝的過程是在徹底決策樹以後進行的,而且要自底向上地對決策樹中的全部非葉節點進行逐一考察, 所以其訓練時間開銷比未剪枝決策樹和預剪枝決策樹都要大得多.
後剪枝python僞代碼:
def verity_divide(tree, train_data_set, verity_data_set): # 驗證集爲空不剪枝 if 驗證集爲空: return False not_divide_right_rate = 不劃分的驗證正確率 divide_right_rate = 劃分後的驗證正確率 # 不劃分的驗證正確率大於劃分的驗證正確率時剪枝 if not_divide_right_rate > divide_right_rate: return True else: return False def create_tree(train_data_set, verity_data_set, labels): if train_data_set中樣本全輸入同一類別C: return 類別C if A爲空: return train_data_set中樣本數最多的類 if not verity_divide(train_data_set, verity_data_set): return train_data_set中樣本最多的類 從A中選取最優劃分屬性a node = {label: {}} for a的每個屬性值av: node[label][av] = {} 令TDv表示train_data_set在屬性a上取值av的樣本子集 令TVv表示verity_data_set在屬性a上取值av的樣本子集 if TDv爲空: node[label][av] = train_data_set中樣本最多的類 else: node[label][av] = create_tree(TDv, TVv, labels) if verity_divide(node, train_data_set, verity_data_set): node = train_data_set中樣本最多的類 return node
因爲連續屬性的可取值數目再也不有限, 所以,不能直接根據連續屬性的可取值來對節點進行劃分, 須要將連續屬性離散化, 最簡單的策略是採用二分法對連續屬性進行處理.
給定樣本集$D$和連續屬性$a$, 假定$a$在$D$上出現了n個不一樣的取值, 將這些值從小到大進行排序, 記爲$\{a^1,a^1,...,a^n\}$. 基於劃分點$t$可將D分爲子集$D_t^-$和$D_t^+$, 其中$D_t^-$包含那行屬性a上取值不大於t的樣本, 而$D_t^+$則包含那些在屬性$a$上取值大於$t$的樣本. 顯然, 對相鄰的屬性取值$a^i$和$a^{i+1}$來講, $t$在區間$[a^i,a^{i+1})$中取任何值產生的劃分結果相同. 所以, 對連續屬性$a$, 咱們可考察包含$n-1$個元素的候選劃分點集合
$$T_a=\{\frac{a^i+a^{i+1}}{2}|i\le i\le n-1|\}$$
即把區間$[a^i,a^{i+1})$的中位點$\frac{a^i+a^{i+1}}{2}$做爲候選劃分點. 而後就可像離散屬性同樣來考察這些劃分點, 選取最優的劃分點進行樣本集合的劃分.
$$Gain(D,a)=\max\limits_{t\in T_a} Ent(D) - \sum_{\lambda\in \{-,+\}} \frac{|D|}{{|D_t^\lambda|}}Ent(D_t^\lambda)$$
其中$Gain(D,a,t)$是樣本集$D$基於劃分點$t$二分後的信息增益. 因而,可選擇$Gain(D,a,t)$最大化的劃分點.
須要注意, 與離散值不一樣, 若當前節點劃分屬性爲連續屬性, 連續屬性還可做爲其後代節點的劃分屬性.
在寫代碼的時候須要注意在離散屬性和連續屬性的$gain(D,a)$時須要分開處理. 在構建決策樹時, 離散屬性和連續屬性也須要分開處理, 由於劃分連續屬性時,不須要在數據集中去除連續屬性.
完整python代碼tree_gain_continuous_value.py
面對缺失值須要解決的兩個問題: (1)如何在屬性值缺失的狀況下進行劃分屬性選擇? (2)給定劃分屬性,若樣本在改屬性上的值缺失,如何對樣本進行劃分?
給定訓練集$D$和屬性$a$, 令$\tilde{D}$表示$D$中在屬性$a$上沒有缺失值的樣本子集. 對問題(1), 顯然僅可根據$\tilde{D}$來判斷屬性$a$的優劣. 假定屬性$a$有$V$個可取值$\{a^1,a^2,...,a^V\}$, 令$\tilde{D}^v$表示$\tilde{D}$在屬性$a$上的取值爲$a^v$的樣本子集, $\tilde{D}_k$表示$\tilde{D}$屬性第$k$類$(k=1,2,...,|\mathcal{Y}|)$的樣本子集, 則顯然有$\tilde{D}=\cup_{k=1}^{|\mathcal{Y}|}\tilde{D}_k$, $\tilde{D}=\cup_{v=1}^V\tilde{D}^v$. 假定咱們爲每一個樣本$\boldsymbol{x}$賦予一個權重$w_{\boldsymbol{x}}$, 並定義
$$\rho=\frac{\sum_{\boldsymbol{x}\in \tilde{D}}w_{\boldsymbol{x}}}{\sum_{\boldsymbol{x}\in D}w_{\boldsymbol{x}}}$$
$$\tilde{p}_k=\frac{\sum_{\boldsymbol{x}\in \tilde{D}_k}w_{\boldsymbol{x}}}{\sum_{\boldsymbol{x}\in \tilde{D}}w_{\boldsymbol{x}}} \ \ \ \ \ (1\le k\le |\mathcal{Y}|)$$
$$\tilde{r}_v=\frac{\sum_{\boldsymbol{x}\in \tilde{D}^v}w_{\boldsymbol{x}}}{\sum_{\boldsymbol{x}\in \tilde{D}}w_{\boldsymbol{x}}} \ \ \ \ \ (1\le v\le V)$$
對屬性$a$, $\rho$表示完好失值樣本所佔的比例, $\tilde{p}_k$表示完好失值樣本中第$k$類所佔的比例, $\tilde{r}_v$則表示完好失值樣本中屬性a上取值$a^v$的樣所佔的比例. 顯然$\sum_{k=1}^{|\mathcal{Y}|}\tilde{p}_k=1$, $\sum_{v=1}^V\tilde{r}_v=1$
基於上述定義, 可將信息增益的計算式推廣爲
$$Gian(D,a)=\rho \times Gain(\tilde{D},a)=\rho \times (Ent(\tilde{D}) - \sum_{v=1}^V \tilde{r}_v Ent(\tilde{D}^v))$$
其中
$$Ent(\tilde{D}) = - \sum_{k=1}^{|\mathcal{Y}|}\tilde{p}_k log_2 \tilde{p}_k$$
對問題(2), 若樣本$\boldsymbol{x}$在劃分屬性$a$上的取值已知, 則將$\boldsymbol{x}$劃入與其取值對應的子節點, 且樣本權值在子節點中保持$w_{\boldsymbol{x}}$. 若樣本$\boldsymbol{x}$在劃分屬性$a$上的取值未知, 則將$\boldsymbol{x}$同時劃入全部子節點, 且樣本權值在與屬性$a^v$對應的子節點中調整爲$\tilde{r}_v \cdot w_{\boldsymbol{x}}$; 直觀地看, 就是讓同同樣本以不一樣的機率劃入到不一樣的子節點中去.