前面介紹的決策樹通常還有一個名字,叫做 CART(讀音與cut相近)。CART 是 Classification And Regression Tree 的首字母縮寫,通過 Classification And Regression Tree 的字面意思可以看出,CART 這種決策樹既能夠解決分類問題(Classification)也能夠解決回歸問題(Regression)。每個節點根據某種衡量系統不確定性的指標(信息熵或基尼係數)來找到某個合適的維度 d 以及維度 d 上的閾值 v,根據 d 和 v 對當前節點中的數據進行二分,通過這種方式得到的決策樹一定是一顆二叉樹,這也是 CART 這種決策樹的特點。
在 sklearn 中的決策樹都是 CART。在文獻或者資料中看到的 ID3、C4.5 和 C5.0 都是構建決策樹的不同方法。
CART的複雜度決策樹的預測時間複雜度為 O(logm),訓練的時間複雜度為 O(n*m*logm),「需要注意我們介紹的都是 CART 這種決策樹。」
通過之前對決策樹上的各個節點上的數據進行劃分的模擬,對於構建好的一棵決策樹進行預測,平均而言的時間複雜度為 O(logm),其中 m 為樣本個數。每次在一個節點上都是對當前的數據進行對半劃分,因此最終這棵決策樹的高度大概為 logm,當構建好了這棵高度為 logm 的決策樹之後,來了一個新樣本,這個新樣本就需要從這棵決策樹的根節點開始一步一步進行決策判斷,最終走到葉子節點。根據對應葉子節點上的數據來決定新樣本的標籤(分類問題)和目標(回歸問題)。
由於決策樹算法屬於無參數學習,因此構造決策樹的過程就是決策樹的訓練過程,決策樹訓練的時間複雜度為 O(n*m*logm)。在模擬構建決策樹的過程中,需要對樣本中的每一個維度 n 和每一個樣本 m 進行遍歷,最終找到在哪個維度上的哪個閾值上進行劃分的最佳劃分點,訓練的時間複雜度相對來說還是比較高的。
CART的剪枝決策樹和 kNN 算法一樣都屬於非參數學習算法,所有的非參數學習算法都非常容易發生過擬合。因此對於決策樹來說,不僅訓練的時間複雜度比較高,而且非常容易發生過擬合。基於決策樹的高時間複雜度以及容易產生過擬合的問題,實際在構建決策樹的時候必須要對決策樹進行剪枝的操作,剪枝操作有兩個目的:
其實前面構建決策樹的例子中一直在使用剪枝操作。在使用 sklearn 創建決策樹的對象時傳入的 max_depth 參數,指定 max_depth 參數一直是 2,即構建決策樹的最大深度,這其實就是剪枝的一種手段。換句話說,所謂的剪枝,其實就是在創建決策樹對象時傳入參數的平衡。
除了前面一直使用的 max_depth 最大深度這個參數外,還有很多參數可以用於剪枝,這些參數既可以降低決策樹訓練過程的時間複雜度,同時也可以減輕過擬合的問題。
首先導入相應的庫函數。
In[1]: import numpy as np
import matplotlib.pyplot as plt本小節使用的是由 sklearn.datasets 中的 make_moons 函數生成的噪聲為 0.25 的非線性虛擬數據集。使用非線性數據集是為了能夠更好的看出決策樹發生過擬合的樣子,以及使用超參數解決過擬合後的結果。
In[2]: from sklearn import datasets
X, y = datasets.make_moons(noise = 0.25, random_state = 666)通過散點圖繪製數據集的分布。
In[3]: plt.scatter(X[y == 0, 0], X[y == 0, 1])
plt.scatter(X[y == 1, 0], X[y == 1, 1])
plt.show()In[4]: from sklearn.tree import DecisionTreeClassifier
dt_clf = DecisionTreeClassifier()
dt_clf.fit(X, y)
Out[4]: DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False, random_state=None,
splitter='best')使用 sklearn 中的決策樹不傳入任何參數,默認節點上的劃分標準使用基尼係數。默認 max_depth = None,對決策樹的深度不做限定,決策樹會一直向下劃分,直到劃分後的節點的基尼係數都為 0 為止。
訓練好了默認參數的決策樹,接下來使用前面一直使用的 plot_decision_boundary 函數繪製決策邊界。
In[5]: def plot_decision_boundary(model, axis):
x0, x1 = np.meshgrid(
np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1, 1),
np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1, 1),
)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)選擇合適的繪製範圍,在繪製決策邊界的同時將數據集以散點圖的形式也繪製出來。
In[6]: plot_decision_boundary(dt_clf, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()此時繪製出來的決策邊界的形狀相對不規則,顯然默認參數的決策樹模型發生了過擬合。
指定 max_depth 參數為 2,限制整個決策樹的最大深度為 2In[7]: dt_clf2 = DecisionTreeClassifier(max_depth=2)
dt_clf2.fit(X, y)
plot_decision_boundary(dt_clf2, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()此時繪製出來的決策邊界非常清晰,沒有那種特別不規則的邊界。換句話說,決策樹模型不會針對某幾個特別的樣本點進行特殊的變化。相對於前面默認參數的決策樹,顯然指定參數max_depth = 2 的決策樹模型的過擬合程度降低。當然此時的模型又可能欠擬合,所以對於這些參數,我們需要進行比較精細的調整,讓決策樹模型處在一個既不過擬合又不欠擬合的位置上。
指定 min_samples_split 參數為 10,節點再劃分所需要的最小樣本數為 10In[8]: dt_clf3 = DecisionTreeClassifier(min_samples_split=10)
dt_clf3.fit(X, y)
plot_decision_boundary(dt_clf3, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()此時繪製出來的決策邊界非常清晰,沒有那種特別不規則的邊界。換句話說,決策樹模型不會針對某幾個特別的樣本點進行特殊的變化。相對於前面默認參數的決策樹,顯然指定參數min_samples_split = 10 的決策樹模型的過擬合程度降低。
「將 min_samples_split 參數的值設置的越低,決策樹模型越容易發生過擬合。」 考慮極端情況下,如果將 min_samples_split 設置的值大於等於樣本總數,此時僅有的根節點不需要進行劃分,顯然此時僅有一個根節點的決策樹模型欠擬合。
指定 min_samples_leaf 參數為 6,葉子節點最少樣本數為 6In[9]: dt_clf4 = DecisionTreeClassifier(min_samples_leaf=6)
dt_clf4.fit(X, y)
plot_decision_boundary(dt_clf4, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()此時繪製出來的決策邊界非常清晰,沒有那種特別不規則的邊界。換句話說,決策樹模型不會針對某幾個特別的樣本點進行特殊的變化。相對於前面默認參數的決策樹,顯然指定參數min_samples_leaf = 6 的決策樹模型的過擬合程度降低。
「將 min_samples_leaf 參數的值設置的越低,決策樹模型越容易發生過擬合。」 考慮極端情況下,如果將 min_samples_leaf 設置為 1,對於只有一個樣本點的葉子節點,在具體預測的時候,測試樣本點需要根據所到達的葉子節點上的樣本點來決定預測的類別(或目標),而如果此時葉子節點僅有一個樣本點,那麼測試樣本點非常容易受到這一個樣本點的影響,測試樣本點的預測類別(或目標)會變得非常敏感。
指定 max_leaf_nodes 參數為 4,最大葉子節點數為 4In[10]: dt_clf5 = DecisionTreeClassifier(max_leaf_nodes=4)
dt_clf5.fit(X, y)
plot_decision_boundary(dt_clf5, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()此時繪製出來的決策邊界非常清晰,沒有那種特別不規則的邊界。換句話說,決策樹模型不會針對某幾個特別的樣本點進行特殊的變化。相對於前面默認參數的決策樹,顯然指定參數max_leaf_nodes = 4 的決策樹模型的過擬合程度降低。
「將 max_leaf_nodes 參數的值設置的越高,決策樹模型越容易發生過擬合。」
小結「決策樹這種非參數學習很容易過擬合,所以在實際使用這些參數的時候,要注意避免決策樹模型被過渡調節參數,從而導致決策樹模型欠擬合。同時這些參數並不是相互獨立的,它們之間可以相互組合,所以可以使用網格搜索的方式尋找最優的參數組合。」
對於決策樹算法來說,可以調節的參數還有很多,「但是需要注意,無論將決策樹用於分類問題,還是用於回歸問題,可能無論怎樣調節這些參數都不能得到特別好的效果,這就是決策樹的局限性。」 儘管如此,決策樹依然非常重要,因為機器學習中非常重要的算法隨機森林,使用了決策樹的思想,所以本小節介紹的這些參數,也能夠用於隨機森林的調參。