決策樹
決策樹定義:分類決策樹模型是一種描述實例進行分類的樹形結構,決策樹由節點和有向邊組成,結點有兩種類型:內部結點和葉結點。內部結點表示一個特徵或屬性,葉結點表示一個類。
決策樹示意圖如下:
其中,紅色結點表示葉子結點(最終的分類情況),綠色節點表示分類的規則。決策樹學習,是在給訓練數據集的情況下,構建一個決策樹模型,使它能夠對實例進行正確的分類。而決策樹的本質是從訓練集中歸納出一組分類規則,與訓練數據不相互矛盾的決策樹(即能對訓練數據進行正確分類的決策樹)可能有多個,也可能一個也沒有。我們需要的是一個與訓練數據矛盾較小的決策樹,同時具有橫好的泛化能力(對於未知數據的預測能力)。決策樹的學習主要分爲以下三個步驟:
1. 特徵選擇
目的:如果利用一個特徵進行分類的結果與隨機分類的結果沒有很大差別,則稱這個特徵是沒有分類能力的。特徵選擇在於選取對訓練數據具有分類能力的特徵,這樣能夠提高決策樹學習的效率。通常的選擇標準爲信息增益和信息增益比。
信息增益
問題描述:以下兩個決策樹都可以從此延續下去,那麼究竟哪一個特徵具有更好的分類能力,
直觀上如果一個特徵具有更好的分類能力,或者說,按照這一特徵將訓練數據集分割成子集,使得各個自己在當前條件下有最好的分類,那麼就選擇這個特徵。信息增益就能夠很好的表示這一直觀的準則。
熵:表示隨機變量的不確定性的度量,公式如下:
熵只與的分佈有關,與取值無關,這句注意理解,定義,熵是非負的。
條件熵:
隨機變量的聯合概率分佈爲
條件熵表示在已知隨機變量的條件下隨機變量的不確定性
其中
經驗熵,經驗條件熵:當熵和條件熵中的概率由數據估計(特別是極大似然估計)得到時,所對應的熵與條件熵分別稱爲經驗熵和經驗條件熵,就是從已知的數據計算得到的結果。
信息增益:
特徵對訓練數據集的信息增益,定義爲集合的經驗熵與特徵給定的條件下的經驗條件熵之差
熵與條件熵的差稱爲互信息.
決策樹中的信息增益等價於訓練數據集中的類與特徵的互信息。
考慮ID這種特徵, 本身是唯一的。按照ID做劃分, 得到的經驗條件熵爲0, 會得到最大的信息增益。所以, 按照信息增益的準則來選擇特徵, 可能會傾向於取值比較多的特徵。
例1:
數據集:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
import math
from math import log
import pprint
# 書上題目5.1
def create_data():
datasets = [['青年', '否', '否', '一般', '否'],
['青年', '否', '否', '好', '否'],
['青年', '是', '否', '好', '是'],
['青年', '是', '是', '一般', '是'],
['青年', '否', '否', '一般', '否'],
['中年', '否', '否', '一般', '否'],
['中年', '否', '否', '好', '否'],
['中年', '是', '是', '好', '是'],
['中年', '否', '是', '非常好', '是'],
['中年', '否', '是', '非常好', '是'],
['老年', '否', '是', '非常好', '是'],
['老年', '否', '是', '好', '是'],
['老年', '是', '否', '好', '是'],
['老年', '是', '否', '非常好', '是'],
['老年', '否', '否', '一般', '否'],
]
labels = [u'年齡', u'有工作', u'有自己的房子', u'信貸情況', u'類別']
# 返回數據集和每個維度的名稱
return datasets, labels
原始數據:
數據處理代碼:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
import math
from math import log
import pprint#頭文件
#計算熵
def calc_ent(datasets):
data_length = len(datasets)
label_count = {}
for i in range(data_length):
label = datasets[i][-1]
if label not in label_count:
label_count[label] = 0
label_count[label] += 1
ent = -sum([(p / data_length) * log(p / data_length, 2)
for p in label_count.values()])
return ent
#計算經驗條件熵
def cond_ent(datasets, axis=0):
data_length = len(datasets)
feature_sets = {}
for i in range(data_length):
feature = datasets[i][axis]#遍歷每一行數據 以每一列特徵爲標準
if feature not in feature_sets:
feature_sets[feature] = []
feature_sets[feature].append(datasets[i])#取得每一個子數據集
cond_ent = sum(
[(len(p) / data_length) * calc_ent(p) for p in feature_sets.values()])
return cond_ent
#計算信息增益
def info_gain(ent, cond_ent):
return ent - cond_ent
#遍歷數據。計算第一次整體信息增益
def info_gain_train(datasets):
count = len(datasets[0]) - 1 #總體的特徵
ent = calc_ent(datasets)
best_feture = []
for c in range(count):
c_info_gain = info_gain(ent , cond_ent(datasets, axis = c))
best_feture.append((c, c_info_gain))
print("特徵:%s, 信息增益%f" %(labels[c], c_info_gain))
best_ = max(best_feture, key=lambda x : x[-1])
return '特徵({})的信息增益最大,選擇爲根節點特徵'.format(labels[best_[0]])
if __name__ == "__main__":
datasets, labels = create_data()
info_gain_train(np.array(datasets))
代碼結果:
特徵:年齡, 信息增益0.085491
特徵:有工作, 信息增益0.333333
特徵:有自己的房子, 信息增益0.432538
特徵:信貸情況, 信息增益0.373850
'特徵(有自己的房子)的信息增益最大,選擇爲根節點特徵'
信息增益缺點:
以信息增益作爲劃分訓練數據集的特徵,存在偏向與選擇取值比較多的特徵的問題。使用信息增益比可以對這一問題進行校正,這也是特徵選擇的另一個準則。
信息增益比:
2. 決策樹的生成
ID3算法
輸入:訓練數據集, 特徵集,閾值
輸出:決策樹
- 如果屬於同一類,爲單節點樹,類作爲該節點的類標記,返回
- 如果是空集,置爲單節點樹,實例數最多的類作爲該節點類標記,返回(注:也即沒有特徵可以在劃分)
- 計算, 選擇信息增益最大的特徵
- 如果的信息增益小於,爲單節點樹,中實例數最大的類作爲類標記,返回
- 劃分若干非空子集,
- 訓練集,爲特徵集,遞歸調用前面步驟,得到,返回(注:已經使用過的特徵將不在繼續下面結點的特徵分裂)
代碼:
# 定義節點類 二叉樹
class Node:
def __init__(self, root=True, label=None, feature_name=None, feature=None):
self.root = root
self.label = label
self.feature_name = feature_name
self.feature = feature
self.tree = {}
self.result = {
'label:': self.label,
'feature': self.feature,
'tree': self.tree
}
def __repr__(self):
return '{}'.format(self.result)
def add_node(self, val, node):
self.tree[val] = node
def predict(self, features):
if self.root is True:
return self.label
return self.tree[features[self.feature]].predict(features)
class DTree:
def __init__(self, epsilon=0.1):
self.epsilon = epsilon
self._tree = {}
# 熵
@staticmethod
def calc_ent(datasets):
data_length = len(datasets)
label_count = {}
for i in range(data_length):
label = datasets[i][-1]
if label not in label_count:
label_count[label] = 0
label_count[label] += 1
ent = -sum([(p / data_length) * log(p / data_length, 2)
for p in label_count.values()])
return ent
# 經驗條件熵
def cond_ent(self, datasets, axis=0):
data_length = len(datasets)
feature_sets = {}
for i in range(data_length):
feature = datasets[i][axis]
if feature not in feature_sets:
feature_sets[feature] = []
feature_sets[feature].append(datasets[i])
cond_ent = sum([(len(p) / data_length) * self.calc_ent(p)
for p in feature_sets.values()])
return cond_ent
# 信息增益
@staticmethod
def info_gain(ent, cond_ent):
return ent - cond_ent
def info_gain_train(self, datasets):
count = len(datasets[0]) - 1
ent = self.calc_ent(datasets)
best_feature = []
for c in range(count):
c_info_gain = self.info_gain(ent, self.cond_ent(datasets, axis=c))
best_feature.append((c, c_info_gain))
# 比較大小
best_ = max(best_feature, key=lambda x: x[-1])
return best_
def train(self, train_data):
"""
數據集的收集爲pd
"""
_, y_train, features = train_data.iloc[:,
:-1], train_data.iloc[:, -1],train_data.columns[:-1]
#1.判斷是否單個節點
if len(y_train.value_counts()) == 1:
return Node(root = True, label= y_train.iloc[0])
#2.是否爲空節點
if len(features) == 0:
return Node(root = True,
label = y_train.value_counts().sort_values(
ascending = False).index[0])
#3.創建節點,選擇信息增益最大的特徵作爲節點
#3.1得到信息增益最大特徵信息
max_feature, max_info_gain = self.info_gain_train(np.array(train_data))
max_feature_name = features[max_feature]
#4.判斷是否小於閥值
if max_info_gain < self.epsilon:
return Node(root = True,
label=y_train.value_counts().sort_values(
ascending=False).index[0])
#5.構建Ag子集
node_tree = Node(
root=False, feature_name=max_feature_name, feature=max_feature)
feature_list = train_data[max_feature_name].value_counts().index
for f in feature_list:
sub_train_df = train_data.loc[train_data[max_feature_name] ==
f].drop([max_feature_name], axis=1)
# 6, 遞歸生成樹
sub_tree = self.train(sub_train_df)
node_tree.add_node(f, sub_tree)
return node_tree
def fit(self, train_data):
self._tree = self.train(train_data)
return self._tree
def predict(self, X_test):
return self._tree.predict(X_test)
if __name__ == "__main__":
datasets, labels = create_data()
data_df = pd.DataFrame(datasets, columns=labels)
dt = DTree()
tree = dt.fit(data_df)
print("tree:\n",tree)
print("測試結果:", end="")
print(dt.predict(["老年","否", "否", "一般"]))
ID3算法結果:
tree:
{'label:': None, 'feature': 2, 'tree': {'否': {'label:': None, 'feature': 1, 'tree': {'否': {'label:': '否', 'feature': None, 'tree': {}}, '是': {'label:': '是', 'feature': None, 'tree': {}}}}, '是': {'label:': '是', 'feature': None, 'tree': {}}}}
測試結果:
'否'
ID3算法缺點:
ID3算法只有樹的生成,所以該算法生成的樹容易過擬合。
C4.5 算法
輸入:訓練數據集, 特徵集,閾值
輸出:決策樹
- 如果屬於同一類,爲單節點樹,類作爲該節點的類標記,返回
- 如果是空集, 置爲單節點樹,實例數最多的作爲該節點類標記,返回
- 計算, 選擇信息增益比最大的特徵
- 如果的信息增益比小於,爲單節點樹,中實例數最大的類作爲類標記,返回
- 劃分若干非空子集,
- 訓練集,爲特徵集,遞歸調用前面步驟,得到,返回
Tip: ID3和C4.5在生成上,差異只在準則的差異。
C4.5 算法代碼:
# 定義節點類 二叉樹
class Node:
def __init__(self, root=True, label=None, feature_name=None, feature=None):
self.root = root
self.label = label
self.feature_name = feature_name
self.feature = feature
self.tree = {}
self.result = {
'label:': self.label,
'feature': self.feature,
'tree': self.tree
}
def __repr__(self):
return '{}'.format(self.result)
def add_node(self, val, node):
self.tree[val] = node
def predict(self, features):
if self.root is True:
return self.label
return self.tree[features[self.feature]].predict(features)
class DTree:
def __init__(self, epsilon=0.1):
self.epsilon = epsilon
self._tree = {}
# 熵
@staticmethod
def calc_ent(datasets):
data_length = len(datasets)
label_count = {}
for i in range(data_length):
label = datasets[i][-1]
if label not in label_count:
label_count[label] = 0
label_count[label] += 1
ent = -sum([(p / data_length) * log(p / data_length, 2)
for p in label_count.values()])
return ent
# 經驗條件熵
def cond_ent(self, datasets, axis=0):
data_length = len(datasets)
feature_sets = {}
for i in range(data_length):
feature = datasets[i][axis]
if feature not in feature_sets:
feature_sets[feature] = []
feature_sets[feature].append(datasets[i])
cond_ent = sum([(len(p) / data_length) * self.calc_ent(p)
for p in feature_sets.values()])
# 劃分特徵的信息熵(信息增益比的分母)
sub_ent = -sum([len(p) / data_length * log(len(p)/data_length
,2) for p in feature_sets.values()])
return cond_ent, sub_ent
# 信息增益
@staticmethod
def info_gain(ent, cond_ent, sub_ent):
return (ent - cond_ent)/sub_ent
def info_gain_train(self, datasets):
count = len(datasets[0]) - 1
ent = self.calc_ent(datasets)
best_feature = []
for c in range(count):
print(c)
cond_ent, sub_ent = self.cond_ent(datasets, axis=c)
c_info_gain = self.info_gain(ent, cond_ent, sub_ent)
best_feature.append((c, c_info_gain))
print("特徵:%s, 信息增益%f" %(labels[c], c_info_gain))
# 比較大小
best_ = max(best_feature, key=lambda x: x[-1])
return best_
def train(self, train_data):
"""
數據集的收集爲pd
"""
_, y_train, features = train_data.iloc[:,
:-1], train_data.iloc[:,
-1],train_data.columns[:-1]
#1.判斷是否單個節點
if len(y_train.value_counts()) == 1:
return Node(root = True, label= y_train.iloc[0])
#2.是否爲空節點
if len(features) == 0:
return Node(root = True,
label = y_train.value_counts().sort_values(
ascending = False).index[0])
#3.創建節點,選擇信息增益最大的特徵作爲節點
#3.1得到信息增益最大特徵信息
max_feature, max_info_gain = self.info_gain_train(np.array(train_data))
max_feature_name = features[max_feature]
#4.判斷是否小於閥值
if max_info_gain < self.epsilon:
return Node(root = True,
label=y_train.value_counts().sort_values(
ascending=False).index[0])
#5.構建Ag子集
node_tree = Node(
root=False, feature_name=max_feature_name, feature=max_feature)
feature_list = train_data[max_feature_name].value_counts().index
print("feature_list :",feature_list)
for f in feature_list:
sub_train_df = train_data.loc[train_data[max_feature_name] ==
f].drop([max_feature_name], axis=1)
# 6, 遞歸生成樹
sub_tree = self.train(sub_train_df)
node_tree.add_node(f, sub_tree)
return node_tree
def fit(self, train_data):
self._tree = self.train(train_data)
return self._tree
def predict(self, X_test):
return self._tree.predict(X_test)
if __name__ == "__main__":
datasets, labels = create_data()
data_df = pd.DataFrame(datasets, columns=labels)
dt = DTree()
tree = dt.fit(data_df)
print("tree:\n",tree)
print("測試結果:")
dt.predict(["老年","否", "否", "一般"])
3.決策樹的剪枝
目的:由決策樹算法生成的決策樹,往往對於訓練數據的分類很準確,但是對於未知的測試數據的分類卻沒有那麼準確,即出現過擬合,過擬合的原因在於學習時過多的考慮如何提高訓練數據的正確分類,從而構建過於複雜的決策樹,所以要考慮決策樹的複雜度,對已生成的決策樹進行簡化。而通常決策樹的剪枝往往是通過減少損失函數來體現的。如下:
樹的葉結點個數爲,是樹的葉結點,該結點有個樣本點,其中類的樣本點有個,爲葉結點上的經驗熵, 爲參數,決策樹學習的損失函數可以定義爲
其中
這時有
其中表示模型對訓練數據的誤差,表示模型複雜度,參數控制兩者之間的影響。
算法描述:
輸入:生成算法生成的整個樹,參數
輸出:修剪後的子樹
- $ \alpha(T_A)\leqslant C_\alpha(T_B)$計算每個結點的經驗熵
- 遞歸的從樹的葉結點向上回縮
假設一組葉結點回縮到其父結點之前與之後的整體樹分別是和,其對應的損失函數分別是和,如果則進行剪枝,即將父結點變爲新的葉結點- 返回2,直至不能繼續爲止,得到損失函數最小的子樹
CART(重點)
迴歸樹
算法描述
輸入:訓練數據集
輸出:迴歸樹
步驟:
-
遍歷變量,對固定的切分變量掃描切分點,得到滿足上面關係的
-
用選定的, 劃分區域並決定相應的輸出值
-
對兩個子區域調用(1)(2)步驟, 直至滿足停止條件
-
將輸入空間劃分爲個區域,生成決策樹:
代碼:
from numpy import *
def loadDataset():
"""加載本地數據"""
dataset = []
f = open("regData.txt")
fr = f.readlines()
for line in fr:
line = line.strip().split("\t")
linef = [float(li) for li in line]
dataset.append(linef)
# print(dataset)
dataSetMat = mat(dataset)
return dataSetMat
def calcErr(dataSetMat):
error = var(dataSetMat[:,-1])*shape(dataSetMat)[0]
return error
def chooseBestFeatureval2split(dataSetMat):
"""選擇分類節點"""
# 如果預測值相同
if(len(set(dataSetMat[:, -1].T.tolist()[0])) == 1): return None, None
bestFeature = 0
bestValue = 0
lowestErr = 100000
totalErr = calcErr(dataSetMat)
# 遍歷特徵
for feature in range(shape(dataSetMat)[1] - 1):
allValues = [d[feature] for d in dataSetMat.tolist()]
values = set(allValues) #每一個維度的特徵
for value in values:
leftChild, rightChild = splitByFeatVal(feature, value, dataSetMat)
if(shape(leftChild)[0] == 0 or shape(rightChild)[0] == 0): continue
curErr = calcErr(leftChild) + calcErr(rightChild)
if (curErr < lowestErr):
bestFeature = feature
bestValue = value
lowestErr = curErr
if (totalErr - lowestErr < 1): return None, None # 防止過擬合,並且作爲遞歸的終止條件
return bestFeature, bestValue
def splitByFeatVal(feature, value, dataSetMat):
"""分裂節點"""
leftChild = dataSetMat[nonzero(dataSetMat[:, feature] > value)[0],:]
rightChild = dataSetMat[nonzero(dataSetMat[:, feature] <= value)[0],:]
return leftChild, rightChild
def createRegTree(dataSetMat):
"""創建迴歸樹"""
feature, value = chooseBestFeatureval2split(dataSetMat)
if feature == None: return mean(dataSetMat[:, -1])#返回最終的預測結果
regTree = {}
regTree["feature"] = feature
regTree["value"] = value
leftChild, rightChild = splitByFeatVal(feature, value, dataSetMat)
regTree["leftChild"] = createRegTree(leftChild)
regTree["rightChild"] = createRegTree(rightChild)
return regTree
if __name__ == "__main__":
dataSetMat = loadDataset()
regTree = createRegTree(dataSetMat)
print(regTree)
分類樹
算法描述
分類樹用基尼指數選擇最優特徵,同時決定該特徵的最優二值切分點。
基尼指數:分類問題中,假設有個類,樣本點屬於第類的概率爲,則概率分佈的基尼指數定義爲:
分類樹算法描述:
輸入: 訓練數據集D,停止計算條件
輸出:CART 分類樹
1 訓練數據集爲,計算現有特徵對訓練數據集的基尼指數,此時對於每一個特徵A,對其可能取得每一個值a,根據此值將訓練樣本切分爲和兩部分,然後根據上式計算A=a基尼指數
2 在所有可能的特徵以及所有可能的值裏面選擇基尼指數最小的特徵及其切分點作爲最優的特徵及切分點,從結點生成兩個子結點,將訓練數據集分配到子結點中去3 遞歸的調用1 2 直到滿足停止的條件
4 生成分類決策樹
代碼
from numpy import *
def loadDataSet():
"""加載數據"""
dataSet = [[0, 0, 0],
[0, 1, 1],
[0, 2, 0],
[1, 0, 1],
[1, 1, 1],
[1, 2, 1],
[2, 0, 0],
[2, 1, 1],
[2, 2, 0]]
labels = ['color','shape']
return dataSet, labels
def calcGini(dataSet):
"""計算基尼指數"""
totalNum = shape(dataSet)[0]
labelNum = {}
gini = 0
for data in dataSet:
label = data[-1]
if label in labelNum:
labelNum[label] += 1
else:
labelNum[label] = 1
for key in labelNum:
p = labelNum[key]/ totalNum
gini += p*(1-p)
return gini
def chooseBestFeatVal2Split(dataSet):
"""選擇最優切分點"""
# 1.如果沒有切分的變量
if(len(dataSet[0]) == 1):
print("dfadfafa")
return None, None
# 2.所有的目標變量的相等
if(len(set([d[-1] for d in dataSet])) == 1):
return None, None
# 3.尋找最佳切分點
bestfeatur = 0
bestValue = 0
lowestGini = 1000000
totalGini = calcGini(dataSet)
totalNum = shape(dataSet)[0]
# 4.遍歷特徵
for feature in range(shape(dataSet)[1] - 1):
allValues = [d[feature] for d in dataSet]
values = set(allValues)
# 5.遍歷每個特徵所可能要取的值
for value in values:
leftChild, rightChild = splitByFeature(feature, value, dataSet)
# 6.如果左子樹或右子樹無節點
if(shape(leftChild)[0] == 0 or shape(rightChild)[0] == 0): return None, None
# 7.計算劃分後的基尼指數
leftNum = shape(leftChild)[0]
rightNum = shape(rightChild)[0]
curGini = leftNum/totalNum *calcGini(leftChild) + rightNum/totalNum*calcGini(rightChild)
if (curGini < lowestGini):
bestfeatur = feature
bestValue = value
lowestGini = curGini
if(totalNum - lowestGini < 0.00001): return None, None
return bestfeatur, bestValue
def splitByFeature(feature, value, dataSet):
dataSet = mat(dataSet)
leftChild = dataSet[nonzero(dataSet[:, feature] == value)[0],:].tolist()
rightChild = dataSet[nonzero(dataSet[:, feature] != value)[0],:].tolist()
return leftChild, rightChild
def checkIsOneCateg(newDataSet):
"""是否劃分節點是唯一的節點"""
flag = False
categList = [data[-1] for data in newDataSet]
category = set(categList)
if len(category) == 1:
flag = True
return flag
def majorityCateg(newDataSet):
"""主要的類別"""
categCount = {}
categList = [data[-1] for data in newDataSet]
for c in categList:
if c not in categCount:
categCount[c] = 1
else:
categCount[c] += 1
sortedCateg = sorted(categCount.item(), key=lambda x:x[1], reverse=True)
return sortedCateg
def createClassifTree(dataSet):
"""創建分類樹"""
feature, value = chooseBestFeatVal2Split(dataSet)
if feature == None and checkIsOneCateg(dataSet):
return dataSet[0][-1]
if feature == None and not checkIsOneCateg:
return majorityCateg(dataSet)
classifTree = {}
classifTree["featIndex"] = feature
classifTree["value"] = value
leftChild, rightChild = splitByFeature(feature, value, dataSet)
classifTree["leftChild"] = createClassifTree(leftChild)
classifTree["rightChild"] = createClassifTree(rightChild)
return classifTree
if __name__ == '__main__':
dataSet, labels = loadDataSet()
classifTree = createClassifTree(dataSet)
print(classifTree)
CART剪枝
輸入:CART算法生成的決策樹T0
輸出: 最優的決策樹Ta
1 設k=0,T=T0
2 設=Na(正無窮)
3自下而上的對內部結點t進行計算C(Tt),|Tt|和g(t)=,=min(,g(t))
4 對g(t)=的內部結點進行剪枝,並對葉結點t以多數表決法決定其類,得到樹T
5 設k=k+1,k=,Tk=T
6 如果Tk不是由根結點及兩個葉結點構成的樹,回到步驟3,否則另Tk=Tn