機器學習系列(三十五)——決策樹Decision Tree

本篇主要內容:決策樹,信息熵,Gini係數

什麼是決策樹

決策樹(Decision Tree)是knn之後又一種非參數的有監督學習方法,它能夠從一系列有特徵和標籤的數據中總結出決策規則,並用樹狀圖的結構來呈現這些規則,以解決分類和迴歸問題,而且決策樹天然能解決多分類問題,具有非常好的可解釋性。
以一個判斷哺乳動物的例子來直觀理解一下決策樹:

這是一個樹結構,每個葉子節點都是一個決策(分類),每當有一個新的樣本,就按照這棵樹結構進行判斷。比如現在有動物企鵝,在不知道它是否是哺乳動物的情況下,用這棵決策樹進行判斷,首先在根節點判斷它是恆溫動物嗎?回答是,接着在一箇中間節點判斷它是胎生嗎?回答不是,這時判斷就進行完畢,企鵝不是哺乳動物。這就是一個決策樹的決策過程,當然實際中要進行的決策可能要複雜的多,不過原理是一樣的。
決策樹中最初的問題所在的地方叫做根節點,在得到結論前的每一個問題都是中間節點,而得到的每一個結論(決策結果)都叫做葉子節點。決策樹的深度是判斷的最多的次數。


構建決策樹

信息熵作爲不純度

下面就來看看如何構造一棵決策樹,在此之前首先給出信息熵的概念,熵在信息論中代表一個系統不確定性的度量,隨機變量的熵越大,數據的不確定性越高。信息熵的計算公式爲:
Entropy=-\sum_{i=1}^kp_ilog(p_i)

其中p_i是事件的發生概率,一個系統中,所有p_i的和是1。如${1,0,0}這個系統,可以計算得到信息熵爲0,因爲有概率爲1的事件,於是這個系統沒有不確定性。以二分類的信息熵爲例,繪製它的函數圖像:

def H(p):
    return -p*np.log(p)-(1-p)*np.log(1-p)
x = np.linspace(0.01,0.99,100)
plt.plot(x,H(x))
plt.show()

可以看到等概率事件具有最大的信息熵,這也很好理解,等概率時是最沒有把握將樣本分爲其中一類的,也就是等概率時系統的不確定性最高,這樣的情況同樣可以拓展到多分類。
決策樹就是一個按照信息增益不斷提純數據的過程,我們在樣本的某個維度(某個特徵)進行劃分,又在某一維度的某個閥值(特徵取值)進行劃分,使得劃分後的數不純度不斷降低。當前最好的劃分就是讓劃分後的不純度與原不純度有最大的絕對值差。接下來我們就來使用信息熵來尋找最優劃分:

'''信息熵尋找最優劃分'''
def split(x,y,d,value):
    index_a = (x[:,d] <= value)#布爾向量
    index_b = (x[:,d] > value)
    return x[index_a], x[index_b], y[index_a], y[index_b]

from collections import Counter
from math import log
def entropy(y):
    counter = Counter(y)#統計各類數目,以鍵值對形式返回
    res = 0.0
    for num in counter.values():
        p = num / len(y)
        res += - p * log(p)
    return res
def try_split(x, y):
    best_entropy = float('inf')
    best_d, best_v = -1, -1
    for d in range(x.shape[1]):#shape[1]=2共兩個維度
        sorted_index = np.argsort(x[:, d])#返回排序後的索引
        for i in range(1, len(x)):
            if x[sorted_index[i-1], d] != x[sorted_index[i],d]:
                v = (x[sorted_index[i-1], d] + x[sorted_index[i], d]) / 2
                x_l, x_r, y_l, y_r = split(x, y, d, v)#進行劃分
                e = entropy(y_l) + entropy(y_r)#劃分後求熵
                
                if e < best_entropy:
                    best_entropy, best_d, best_v = e, d, v#更新劃分參數,直到最優
    return best_entropy,best_d,best_v

爲了展示我們找到的劃分結果,首先我們使用sklearn中的決策樹來對鳶尾花進行分類,同樣爲了可視化的方便,只取兩個特徵,取的是後兩個特徵:

'''鳶尾花數據,取後兩個特徵'''
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

iris = datasets.load_iris()
X = iris.data[:,2:]
y = iris.target

plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.scatter(X[y==2,0],X[y==2,1])
plt.show()

接下來使用sklearn中的決策樹進行分類,並繪製決策邊界,這裏指定決策樹最大深度爲2:

'''這是使用很多次的決策邊界繪製函數'''
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)
'''訓練模型並繪製決策邊界'''
from sklearn.tree import DecisionTreeClassifier
dt_clf = DecisionTreeClassifier(max_depth=2, criterion='entropy')
dt_clf.fit(X,y)
plot_decision_boundary(dt_clf,axis=[0.5,7.5,0,3])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.scatter(X[y==2,0],X[y==2,1])
plt.show()

決策邊界:

從上面的決策邊界大致可以看出,決策的基本過程是:

如果安裝了python-graphviz模塊,還可以在Jupyter中畫出決策樹:

from sklearn import tree
feature_name = ['feature1','feature2']
import graphviz
dot_data = tree.export_graphviz(dt_clf
                                ,feature_names=feature_name
                                ,class_names=["A","B","C"]
                                ,filled=True#填充顏色
                                ,rounded=True#圓角
                                )
graph = graphviz.Source(dot_data)
graph

這裏詳細給出了決策樹計算的一些指標。
下面使用我們的信息熵劃分代碼進行劃分:

'''第一次劃分'''
best_entropy, best_d, best_v = try_split(X, y)
print(best_entropy)
print(best_v)
print(best_d)

從結果看出,第一次劃分是在特徵1上的2.45值處,與sklearn結果一致,接着進行下一步劃分,由於左邊信息熵已爲0,只需劃分右邊:

'''第一次劃分結果'''
x1_l,x1_r,y1_l,y1_r=split(X,y,best_d,best_v)
entropy(y1_l)#左邊信息熵爲0
'''對右邊繼續劃分'''
#第二步劃分
best_entropy2,best_d2,best_v2=try_split(x1_r,y1_r)
print(best_entropy2)
print(best_v2)
print(best_d2)

第二次劃分是在特徵2上的1.75值處,和sklearn中結果一致,此時劃分後的左右信息熵:

劃分後信息熵仍沒變爲0,實際上我們還能繼續劃分,直至所有葉子節點信息熵皆爲0。不過sklearn這裏指定了決策樹最大深度爲2,此時已達到最大深度,故沒有繼續下去了,所以我們這裏也不繼續進行劃分了。

Gini係數作爲不純度

除了信息熵之外,我們還有其它指標對數據進行純度衡量,從而劃分數據,就是基尼係數(Gini),Gini係數的計算公式爲:
Gini=1-\sum_{i=1}^mp_i^2

其實它和信息熵有幾乎相同的性質,Gini係數越大表示系統數據不確定性越大,如果對二分類數據畫圖,圖像走勢和使用信息熵是一致的,都在等概率時達到最大值,這個結論同樣在多分類適用。

既然二者性質相當,在實際使用中,絕大多數的情況信息熵和基尼係數的效果也都基本相同,那爲什麼還要使用Gini係數呢?這是因爲Gini係數計算不涉及對數運算,計算比信息熵快很多,因此sklearn中默認的就是使用Gini。當然並非所有問題爲了計算簡便都使用Gini係數,比起Gini係數,信息熵對不純度更加敏感,對不純度的懲罰更強,所以信息熵作爲指標時,決策樹的生長會更加“精細”,因此當模型擬合程度不足的時候,即當模型在訓練集和測試集上都表現不太好的時候,可以考慮信息熵。對於高維數據或者噪音很多的數據,信息熵往往容易過擬合,此時選用Gini係數。
下面在同樣的數據集上使用Gini係數作爲不純度指標來進行劃分,並繪製決策邊界:

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
iris=datasets.load_iris()
x=iris.data[:,2:]
y=iris.target

from sklearn.tree import DecisionTreeClassifier
dt_clf = DecisionTreeClassifier(max_depth=2,criterion="gini")
dt_clf.fit(x,y)#訓練模型
'''繪製決策邊界'''
plot_decision_boundary(dt_clf,axis=[0.5,7.5,0,3])
plt.scatter(x[y==0,0],x[y==0,1])
plt.scatter(x[y==1,0],x[y==1,1])
plt.scatter(x[y==2,0],x[y==2,1])
plt.show()

決策邊界:

可以看到和用信息熵得到的決策邊界是相同的。繪製此時的決策樹:

from sklearn import tree
feature_name = ['feature1','feature2']
import graphviz
dot_data = tree.export_graphviz(dt_clf
                                ,feature_names=feature_name
                                ,class_names=["A","B","C"]
                                ,filled=True#填充顏色
                                ,rounded=True#圓角
                                )
graph = graphviz.Source(dot_data)
graph

決策樹圖示:

可見和使用信息熵的劃分相同。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章