一. 決策樹與預備知識
- 基本概述
決策樹是一種樹型結構,它是以實例爲基礎的歸納學習,每個內部結點表示在一個屬性上的測試,每個分支代表一個測試輸出,每個葉結點代表一個類別。決策樹採用的是自頂向下的遞歸方法,其基本思想是以信息熵爲度量構造一棵熵值下降最快的樹,到葉子節點處的熵值爲0,此時每個葉節點中的實例都屬於同一類。
- 決策樹學習算法的特點
決策樹學習算法的最大優點是它可以自學習,只需要對訓練實例進行較好的標註,就能夠進行學習。
決策樹算法屬於有監督學習算法。
從一類無序無規則的事物中,推理出決策樹表示的分類規則。
- 熵
當熵和條件熵中的概率由數據估計(特別是極大似然估計)得到時,所對應的熵和條件熵分別稱爲經驗熵和經驗條件熵。
熵度量了事物的不確定性,越不確定的事物,它的熵就越大。具體的,隨機變量X的熵的表達式如下:
推廣到多個個變量的聯合熵,這裏給出兩個變量X和Y的聯合熵表達式:
- 信息增益
信息增益表示得知特徵A的信息而使得類X的信息的不確定性減少的程度。例如:當你要判斷一個女生是否適合你,比如 身高/長相/體重這三個因素是決定你是否選擇她的關鍵指標,信息增益就是說當你知道女生的身高後,你就能進一步確定是否是適合你的,那麼這種不確定性減少的程度就是信息增益。
- 互信息
定義:特徵A對訓練數據集D的信息增益爲,定義集合D的經驗熵與特徵A給定條件下D的經驗條件熵之差,即
, 這即爲訓練數據集D和特徵A的互信息。
- 信息增益的計算方法
a. 計算數據集D的經驗熵
b. 計算特徵A對數據集D的經驗條件熵
c. 計算信息增益
經驗熵計算:
經驗條件熵計算:
信息增益:
** 誰的信息增益最大,那個特徵就是當前決策樹要選擇的節點。
- 信息增益率
特徵對訓練數據集的信息增益定義爲其信息增益與訓練數據集關於特徵的值的熵之比,即
- 基尼指數
- 基尼指數的圖像, 熵, 分類誤差率三者之間的關係
基尼係數和熵之半的曲線非常接近,僅僅在45度角附近誤差稍大。因此,基尼係數可以做爲熵模型的一個近似替代。而CART分類樹算法就是使用的基尼係數來選擇決策樹的特徵。同時,爲了進一步簡化,CART分類樹算法每次僅僅對某個特徵的值進行二分,而不是多分,這樣CART分類樹算法建立起來的是二叉樹,而不是多叉樹。這樣一可以進一步簡化基尼係數的計算,二可以建立一個更加優雅的二叉樹模型。
二. 決策樹學習算法
特徵選擇也即選擇最優劃分屬性,從當前數據的特徵中選擇一個特徵作爲當前節點的劃分標準。我們希望在不斷劃分的過程中,決策樹的分支節點所包含的樣本儘可能屬於同一類,即節點的“純度”越來越高。而選擇最優劃分特徵的標準不同,也導致了決策樹算法的不同。
- 三種決策樹學習算法介紹
ID3算法: 適應信息來進行特徵選擇的決策樹學習過程即爲ID3決策。
C4.5算法: 利用信息增益率進行特徵選擇
CART算法:利用基尼指數進行特徵選擇
總結:一個屬性的信息增益越大,表明屬性對樣本的熵減少的能力更強,這個屬性使得數據由不確定性變成確定性的能力越強。
2. 算法
算法的核心是在決策樹各個節點上根據信息增益來選擇進行劃分的特徵,然後遞歸地構建決策樹。具體方法:
- 從根節點開始,對節點計算所有可能的特徵的信息增益,選擇信息增益值最大的特徵作爲節點的劃分特徵;
- 由該特徵的不同取值建立子節點;
- 再對子節點遞歸地調用以上方法,構建決策樹;
- 到所有特徵的信息增益都很小或者沒有特徵可以選擇爲止,得到最終的決策樹
的侷限:
- 沒有剪枝(沒有考慮過擬合的問題)
- 採用信息增益作爲選擇最優劃分特徵的標準,然而信息增益會偏向那些取值較多的特徵(這也是採用信息增益率的原因)。在相同條件下,取值比較多的特徵比取值少的特徵信息增益大。比如一個變量有個值,各爲,另一個變量爲個值,各爲,其實他們都是完全不確定的變量,但是取個值的比取2個值的信息增益大。
- 沒有考慮連續特徵,比如長度,密度都是連續值,無法在運用。這大大限制了的用途。
- 算法對於缺失值的情況沒有做考慮
舉例:
ID3 算法的作者昆蘭基於上述不足,對ID3算法做了改進,這就是C4.5算法
3. C4.5算法
參考博客:https://www.cnblogs.com/pinard/p/6050306.html的講解。
總之,C4.5解決了上述四個問題:
第一 不能處理連續特徵
第二個就是用信息增益作爲標準容易偏向於取值較多的特徵
第三缺失值處理的問
第四過擬合問題
解決不能處理連續特徵的問題:
的思路是將連續的特徵離散化。比如個樣本的連續特徵有個,從小到大排列爲,則取相鄰兩樣本值的平均數,一共取得個劃分點,其中第個劃分點表示爲:
對於這個點,分別計算以該點作爲二元分類點時的信息增益。選擇信息增益最大的點作爲該連續特徵的二元離散分類點。比如取到的增益最大的點爲,則小於的值爲類別,大於的值爲類別,這樣我們就做到了連續特徵的離散化。
解決信息增益作爲標準容易偏向於取值較多的特徵
引入一個信息增益比的變量,它是信息增益和特徵熵的比值。表達式如下: 其中: 爲樣本特徵輸出的集合,爲樣本特徵,對於特徵熵,
表達式如下:
其中:爲特徵A的類別數,爲特徵的第個取值對應的樣本個數。爲樣本個數。特徵數越多的特徵對應的特徵熵越大,它作爲分母,可以校正信息增益容易偏向於取值較多的特徵的問題。
對於第三個缺失值處理的問題,主要需要解決的是兩個問題,一是在樣本某些特徵缺失的情況下選擇劃分的屬性,二是選定了劃分屬性,對於在該屬性上缺失特徵的樣本的處理。
- CART算法
首先,我們要明白,什麼是迴歸樹,什麼是分類樹。兩者的區別在於樣本輸出,如果樣本輸出是離散值,那麼這是一顆分類樹。如果果樣本輸出是連續值,那麼這是一顆迴歸樹。
,分類迴歸樹算法,既可用於分類也可用於迴歸。CART分類樹算法使用基尼係數來代替信息增益比,基尼係數代表了模型的不純度,基尼係數越小,則不純度越低,特徵越好。這和信息增益(比)是相反的。
假設決策樹是二叉樹,內部節點特徵的取值爲“是”和“否”,左分支爲取值爲“是”的分支,右分支爲取值爲”否“的分支。這樣的決策樹等價於遞歸地二分每個特徵,將輸入空間(即特徵空間)劃分爲有限個單元。的分類樹用基尼指數來選擇最優特徵的最優劃 分點,具體過程如:
具體的,在分類問題中,假設有個類別,第個類別的概率爲, 則基尼係數的表達式爲:
如果是二類分類問題,屬於第一個樣本輸出的概率是,則基尼係數的表達式爲:
對於個給定的樣本,假設有個類別, 第個類別的數量爲,則樣本的基尼係數表達式爲:
從根節點開始,對節點計算現有特徵的基尼指數,對每一個特徵,例如,再對其每個可能的取值 如,根據樣本點對的結果的”是“與”否“劃分爲兩個部分,利用下式進行計算。
- 在所有可能的特徵以及該特徵所有的可能取值中,選擇基尼指數最小的特徵及其對應的取值作爲最優特徵和最優切分點。然後根據最優特徵和最優切分點,將本節點的數據集二分,生成兩個子節點。
- 對兩個字節點遞歸地調用上述步驟,直至節點中的樣本個數小於閾值,或者樣本集的基尼指數小於閾值,或者沒有更多特徵後停止;
- 生成分類樹;
三. 決策樹過擬合
(1) 由於決策樹算法非常容易過擬合,因此對於生成的決策樹必須要進行剪枝。剪枝的算法有非常多,的剪枝方法有優化的空間。思路主要是兩種,一種是預剪枝,即在生成決策樹的時候就決定是否剪枝。另一個是後剪枝,即先生成決策樹,再通過交叉驗證來剪枝。
Cart樹剪枝
(2) sklearn 提供的剪枝方式:
a. max_depth
限制樹的最大深度,超過設定深度的樹枝全剪掉
b. min_samples_split
一個節點要包含min_samples_split個訓練樣本
c. min_samples_leaf
一個節點在分支後的每個節點至少包含多少個葉子節點
d. min_weight_fraction_leaf
這個參數用來限制信息增益的大小
e. max_features
這個參數用做樹的精修,限制分枝時考慮的特徵個數
# 決策樹.py
#
import numpy as np
from sklearn import tree, datasets, preprocessing
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import graphviz
np.random.RandomState(0)
# 加載數據
iris = datasets.load_iris()
# 劃分訓練集與測試集
x, y = iris.data, iris.target
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)
# 數據預處理
scaler = preprocessing.StandardScaler().fit(x_train)
x_train = scaler.transform(x_train)
x_test = scaler.transform(x_test)
# 創建模型
clf = tree.DecisionTreeClassifier('gini')
# 模型擬合
clf.fit(x_train, y_train)
# 預測
y_pred = clf.predict(x_test)
# 評估
print(accuracy_score(y_test, y_pred))
dot_data = tree.export_graphviz(clf, out_file=None,
feature_names=iris.feature_names,
class_names=iris.target_names,
filled=True, rounded=True,
special_characters=True)
graph = graphviz.Source(dot_data)
graph.render("iris")