1. 程式人生 > >【機器學習】分類決策樹基本介紹+程式碼實現

【機器學習】分類決策樹基本介紹+程式碼實現

參考:https://blog.csdn.net/u012351768/article/details/73469813

1.基礎知識

基於特徵對例項進行分類。

優點:複雜度低,輸出結果易於理解,缺失中間值不敏感,可處理不相關特徵資料。

缺點:過度匹配。

適用資料型別:標稱和數值型。(數值型需要離散化)

構建決策樹時,在每次分支時都要選擇最能區分資料的特徵。

2.劃分資料集依據

2.1 資訊增益(ID3),越大越好

D:資料集

A:特徵A

K:類別的集合 k~K

D的經驗熵:

H(D) = -\sum_{k=1}^{K}\frac{|D^k|}{|D|}log_2\frac{|D^k|}{|D|},(表示資料集D的純度,H越小,純度越高

特徵A將資料集D分成N個數據集,特徵A對資料集D的經驗條件熵:

H(D|A) = \sum_{i = 1}^{N} \frac{|D_i|}{|D|}H(D_i),(即給定特徵A,計算每個子資料集的純度再求和,表示給定A後資料集的純度,數值越小純度越高

特徵A對資料集的資訊增益:

G(D, A) = H(D) - H(D|A),(即特徵A幫助提升的純度的大小,值越大越好

2.2 資訊增益率(C4.5)越大越好

由於資訊增益會偏向取值較多的特徵(過擬合),解釋:當特徵A取值很多,則劃分出的組數增多,使得H(D|A)減小,則資訊增益增大。但是過於精細的劃分,會使得分類失去意義。(比如按照身份證號給人分類,則每一個人都是一類)。

特徵A對資料集D的資訊增益率:

G_r(D,A) = \frac{G(D,A)}{H(A)}

其中,特徵A將資料集分成N類,則對於所有特徵A相對應的資料的N個類的資訊經驗熵為(即表示了特徵A為類別標籤時,資料D的純度):

H(A) = - \sum_{i=1}^{N}\frac{|D_{A}^i|}{|D_A|}log_2\frac{|D_{A}^i|}{|D_A|}

因為當A將資料集D分成太多類時,其純度降低,H(A)增加,相當於給資訊增益添加了一項懲罰項。

2.3 Gini係數(CART)越小越好

基尼指數:從資料集裡隨機選取子項,度量其被錯誤分類到其他分組裡的概率。基尼係數指資料的不純度,越小越好。

CART是一個二叉樹分類。

K是資料集D的標籤集合:k~K

資料D的基尼係數:

Gini(D) =1- \sum_{k=1}^{K}P_k^2 = 1- \sum_{k=1}^{K}(\frac{|D^k|}{|D|})^2

若特徵A將資料D分成兩個資料集:

Gini(D,A) = \frac{|D_1|}{|D|}Gini(D_1) + \frac{|D_2|}{|D|}Gini(D_2)

但是若當特徵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)