上一篇介紹了決策樹之分類樹構造的幾種方法,本文主要介紹使用CART算法構建迴歸樹及剪枝算法實現。主要包括如下內容:git
一、CART迴歸樹的介紹算法
二、二元切分的實現學習
三、總方差法劃分特徵測試
四、迴歸樹的構建spa
五、迴歸樹的測試與應用code
六、剪枝算法orm
1、CART迴歸樹的介紹blog
迴歸樹與分類樹比較相似,不一樣的是分類樹最後的決策的結果是離散型的值,迴歸樹決策的結果是輸出一個實數。遞歸
2、二元切分的實現ci
CART算法作迴歸樹時,只作二元切分,最後生成的樹是一棵二叉樹。切分代碼以下:
1 def bin_split_data_set(data_set, feature, value): 2 """對數據集進行二元切分""" 3 # np.nonzero(data_set[:,feature] > value)[0] 返回feture值 大於 value 的行號 4 mat0 = data_set[np.nonzero(data_set[:, feature] == value)[0], :] 5 mat1 = data_set[np.nonzero(data_set[:, feature] != value)[0], :] 6 return mat0, mat1
因爲使用的數據集特徵是枚舉類型的,因此這裏條件是 【等於】 np.nonzero(data_set[:, feature] == value,假如爲連續數值型的,可使用【小於】或【大於】
3、總方差法劃分特徵
上一節講到分類樹有三種經常使用劃分特徵的方法,分別是信息增益,增益率,和基尼指數。CART迴歸樹這裏使用最小總方差法選取劃分特徵。
1 def reg_leaf(data_set): 2 """生成葉子結點""" 3 # 計算平均值 4 result = tools.filter_reg_values(data_set) 5 value = np.mean(result) 6 return value 7 8 9 def reg_err(data_set): 10 """總方差""" 11 # np.val 計算標準差 12 result = tools.filter_reg_values(data_set) 13 return np.var(result) * np.shape(data_set)[0] 14 15 def choose_best_split(data_set, ops=(1, 4)): 16 """ 17 選取最好的劃分特徵值 18 data_set:數據集 19 ops(x,y):x--偏差減小最小值 y--分類後樣本最少個數 20 """ 21 tols = ops[0] 22 toln = ops[1] 23 24 # 全部分類相同(mat.flatten() 將矩陣數據壓平) 25 if len(set(np.array(data_set[:, -1].flatten()[0])[0])) == 1: 26 return None, reg_leaf(data_set) 27 28 m, n = np.shape(data_set) 29 s = reg_err(data_set) 30 31 # np.inf 無限大的數 32 best_s = np.inf 33 best_index = 0 34 best_value = 0 35 36 # 遍歷每個特徵 37 for feat_index in range(n - 1): 38 # 遍歷當前特徵的全部值 39 for value in set(flatten(np.array(data_set)[:, feat_index])): 40 mat0, mat1 = bin_split_data_set(data_set, feat_index, value) 41 # 分類後樣本個數較少,則退出本次循環 42 if np.shape(mat0)[0] < toln or np.shape(mat1)[0] < toln: 43 continue 44 # 計算新的偏差 45 new_s = reg_err(mat0) + reg_err(mat1) 46 47 # 更新最小偏差 48 if new_s < best_s: 49 best_index = feat_index 50 best_value = value 51 best_s = new_s 52 53 # 若是偏差減少不大,則退出 54 if s - best_s < tols: 55 return None, reg_leaf(data_set) 56 57 # 若是切片分出的數據集很小,就退出 58 mat0, mat1 = bin_split_data_set(data_set, best_index, best_value) 59 if mat0.shape[0] < toln or mat1.shape[0] < toln: 60 return None, reg_leaf(data_set) 61 62 return best_index, best_value
4、迴歸樹的構建
遞歸建立樹形結構:
1 def create_tree(data_set, ops=(1, 4)): 2 """建立迴歸樹""" 3 feat, val = choose_best_split(data_set, ops) 4 if feat is None: 5 return val 6 ret_tree = dict() 7 ret_tree['feature'] = feat 8 ret_tree['value'] = val 9 10 # 左右子樹 11 left_data, right_data = bin_split_data_set(data_set, feat, val) 12 ret_tree['left'] = create_tree(left_data, ops) 13 ret_tree['right'] = create_tree(right_data, ops) 14 15 return ret_tree
5、迴歸樹的測試與應用
選取UCI上面的用於迴歸的數據集,分爲訓練集 和 測試集。
生成的迴歸決策樹圖形以下:
6、決策樹的修剪:
決策樹在構造以後,可能會出現過分擬合的現象,決策樹的複雜度過大,預測效果並不理想,因此須要對決策樹進行剪枝。剪枝就是將決策樹的枝葉適當減去,使決策樹更加精簡,預測效果更加準確。根據剪枝所出現的時間點不一樣,分爲預剪枝和後剪枝。預剪枝是在決策樹的生成過程當中進行的;後剪枝是在決策樹生成以後進行的。
預剪枝:
在構造決策樹的同時進行剪枝。爲了不過擬合,能夠設定一個閾值,如決策樹的高度等,使構造的決策樹不能大於此閾值,因爲事先定好閾值,這種方法實際中的效果並很差。決策樹構造完成後進行剪枝。剪枝的過程是對擁有一樣父節點的一組節點進行檢查,判斷若是將其合併,熵的增長量是否小於某一閾值。若是確實小,則這一組節點能夠合併一個節點,其中包含了全部可能的結果。
後剪枝:
後剪枝的剪枝過程是刪除一些子樹,而後用其葉子節點代替,這個葉子節點所標識的類別經過大多數原則肯定。所謂大多數原則,是指剪枝過程當中, 將一些子樹刪除而用葉節點代替,這個葉節點所標識的類別用這棵子樹中大多數訓練樣本所屬的類別來標識。
後剪枝算法有不少種,這裏簡要介紹兩種:
Reduced-Error Pruning (REP,錯誤率下降剪枝)
用訓練樣本構造的決策樹可能過分擬合,因此再用測試數據集去修正。對於徹底決策樹中的每個非葉子節點的子樹,咱們嘗試着把它替換成一個葉子節點,該葉子節點的類別咱們用子樹所覆蓋訓練樣本中存在最多的那個類來代替,這樣就產生了一個簡化決策樹,而後比較這兩個決策樹在測試數據集中的表現,若是簡化決策樹在測試數據集中的錯誤比較少,那麼該子樹就能夠替換成葉子節點,若是。
Pessimistic Error Pruning (PEP,悲觀剪枝)
PEP剪枝算法是在C4.5決策樹算法中提出的, 把一顆子樹(具備多個葉子節點)用一個葉子節點來替代的話,比起REP剪枝法,它不須要一個單獨的測試數據集。
REP剪枝算法的代碼:
1 def is_tree(obj): 2 """判斷是不是樹""" 3 return isinstance(obj, dict) 4 5 6 def get_mean(tree): 7 """返回樹的平均值""" 8 if is_tree(tree['right']): 9 tree['right'] = get_mean(tree['right']) 10 if is_tree(tree['left']): 11 tree['left'] = get_mean(tree['left']) 12 13 value = (tree['left'] + tree['right']) / 2.0 14 return float('%.2f' % value) 15 16 17 def prune(tree, test_data): 18 """對樹進行剪枝""" 19 # 測試數據爲空 20 if np.shape(test_data)[0] == 0: 21 return get_mean(tree) 22 23 # 切分測試數據 24 if is_tree(tree['left']) or is_tree(tree['right']): 25 l_set, r_set = tools.bin_split_data_set(test_data, tree['feature'], tree['value']) 26 27 # 遞歸對左右子樹進行剪枝 28 if is_tree(tree['left']): 29 tree['left'] = prune(tree['left'], l_set) 30 if is_tree(tree['right']): 31 tree['right'] = prune(tree['right'], r_set) 32 33 # 左右都爲葉子結點 34 if not is_tree(tree['left']) and not is_tree(tree['right']): 35 l_set, r_set = tools.bin_split_data_set(test_data, tree['feature'], tree['value']) 36 37 # 未合併的偏差 38 error_no_merge = sum(tools.filter_reg_values(l_set) - tree['left'], 2) + sum( 39 np.power(tools.filter_reg_values(r_set) - float(tree['right']), 2)) 40 # 合併左右結點以後的偏差 41 tree_mean = (tree['left'] + tree['right']) / 2.0 42 error_merge = sum(np.power(tools.filter_reg_values(test_data) - tree_mean, 2)) 43 44 # 若是合併後偏差減少,則進行合併 45 if error_merge < error_no_merge: 46 print('merging') 47 return float('%.2f' % tree_mean) 48 else: 49 return tree 50 else: 51 return tree
剪枝後生成的決策樹以下:
對比剪枝前和剪枝後的決策樹,剪枝後的決策樹更加精簡,相應的準確率也更高。
本文的完整代碼見https://gitee.com/beiyan/machine_learning/tree/master/decision_tree
本文只是簡單實現CART迴歸樹及剪枝算法,隨着決策樹的研究,也出現不少改進的或者新的劃分算法和剪枝算法,後面慢慢學習。