【機器學習實戰】-- Titanic 資料集(5)-- 決策樹
1. 寫在前面:
本篇屬於實戰部分,更注重於演算法在實際專案中的應用。如需對感知機演算法本身有進一步的瞭解,可參考以下連結,在本人學習的過程中,起到了很大的幫助:
統計學習方法 李航
決策樹演算法原理https://www.cnblogs.com/pinard/p/6050306.html 等共2篇
2. 資料集:
資料集地址:https://www.kaggle.com/c/titanic
Titanic資料集是Kaggle上參與人數最多的專案之一。資料本身簡單小巧,適合初學者上手,深入瞭解比較各個機器學習演算法。
資料集包含11個變數:PassengerID、Pclass、Name、Sex、Age、SibSp、Parch、Ticket、Fare、Cabin、Embarked,通過這些資料來預測乘客在Titanic事故中是否倖存下來。
3. 演算法簡介:
決策樹是一種i基本的分類與迴歸方法,亦經常被作為整合學習的基學習器。決策樹本質上是要一個定義在特徵空間和類空間上的條件概率。本篇主要介紹的是CART決策樹(Classification And Regression Trees),其根據損失函式最小化的原則建立決策樹,並對決策樹進行剪枝。CART決策樹是遞迴的構建二叉決策樹,在每一步中都根據損失函式最小化選取最優解,因此,它是屬於貪心演算法的一種。
決策的學習通常包括3個步驟:特徵選擇、決策樹的生成、以及決策樹的修剪。下面,我們就依次從這3個方面對CART決策樹方法進行簡單的介紹。
3.1 特徵選擇:
在進行特徵選擇之前,我們需要對一些術語有一些瞭解,包括:熵、條件熵、互資訊、資訊增益、資訊增益比、基尼指數。
熵:熵是表示隨機變數不確定性的度量,假設 $X$ 是一個取有限個值的離散隨機變數,其概率分佈為$P(X=x_{i}) = p_{i}, \quad i=1,2,...,n$,則隨機變數 $X$ 的熵定義為 $H(x) =H(p)= -\sum_{i=1}^{n}p_{i}log(p_{i})$。
當隨機變數的取值只有兩個(如0和1,即伯努利分佈時),熵為 $H(p) = -p\log2p - (1-p)\log_2(1-p)$i
條件熵:$H(Y|X)$ 表示隨機變數X給定的條件下隨機變數Y的條件熵,定義為 $H(Y|X) = \sum_{i=1}^{n}p_{i}H(Y|X=x_{i})$
互資訊(mutual information):熵與條件熵之差稱為互資訊。
資訊增益(information gain):特徵A對訓練資料集D的資訊增益$g(D,A)$,定義為D的經驗熵$H(D)$與特徵A給定條件下D的經驗條件熵$H(D|A)$之差,即$ g(D,A) = H(D) - H(D|A) $。決策樹學習中的資訊增益等價於訓練資料集中類與特徵的互資訊。
資訊增益比:資訊增益值的大小是相對於訓練資料集而言的,沒有絕對意義。在相同條件下,取值比較多的特徵比取值少的特徵資訊增益大。比如一個變數有2個值,各為1/2,另一個變數為3個值,各為1/3,其實他們都是完全不確定的變數,但是取3個值的比取2個值的資訊增益大。於是,引入資訊增益比的概念,定義為:$g_{R}(D,A) = \frac{g(D,A)}{H_{A}(D)}$,這裡李航老師的書中有點問題,書中分母為$H(D)$。$H(D)$是訓練資料集對於類別的經驗熵,而$H_{A}(D)$是訓練資料集關於某個特徵的經驗熵。
基尼指數:假設 $X$ 是一個取有限個值的離散隨機變數,其概率分佈為$P(X=x_{i}) = p_{i}, \quad i=1,2,...,n$,則隨機變數 $X$ 的基尼指數定義為 $\text{Gini}(x) = \text{Gini}(p) = \sum_{i=1}^{n}p_{i}(1-p_{i}) = 1 - \sum_{i=1}^{n}p_{i}^{2}$。對於二分類問題,基尼指數和$\frac{1}{2}$熵的曲線很接近,且基尼指數的計算較快,因此在CART樹分類樹演算法中使用基尼指數。
3.1.1 CART分類樹特徵選擇:
CART分類樹使用基尼指數最小化準則選擇最優特徵,同時決定該特徵的最優二值切分點。
CART分類樹是可以處理離散變數和連續變數的。對於離散變數,假設特徵A有n個取值,CART演算法取相鄰兩樣本值的平均數,一共取得n-1個劃分點n。分別計算這n-1個點作為二元分類點時,該特徵的基尼係數。最後選取基尼係數最小的作為該連續特徵的二元離散分類點。未完全區分的離散變數(包括離散化後的連續變數),在後續的迭代過程中依然可以參與子節點的產生選擇過程。
3.1.2 CART迴歸樹特徵選擇:
CART迴歸樹使用平方誤差最小化準則選擇最優特徵:對每個劃分單元$R_{m}$ 有 $\sum_{x_{i} \in R_{m}} (y_{i} - f(x_{i}))^2$,且$f(x_{i})$ 對每個單元是固定值。用過平方誤差最小化可求得,$f(x_{i}) = \text{avg}(y_{i}| x_{i} \in R_{m})$
3.2 決策樹生成:
3.2.1 CART分類樹生成:
根據訓練資料集D,從根結點開始,遞迴地對每個結點進行以下操作,構建二叉決策樹:
第一步:設結點的訓練資料集為D,計算現有所有特徵對該資料集的基尼指數。對每一個特徵A,對其可能取的每個值a(對連續特徵,a即其劃分點),計算A=a時的基尼係數
第二步:在所有可能的特徵A以及它們所有可能的切分點a中,選擇基尼係數最小的特徵及其對應的切分點作為最優特徵與最優切分點。並照此切分,由現結點生成兩個子結點,將訓練集資料分配到兩個子結點中
第三步:對兩個子節點遞迴地重複前兩步,直到滿足停止條件
3.2.1 CART迴歸樹生成:
整體流程和CART分類樹一致。
根據訓練資料集D所在的輸入空間,遞迴地將每個區域劃分成兩個子區域並決定每個子區域上的輸出值,構建二叉決策樹:
第一步:選擇最優切分變數 $j$ 與切分點 $s$:
$$\min_{j,s}\left [ \min_{c_{1}} \sum_{x_{i} \in R_{1}(j,s)}(y_{i} - c_{1})^2 + \min_{c_{2}} \sum_{x_{i} \in R_{2}(j,s)}(y_{i} - c_{2})^2 \right ] $$
第二步:用選定的$(j,s)$對劃分區域$R_{1}$和$R_{2}$,並決定相應的輸出值,輸出值為每個單元上的所有例項對應的輸出的均值
第三步:繼續對兩個子區域遞迴地重複前兩步,直到滿足停止條件
3.3 決策樹剪枝:
決策樹剪枝過程,參考李航老師的原文:
這裡簡要解釋一下,其中的 $g(t) = \frac{C(t) - C(T_{t})}{|T_{t}| - 1}$ 是如何推匯出來的:
首先,我們定義子樹的損失函式為:
$$ C_{\alpha}(T) = C(T) + \alpha |T| $$
其中,$T$為任意子樹,$C(T)$ 為對訓練資料的預測誤差即損失函式(分類樹為基尼指數、迴歸樹為平方誤差),$|T|$ 為子樹的葉結點個數,$\alpha \geq0$ 為引數,類似於正則化係數。$C_{\alpha}(T)$ 為引數是 $\alpha$ 時的子樹 $T$ 的整體損失。引數 $\alpha$ 權衡訓練資料的擬合程度與模型的複雜度。
接下來,對於整體樹$T_{0}$的任意內部結點$t$:
以$t$為單結點樹的損失函式是:$C_{\alpha}(t) = C(t) + \alpha$
以$t$為根結點的子樹$T_{t}$的損失函式是:$ C_{\alpha}(T_{t}) = C(T_{t}) + \alpha|T_{t}| $
當$\alpha$極小時,$C_{\alpha}(T_{t}) < C_{\alpha}(t)$,當$\alpha$增大到一定程度時,$C_{\alpha}(T_{t}) = C_{\alpha}(t)$,此時,單結點樹$t$的損失函式和根節點樹$T_{t}$相同且結點更少,便對$T_{t}$進行剪枝。而此時滿足等式的$\alpha$為 $\alpha=\frac{C(t) - C(T_{t})}{|T_{t}| - 1}$。
4. 實戰:
sklearn中,分類樹主要使用 sklearn.tree.DecisionTreeClassifier,迴歸樹主要使用 sklearn.tree.DecisionTreeRegressor。Titanic 資料集是分類問題,所以使用 sklearn.tree.DecisionTreeClassifier。
下面,對DecisionTreeClassifier中一些比較重要的引數進行一下說明:
1. criterion:‘gini’,'entropy' 損失函式的選取基尼指數或者是熵
2. splitter:‘best’,‘random’ 每個結點進行二叉樹分類時,如何選取最優劃分點。‘best’是指特徵所有劃分點中的最優劃分點,'random'是指隨機選取的劃分點中的最優劃分點。資料量較大時,建議使用'random'。
3. max_depth:樹的深度,資料量較大時,可以給定一個限制,一般根據資料分佈在10~100之間。
4. min_samples_split:每個可以被再次分類的結點中所需要的最少的樣本數。預設為2
5. min_samples_leaf:每個葉結點中所需要的最少的樣本數,若不滿足,將和它對應的另一個葉結點一起被剪枝,退回上一節點。預設為1
6. max_features:劃分是考慮的最大特徵數。當特徵數特別大時,可以用'sqrt',‘log2’等降級特徵數,加快樹的生成時間‘
7. max_leaf_nodes:最大的葉結點數,防止過擬合
8. ccp_alpha:即上一節決策樹剪枝中提到的$\alpha$引數,小於$\alpha$的子樹將會被剪枝。但需要注意的是,sklearn中不會在所有子樹中進行交叉驗證,然後選取最優子樹。
以下是簡單的程式碼,供參考:
1 import pandas as pd 2 import numpy as np 3 import matplotlib.pyplot as plt 4 from sklearn.preprocessing import MinMaxScaler, StandardScaler, OneHotEncoder, OrdinalEncoder 5 from sklearn.impute import SimpleImputer 6 from sklearn.model_selection import StratifiedKFold, GridSearchCV 7 from sklearn.pipeline import Pipeline, FeatureUnion 8 from sklearn.tree import DecisionTreeClassifier 9 from sklearn.metrics import accuracy_score, precision_score, recall_score 10 from sklearn.base import BaseEstimator, TransformerMixin 11 12 13 class DataFrameSelector(BaseEstimator, TransformerMixin): 14 def __init__(self, attribute_name): 15 self.attribute_name = attribute_name 16 17 def fit(self, x, y=None): 18 return self 19 20 def transform(self, x): 21 return x[self.attribute_name].values 22 23 24 # Load data 25 data_train = pd.read_csv('train.csv') 26 27 train_x = data_train.drop('Survived', axis=1) 28 train_y = data_train['Survived'] 29 30 # Data cleaning 31 cat_attribs = ['Pclass', 'Sex', 'Embarked'] 32 dis_attribs = ['SibSp', 'Parch'] 33 con_attribs = ['Age', 'Fare'] 34 35 # encoder: OneHotEncoder()、OrdinalEncoder() 36 cat_pipeline = Pipeline([ 37 ('selector', DataFrameSelector(cat_attribs)), 38 ('imputer', SimpleImputer(strategy='most_frequent')), 39 ('encoder', OneHotEncoder()), 40 ]) 41 42 dis_pipeline = Pipeline([ 43 ('selector', DataFrameSelector(dis_attribs)), 44 ('scaler', StandardScaler()), 45 ('imputer', SimpleImputer(strategy='most_frequent')), 46 ]) 47 48 con_pipeline = Pipeline([ 49 ('selector', DataFrameSelector(con_attribs)), 50 ('scaler', StandardScaler()), 51 ('imputer', SimpleImputer(strategy='mean')), 52 ]) 53 54 full_pipeline = FeatureUnion( 55 transformer_list=[ 56 ('con_pipeline', con_pipeline), 57 ('dis_pipeline', dis_pipeline), 58 ('cat_pipeline', cat_pipeline), 59 ] 60 ) 61 62 train_x_cleaned = full_pipeline.fit_transform(train_x) 63 64 cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=2) 65 66 clf1 = DecisionTreeClassifier(random_state=1992) 67 68 param_grid = [{ 69 'criterion': ['gini', 'entropy'], 70 'min_samples_split':[2, 4, 8, 16, 32], 71 'class_weight': [None, 'balanced'], 72 'ccp_alpha':[0, 1e-1, 1e-2, 1e-3], 73 }] 74 75 grid_search = GridSearchCV(clf1, param_grid=param_grid, cv=cv, scoring='accuracy', n_jobs=-1, return_train_score=True) 76 77 grid_search.fit(train_x_cleaned, train_y) 78 predicted_y = grid_search.predict(train_x_cleaned) 79 80 df_cv_results = pd.DataFrame(grid_search.cv_results_).sort_values(by='rank_test_score') 81 print('-------Result of DecisionTreeClassifier-------') 82 print(grid_search.best_params_) 83 print(accuracy_score(train_y, predicted_y)) 84 print(precision_score(train_y, predicted_y)) 85 print(recall_score(train_y, predicted_y)) 86 87 # 匯入預測資料,預測結果,並生成csv檔案 88 data_test = pd.read_csv('test.csv') 89 submission = pd.DataFrame(columns=['PassengerId', 'Survived']) 90 submission['PassengerId'] = data_test['PassengerId'] 91 92 test_x_cleaned = full_pipeline.fit_transform(data_test) 93 94 submission_DecisionTree = pd.DataFrame(submission, copy=True) 95 submission_DecisionTree['Survived'] = pd.Series(grid_search.predict(test_x_cleaned)) 96 97 submission_DecisionTree.to_csv('submission_DecisionTree.csv', index=False)
4.1 結果分析:
如果大家仔細檢視交叉驗證的結果 grid_serch.cv_results_。可以發現,部分引數下決策樹的 train_test_score 遠遠好於 train_test_score。這是明顯的過擬合現象,因此在引數中加入 'min_sample_split' 和 ’ccp_alpha‘ 防止過擬合。
和前幾篇一樣,將交叉驗證後的最優引數" ccp_alpha = 0.01, class_weight=None, criterion='entropy', min_samples_split=2 "用於預測集,並將結果上傳kaggle,結果如下:
訓練集 accuracy |
訓練集 precision |
訓練集 recall |
預測集 accuracy(需上傳kaggle獲取結果) |
|
樸素貝葉斯最優解 | 0.790 | 0.731 | 0.716 | 0.756 |
感知機 | 0.771 | 0.694 | 0.722 | 0.722 |
邏輯迴歸 | 0.807 | 0.781 | 0.690 | 0.768 |
線性SVM | 0.801 | 0.772 | 0.684 | 0.773 |
rbf核SVM | 0.834 | 0.817 | 0.731 | 0.785 |
決策樹 | 0.831 | 0.809 | 0.731 | 0.778 |