決策樹的原理與實踐

學習目標


  1. 了解 決策樹 的理論知識

  2. 掌握 決策樹 的 sklearn 函數調用使用並將其運用到企鵝數據集預測

決策樹的介紹


決策樹是一種常見的分類模型,在金融分控、醫療輔助診斷等諸多行業具有較為廣泛的應用。決策樹的核心思想是基於樹結構對數據進行劃分,這種思想是人類處理問題時的本能方法。例如在婚戀市場中,女方通常會先看男方是否有房產,如果有房產再看是否有車產,如果有車產再看是否有穩定工作……最後得出是否要深入了解的判斷。

決策樹的主要優點:

  1. 具有很好的解釋性,模型可以生成可以理解的規則。

  2. 可以發現特徵的重要程度。

  3. 模型的計算複雜度較低。

決策樹的主要缺點:

  1. 模型容易過擬合,需要採用減枝技術處理。

  2. 不能很好利用連續型特徵。

  3. 預測能力有限,無法達到其他強監督模型效果。

  4. 方差較高,數據分佈的輕微改變很容易造成樹結構完全不同。

決策樹的應用


由於決策樹模型中自變量與因變量的非線性關係以及決策樹簡單的計算方法,使得它成為集成學習中最為廣泛使用的基模型。梯度提升樹(GBDT),XGBoost以及LightGBM等先進的集成模型都採用了決策樹作為基模型,在廣告計算、CTR預估、金融風控等領域大放異彩,成為當今與神經網絡相提並論的複雜模型,更是數據挖掘比賽中的常客。在新的研究中,南京大學周志華老師提出一種多粒度級聯森林模型,創造了一種全新的基於決策樹的深度集成方法,為我們提供了決策樹發展的另一種可能。

同時決策樹在一些需要明確可解釋甚至提取分類規則的場景中被廣泛應用,而其他機器學習模型在這一點很難做到。例如在醫療輔助系統中,為了方便專業人員發現錯誤,常常將決策樹算法用於輔助病症檢測。例如在一個預測哮喘患者的模型中,醫生髮現測試的許多高級模型的效果非常差。所以他們在數據上運行了一個決策樹的模型,發現算法認為劇烈咳嗽的病人患哮喘的風險很小。但醫生非常清楚劇烈咳嗽一般都會被立刻檢查治療,這意味着患有劇烈咳嗽的哮喘病人都會馬上得到收治。用於建模的數據認為這類病人風險很小,是因為所有這類病人都得到了及時治療,所以極少有人在此之後患病或死亡。

決策樹構建的偽代碼


TB1peuGdsVl614jSZKPXXaGjpXa-1174-1194.png

決策樹的構建過程是一個遞歸過程。函數存在三種返回狀態:(1)當前節點包含的樣本全部屬於同一類別,無需繼續劃分;(2)當前屬性集為空或者所有樣本在某個屬性上的取值相同,無法繼續劃分;(3)當前節點包含的樣本集合為空,無法劃分。

劃分選擇


從上述偽代碼中我們發現,決策樹的關鍵在於第六步從A中選擇最優劃分屬性a∗,一般我們希望決策樹每次劃分節點中包含的樣本盡量屬於同一類別,也就是節點的「純度」更高。

信息增益

信息熵是一種衡量數據混亂程度的指標,信息熵越小,則數據的「純度」越高。

TB1vOW_dSR26e4jSZFEXXbwuXXa-258-44.jpg

其中pk代表了第k類樣本在D中佔有的比例。

TB1RHKaiz39YK4jSZPcXXXrUFXa-1348-202.png

TB1NxbGeBFR4u4jSZFPXXanzFXa-389-49.jpg

一般的信息增益越大,則意味着使用特徵a來進行劃分的效果越好。

基尼指數

TB14qHQPoY1gK0jSZFCXXcwqXXa-225-154.jpg

基尼指數反映了從數據集D中隨機抽取兩個的類別標記不一致的概率。

TB127b_gP39YK4jSZPcXXXrUFXa-333-50.jpg

使用特徵a對數據集D劃分的基尼指數定義為上。

Demo實踐


Step1: 庫函數導入

##  基礎函數庫
import numpy as np 

## 導入畫圖庫
import matplotlib.pyplot as plt
import seaborn as sns

## 導入決策樹模型函數
from sklearn.tree import DecisionTreeClassifier
from sklearn import tree

Step2: 訓練模型

## 構造數據集
x_fearures = np.array([[-1, -2], [-2, -1], [-3, -2], [1, 3], [2, 1], [3, 2]])
y_label = np.array([0, 1, 0, 1, 0, 1])

## 調用決策樹模型
tree_clf = DecisionTreeClassifier()

## 用決策樹模型擬合構造的數據集
tree_clf = tree_clf.fit(x_fearures, y_label)

Step3: 數據和模型可視化(需要用到graphviz可視化庫)

## 可視化構造的數據樣本點
plt.figure()
plt.scatter(x_fearures[:, 0], x_fearures[:, 1], c=y_label, s=50, cmap='viridis')
plt.title('Dataset')
plt.show()

微信截圖_20200811195011.png

## 本段代碼實際效果為本地生成PDF可視化文檔,在體驗過程中可以不運行,可能無法正常展示結果;
## 代碼生成的可視化結果會截圖展示實際效果
!pip install graphviz 
import graphviz
dot_data = tree.export_graphviz(tree_clf, out_file=None)
graph = graphviz.Source(dot_data)
graph.render("pengunis")
## 'pengunis.pdf'

微信截圖_20200811195152.png

Step4:模型預測

## 創建新樣本
x_fearures_new1 = np.array([[0, -1]])
x_fearures_new2 = np.array([[2, 1]])
## 在訓練集和測試集上分別利用訓練好的模型進行預測
y_label_new1_predict = tree_clf.predict(x_fearures_new1)
y_label_new2_predict = tree_clf.predict(x_fearures_new2)
print('The New point 1 predict class:\n', y_label_new1_predict)
print('The New point 2 predict class:\n', y_label_new2_predict)
# The New point 1 predict class:
 # [1]
# The New point 2 predict class:
 # [0]

微信截圖_20200811195412.png

基於企鵝數據集的決策樹實戰


在實踐的最開始,我們首先需要導入一些基礎的函數庫包括:numpy (Python進行科學計算的基礎軟件包),pandas(pandas是一種快速,強大,靈活且易於使用的開源數據分析和處理工具),matplotlib和seaborn繪圖。

Step1:函數庫導入

##  基礎函數庫
import numpy as np 
import pandas as pd

## 繪圖函數庫
import matplotlib.pyplot as plt
import seaborn as sns

本次我們選擇企鵝數據(palmerpenguins)進行方法的嘗試訓練,該數據集一共包含8個變量,其中7個特徵變量,1個目標分類變量。共有150個樣本,目標變量為 企鵝的類別 其都屬於企鵝類的三個亞屬,分別是(Adélie, Chinstrap and Gentoo)。包含的三種種企鵝的七個特徵,分別是所在島嶼,嘴巴長度,嘴巴深度,腳蹼長度,身體體積,性別以及年齡。

TB1B5yvbODsXe8jSZR0XXXK6FXa-875-615.jpg

Step2:數據讀取/載入

## 我們利用Pandas自帶的read_csv函數讀取並轉化為DataFrame格式

data = pd.read_csv('penguins_raw.csv')
## 為了方便我們僅選取四個簡單的特徵,有興趣的同學可以研究下其他特徵的含義以及使用方法
data = data[['Species', 'Culmen Length (mm)', 'Culmen Depth (mm)', 'Flipper Length (mm)', 'Body Mass (g)']]

Step3:數據信息簡單查看

## 利用.info()查看數據的整體信息
data.info()
## <class 'pandas.core.frame.DataFrame'>
## RangeIndex: 344 entries, 0 to 343
## Data columns (total 5 columns):
## Species                344 non-null object
## Culmen Length (mm)     342 non-null float64
## Culmen Depth (mm)      342 non-null float64
## Flipper Length (mm)    342 non-null float64
## Body Mass (g)          342 non-null float64
## dtypes: float64(4), object(1)
## memory usage: 13.6+ KB
## 進行簡單的數據查看,我們可以利用 .head() 頭部.tail()尾部
data.head()

TB1KETxPeL2gK0jSZPhXXahvXXa-869-795.png

這裡我們發現數據集中存在NaN,一般的我們認為NaN在數據集中代表了缺失值,可能是數據採集或處理時產生的一種錯誤。這裡我們採用-1將缺失值進行填補,還有其他例如「中位數填補、平均數填補」的缺失值處理方法有興趣的同學也可以嘗試。

data = data.fillna(-1)
data.tail()

TB10XHQPlr0gK0jSZFnXXbRRXXa-886-451.jpg

## 其對應的類別標籤為'Adelie Penguin', 'Gentoo penguin', 'Chinstrap penguin'三種不同企鵝的類別。
data['Species'].unique()
## array(['Adelie Penguin (Pygoscelis adeliae)',
##       'Gentoo penguin (Pygoscelis papua)',
##       'Chinstrap penguin (Pygoscelis antarctica)'], dtype=object)
## 利用value_counts函數查看每個類別數量
pd.Series(data['Species']).value_counts()
## Adelie Penguin (Pygoscelis adeliae)          152
## Gentoo penguin (Pygoscelis papua)            124
## Chinstrap penguin (Pygoscelis antarctica)     68
## Name: Species, dtype: int64
## 對於特徵進行一些統計描述
data.describe()

TB1zyzockcx_u4jSZFlXXXnUFXa-889-427.jpg

Step4:可視化描述

## 特徵與標籤組合的散點可視化
sns.pairplot(data=data, diag_kind='hist', hue= 'Species')
plt.show()

TB11mbrPXY7gK0jSZKzXXaikpXa-970-733.png

從上圖可以發現,在2D情況下不同的特徵組合對於不同類別的企鵝的散點分佈,以及大概的區分能力。

'''為了方便我們將標籤轉化為數字
       'Adelie Penguin (Pygoscelis adeliae)'        ------0
       'Gentoo penguin (Pygoscelis papua)'          ------1
       'Chinstrap penguin (Pygoscelis antarctica)   ------2 '''

def trans(x):
    if x == data['Species'].unique()[0]:
        return 0
    if x == data['Species'].unique()[1]:
        return 1
    if x == data['Species'].unique()[2]:
        return 2

data['Species'] = data['Species'].apply(trans)
for col in data.columns:
    if col != 'Species':
        sns.boxplot(x='Species', y=col, saturation=0.5, palette='pastel', data=data)
        plt.title(col)
        plt.show()

利用箱型圖我們也可以得到不同類別在不同特徵上的分佈差異情況。

# 選取其前三個特徵繪製三維散點圖
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')

data_class0 = data[data['Species'] == 0].values
data_class1 = data[data['Species'] == 1].values
data_class2 = data[data['Species'] == 2].values
# 'setosa'(0), 'versicolor'(1), 'virginica'(2)
ax.scatter(data_class0[:, 0], data_class0[:, 1], data_class0[:, 2],label=data['Species'].unique()[0])
ax.scatter(data_class1[:, 0], data_class1[:, 1], data_class1[:, 2],label=data['Species'].unique()[1])
ax.scatter(data_class2[:, 0], data_class2[:, 1], data_class2[:, 2],label=data['Species'].unique()[2])
plt.legend()

plt.show()

TB1pQ2JPoT1gK0jSZFrXXcNCXXa-572-449.png

Step5:利用 決策樹模型 在二分類上 進行訓練和預測

## 為了正確評估模型性能,將數據劃分為訓練集和測試集,並在訓練集上訓練模型,在測試集上驗證模型性能。
from sklearn.model_selection import train_test_split

## 選擇其類別為0和1的樣本 (不包括類別為2的樣本)
data_target_part = data[data['Species'].isin([0, 1])][['Species']]
data_features_part = data[data['Species'].isin([0, 1])][['Culmen Length (mm)', 'Culmen Depth (mm)', 'Flipper Length (mm)', 'Body Mass (g)']]

## 測試集大小為20%, 80%/20%分
x_train, x_test, y_train, y_test = train_test_split(data_features_part, data_target_part, test_size=0.2, random_state=2020)
## 從sklearn中導入決策樹模型
from sklearn.tree import DecisionTreeClassifier
from sklearn import tree
## 定義 決策樹模型 
clf = DecisionTreeClassifier(criterion='entropy')
## 在訓練集上訓練決策樹模型
clf.fit(x_train, y_train)
## DecisionTreeClassifier(class_weight=None, criterion='entropy', 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')
## 可視化
## 本段代碼實際效果為本地生成PDF可視化文檔,在體驗過程中可以不運行,可能無法正常展示結果;
## 代碼生成的可視化結果會截圖展示實際效果
import graphviz
dot_data = tree.export_graphviz(clf, out_file=None)
graph = graphviz.Source(dot_data)
graph.render("penguins")
## 'penguins.pdf'

TB1cp6nPuL2gK0jSZPhXXahvXXa-1117-702.png

## 在訓練集和測試集上分佈利用訓練好的模型進行預測
train_predict = clf.predict(x_train)
test_predict = clf.predict(x_test)
from sklearn import metrics
## 利用accuracy(準確度)【預測正確的樣本數目佔總預測樣本數目的比例】評估模型效果
print('The accuracy of the Logistic Regression is:', metrics.accuracy_score(y_train, train_predict))
print('The accuracy of the Logistic Regression is:', metrics.accuracy_score(y_test, test_predict))
## 查看混淆矩陣 (預測值和真實值的各類情況統計矩陣)
confusion_matrix_result = metrics.confusion_matrix(test_predict, y_test)
print('The confusion matrix result:\n', confusion_matrix_result)
# 利用熱力圖對於結果進行可視化
plt.figure(figsize=(8, 6))
sns.heatmap(confusion_matrix_result, annot=True, cmap='Blues')
plt.xlabel('Predicted labels')
plt.ylabel('True labels')
plt.show()
## The accuracy of the Logistic Regression is: 0.9954545454545455
## The accuracy of the Logistic Regression is: 1.0
## The confusion matrix result:
## [[31  0]
## [ 0 25]]

TB1.DYtPoz1gK0jSZLeXXb9kVXa-460-375.png

Step6:利用 決策樹模型 在三分類(多分類)上 進行訓練和預測

## 測試集大小為20%, 80%/20%分
x_train, x_test, y_train, y_test = train_test_split(data[['Culmen Length (mm)', 'Culmen Depth (mm)', 'Flipper Length (mm)', 'Body Mass (g)']], data[['Species']], test_size=0.2, random_state=2020)
## 定義 決策樹模型 
clf = DecisionTreeClassifier()
# 在訓練集上訓練決策樹模型
clf.fit(x_train, y_train)
## 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')
## 在訓練集和測試集上分佈利用訓練好的模型進行預測
train_predict = clf.predict(x_train)
test_predict = clf.predict(x_test)
## 由於決策樹模型是概率預測模型(前文介紹的 p = p(y=1|x,\theta)),所有我們可以利用 predict_proba 函數預測其概率
train_predict_proba = clf.predict_proba(x_train)
test_predict_proba = clf.predict_proba(x_test)
print('The test predict Probability of each class:\n', test_predict_proba)
## 其中第一列代表預測為0類的概率,第二列代表預測為1類的概率,第三列代表預測為2類的概率。
## 利用accuracy(準確度)【預測正確的樣本數目佔總預測樣本數目的比例】評估模型效果
print('The accuracy of the Logistic Regression is:', metrics.accuracy_score(y_train, train_predict))
print('The accuracy of the Logistic Regression is:', metrics.accuracy_score(y_test, test_predict))
## The test predict Probability of each class:
## [[0. 0. 1.]
## [0. 1. 0.]
## [0. 1. 0.]
## [1. 0. 0.]
## …………
## …………
## [1. 0. 0.]
## [1. 0. 0.]]
## The accuracy of the Logistic Regression is: 0.9963636363636363
## The accuracy of the Logistic Regression is: 0.9565217391304348
## 查看混淆矩陣
confusion_matrix_result = metrics.confusion_matrix(test_predict, y_test)
print('The confusion matrix result:\n', confusion_matrix_result)
# 利用熱力圖對於結果進行可視化
plt.figure(figsize=(8, 6))
sns.heatmap(confusion_matrix_result, annot=True, cmap='Blues')
plt.xlabel('Predicted labels')
plt.ylabel('True labels')
plt.show()
## The confusion matrix result:
## [[30  1  0]
## [ 0 23  0]
## [ 2  0 13]]

TB1dJzAPoY1gK0jSZFCXXcwqXXa-460-375.png

重要參數


criterion

Criterion這個參數正是用來決定模型特徵選擇的計算方法的。sklearn提供了兩種選擇:

  • 輸入」entropy「,使用信息熵(Entropy)

  • 輸入」gini「,使用基尼係數(Gini Impurity)

random_state & splitter

random_state用來設置分枝中的隨機模式的參數,默認None,在高維度時隨機性會表現更明顯。splitter也是用來控制決策樹中的隨機選項的,有兩種輸入值,輸入」best”,決策樹在分枝時雖然隨機,但是還是會優先選擇更重要的特徵進行分枝(重要性可以通過屬性feature_importances_查看),輸入「random”,決策樹在分枝時會更加隨機,樹會因為含有更多的不必要信息而更深更大,並因這些不必要信息而降低對訓練集的擬合。

max_depth

限制樹的最大深度,超過設定深度的樹枝全部剪掉。這是用得最廣泛的剪枝參數,在高維度低樣本量時非常有效。決策樹多生長一層,對樣本量的需求會增加一倍,所以限制樹深度能夠有效地限制過擬合。

min_samples_leaf

min_samples_leaf 限定,一個節點在分枝後的每個子節點都必須包含至少min_samples_leaf個訓練樣本,否則分枝就不會發生,或者,分枝會朝着滿足每個子節點都包含min_samples_leaf個樣本的方向去發生。一般搭配max_depth使用,在回歸樹中有神奇的效果,可以讓模型變得更加平滑。這個參數的數量設置得太小會引起過擬合,設置得太大就會阻止模型學習數據。