【機器學習】分類決策樹基本介紹+程式碼實現
參考:https://blog.csdn.net/u012351768/article/details/73469813
1.基礎知識
基於特徵對例項進行分類。
優點:複雜度低,輸出結果易於理解,缺失中間值不敏感,可處理不相關特徵資料。
缺點:過度匹配。
適用資料型別:標稱和數值型。(數值型需要離散化)
構建決策樹時,在每次分支時都要選擇最能區分資料的特徵。
2.劃分資料集依據
2.1 資訊增益(ID3),越大越好
D:資料集
A:特徵A
K:類別的集合 k~K
D的經驗熵:
,(表示資料集D的純度,H越小,純度越高)
特徵A將資料集D分成N個數據集,特徵A對資料集D的經驗條件熵:
,(即給定特徵A,計算每個子資料集的純度再求和,表示給定A後資料集的純度,數值越小純度越高)
特徵A對資料集的資訊增益:
,(即特徵A幫助提升的純度的大小,值越大越好)
2.2 資訊增益率(C4.5)越大越好
由於資訊增益會偏向取值較多的特徵(過擬合),解釋:當特徵A取值很多,則劃分出的組數增多,使得H(D|A)減小,則資訊增益增大。但是過於精細的劃分,會使得分類失去意義。(比如按照身份證號給人分類,則每一個人都是一類)。
特徵A對資料集D的資訊增益率:
其中,特徵A將資料集分成N類,則對於所有特徵A相對應的資料的N個類的資訊經驗熵為(即表示了特徵A為類別標籤時,資料D的純度):
因為當A將資料集D分成太多類時,其純度降低,H(A)增加,相當於給資訊增益添加了一項懲罰項。
2.3 Gini係數(CART)越小越好
基尼指數:從資料集裡隨機選取子項,度量其被錯誤分類到其他分組裡的概率。基尼係數指資料的不純度,越小越好。
CART是一個二叉樹分類。
K是資料集D的標籤集合:k~K
資料D的基尼係數:
若特徵A將資料D分成兩個資料集:
但是若當特徵A將資料集D分成超過兩個資料集時,需要計算以每一個取值作為劃分點,對樣本D劃分之後子集的純度Gini(D,Ai),(其中Ai 表示特徵A的可能取值),然後從所有的可能劃分的Gini(D,Ai)中找出Gini指數最小的劃分,這個劃分的劃分點,便是使用特徵A對樣本集合D進行劃分的最佳劃分點。
對各個概念的理解:熵描述了資訊的不確定性,我們的目標是劃分資料集D,使得劃分後的資料集的不確定性降低。資訊增益描述了特徵A對於降低資料集D的不確定性的幫助,資訊增益越大,說明A的引入增加了資訊,降低了D的不確定性。然而,由於資訊增益會偏向取值較多的特徵,為了解決這個特徵之間的選擇不平衡的現象,我們添加了一項懲罰項,引入了資訊增益率。
3. ID3, C4.5, CART比較
任務 | 準則 | 適合資料 | 優點 | 缺點 | |
ID3 | 分類 | 資訊增益最大化 | 標稱 | 基礎演算法,結構簡單 | 偏好取值較多的特徵,無法處理連續值 |
C4.5 | 分類 | 資訊增益率最大化 | 標稱,連續(二分法) | 解決ID3不平衡偏好,可處理連續值 | 過擬合(剪枝) |
迴歸 | 平方誤差最小化 | ||||
CART | 分類 | 基尼指數最小化 | 標稱,連續(二分法) | 既可以分類,又可以迴歸 | 二叉樹,當離散型特徵超過兩個取值時,需要逐個比較選擇。過擬合(剪枝) |
迴歸 | 平方誤差最小化 |
4. 決策樹剪枝
構建決策樹時,根據最優的特徵進行構建,往往會使得所得的樹分支過多,過擬合。因此需要對樹進行剪枝。
預剪枝:邊構建樹邊剪枝。當到某一節點無法達到要求時,即停止分支。
後剪枝:先完整地構建出決策樹,然後自下而上地進行剪枝。
判斷是否剪枝的標準:剪枝前後的泛化能力,若剪枝後泛化能力增強,則剪枝,否則不剪枝。
泛化能力的判斷方法:留出法。即在原資料集中拿出一部分做為驗證集,剪枝時,判斷該分支分與部分對驗證集分類的影響(準確率的影響)。
注意:因為預剪枝含有“貪心”的思想,即一旦某個節點分支不能提高泛化能力,立即停止。此時可能會忽略後續泛化能力高的分支。因此預剪枝可能會造成“欠擬合”。
5. “連續屬性&缺失屬性”的分類問題
連續屬性:使用二分法,屬性值排序後取中點。C4.5使用該方法。
缺失屬性:指樣本資料不完整,某些屬性的值缺失。當使用含有缺失屬性的資料構建決策樹時,一個方法是捨棄含有缺失屬性的樣本;當對含有缺失屬性的資料進行分類時,將含有未知屬性的樣本用不同概率劃分到不同的子節點。
6.程式碼實現
參考:《機器學習實戰》
原始碼地址以及資料:https://github.com/JieruZhang/MachineLearninginAction_src
from math import *
import operator
import pickle
#計算熵
def entropy(data):
num = len(data)
num_labels ={}
for vec in data:
num_labels[vec[-1]] = num_labels.get(vec[-1],0)+1
ent = 0.0
for key in num_labels.keys():
prob = float(num_labels[key])/num
ent -= prob*log(prob,2)
return ent
#根據給定特徵劃分資料集, axis是特徵對應的編號,value是匹配的值
def splitDataSet(data, axis, value):
res = []
for vec in data:
if vec[axis] == value:
res.append(vec[:axis] + vec[axis+1:])
return res
#遍歷資料集,計算每種特徵對應的資訊增益,選出增益最小的,該特徵即為用於分類的特徵
def chooseFeature(data):
num_feature = len(data[0])-1
#計算H(D)
base_ent = entropy(data)
max_gain = 0.0
best_feature = 0
for i in range(num_feature):
#找出feature的種類
uniqueFeature = set([vec[i] for vec in data])
#對每個feature計算其對應的條件資訊熵
sub_ent = 0.0
for feature in uniqueFeature:
sub_data = splitDataSet(data, i, feature)
prob = len(sub_data)/float(len(data))
sub_ent += prob*entropy(sub_data)
#計算每個feature對應的資訊增益
gain = base_ent - sub_ent
#選擇最大的資訊增益,其對應的feature即為最佳的feature
if gain > max_gain:
max_gain = gain
best_feature = i
return best_feature
#遞迴構建決策樹
#原始資料集-> 遞迴地基於最好的屬性劃分資料集->終止條件:所有屬性已經用過或劃分後的資料集每個集合只屬於一個label,
#若所有屬性用完但是每個劃分屬性不都屬於一個label,就使用“多數表決”的方法。
#多數表決函式
def majority(classes):
classCount = {}
for item in classes:
classCount[item] = classCount.get(item,0) + 1
sortedClassesCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse=True)
#返回表決最多的label
return sortedClassCount[0][0]
#遞迴構建樹,存入字典中,以便隨後的視覺化.feature_name是feature的名字,僅是為了視覺化方便。
def createTree(data, feature_names):
#找出data資料集中所有的labels
classList = [item[-1] for item in data]
#如果只屬於一種label,則停止構建樹
if len(set(classList)) == 1:
return classList[0]
#若果所有features屬性都已經使用完,也停止構建樹
#每次用完一個特徵都會刪除,這樣最後資料集data中只剩下最後一位的label位
if len(data[0]) == 1:
return majority(classList)
bestFeat = chooseFeature(data)
bestFeatName = feature_names[bestFeat]
#bestFeatName是用於分離資料集的特徵的名字,作為根
tree = {bestFeatName:{}}
#del只刪除元素,但是原來的index不變
sub_names = feature_names[:]
del(sub_names[bestFeat])
uniqFeats = set([item[bestFeat] for item in data])
#對於每個feature,遞迴地構建樹
for feature in uniqFeats:
tree[bestFeatName][feature] = createTree(splitDataSet(data,bestFeat,feature), sub_names)
return tree
#分類函式,也是遞迴地分類(從根到葉節點)
def classify(tree, feature_names, test_data):
#找到根,即第一個用於分類的特徵
root = list(tree.keys())[0]
#找到根對應的子樹
sub_trees = tree[root]
#找出對應該特徵的index
feat_index = feature_names.index(root)
#對所有子樹,將測試樣本劃分到屬於其的子樹
for key in sub_trees.keys():
if test_data[feat_index] == key:
#檢查是否還有子樹,或者已經到達葉節點
if type(sub_trees[key]).__name__ == 'dict':
#若還可以再分,則繼續向下找
return classify(sub_trees[key], feature_names,test_data)
else:
#否則直接返回分的類
return sub_trees[key]
#儲存樹(儲存模型)
def stroeTree(tree, filename):
fw = open(filename,'w')
pickle.dump(tree,fw)
fw.close()
return
#提取樹
def grabTree(filename):
fr = open(filename)
return pickle.load(fr)
#預測隱形眼鏡型別,已知資料集,如何判斷患者需要佩戴的眼鏡型別
fr = open('lenses.txt')
lenses = [line.strip().split('\t') for line in fr.readlines()]
feature_names = ['age', 'prescript','astigmatic','tearRate']
#構建樹
tree = createTree(lenses,feature_names)
#視覺化樹
createPlot(tree)
#測試
print(classify(tree,feature_names,['pre','myope','no','reduced']))
sklearn 實現,sklearn使用的是cart分類樹。
from sklearn import tree
def DT(train_data, test_data, train_class):
clf = tree.DecisionTreeClassifier()
clf = clf.fit(train_data, train_class)
return clf.predict(test_data)