機器學習實戰讀書筆記系列3——決策樹

1.算法定義

從數據集合中提取出一系列規則,可以更好的理解數據的內在含義
與KNN一樣,是結果確定的分類算法,,數據實例會被明確分到某個類中
優點:計算複雜度不高,輸出結果易於理解,對中間值的缺失不敏感,可以處理不相關特徵的數據
缺點:可能會產生過度匹配問題
適用於數值型與標稱型數據。樹構造算法只適用於標稱型數據,數值型數據必須離散化

2.決策樹構造

需考慮的問題:1.數據集的哪個特徵在分類時起決定性作用——評估特徵,直到所有具有相同類型的數據均在一個數據子集內

創建分支的僞代碼createBranch函數

檢測數據集中的每一個子項是否屬於同一分類:
    If so return 類標籤;
    Else
           尋找劃分數據集的最好的特徵
           劃分數據集
           創建分支節點
                  for 每個劃分的子集
                        調用函數createBranch並增加返回結果到分支節點中
             return 分支節點

2.1信息增益

定義1:劃分數據集之前之後信息發生的變化成爲信息增益,獲得信息增益最高的就是的特徵是最好的選擇。——熵的減少or無序度的減少
定義2:集合信息的度量方式稱爲香農熵或熵(信息的期望值)。——度量數據集的無序程度。熵值越高,則混合的數據也越多。數據集中類別數增加,則相應的熵增加。

如何計算熵?
在這裏插入圖片描述
表示所有類別所有可能值包含的信息期望值,log裏表示選擇某個分類的概率
如何度量信息增益?
熵相減

2.2劃分數據集

方法:——獲取最大信息增益,熵減少,無序度減少(屬性劃分方法不一樣)
1.二分法
2.ID3算法(劃分標稱型數據集,無法直接處理數值型數據)——信息增益
3.C4.5——信息增益率
4.CART——GINI基尼指數(gini impurity 基尼不純度)
注意:這些算法在運行時並不是在每次劃分時都消耗特徵,即特徵數目並不是在每次劃分數據集時都會減少
原則:將無序的數據變得更加有序——如何度量?——信息論量化度量信息——對每個特徵劃分數據集的結果計算一次信息熵

2.3遞歸構建決策樹

遞歸終止條件:
1.程序遍歷完所有劃分數據集的屬性。如果處理完所有屬性,類標籤依然不唯一,應用多數表決的方法決定改葉子節點的分類
2.每個分支下的實例都具有相同的分類
決策樹上有兩種節點:葉子節點(含有類標籤)和判斷節點
使用python內嵌的數據結構字典存儲節點信息,並不構造新的數據結構

3.使用matplotlib註解繪製(plot)樹形圖

3.1 matplotlib 註解

註解工具annotations:可以在數據圖形上添加文本註釋

3.2 構造註解樹

4.測試與存儲分類器

測試

需要決策樹以及用於構造樹的標籤向量(特徵標籤列表用於確定用於劃分數據集的特徵在哪個位置)
比較測試數據與決策樹上的數值,遞歸執行進入葉子節點
測試數據定義爲葉子節點所屬的類型

存儲——可持久化分類器

python模塊序列化對象pickle,在磁盤上保存對象並在需要的時候讀取調用
任何對象都可以執行序列化操作,包括字典對象
預先提煉並存儲數據集中的知識,在對事物進行分類時再使用這些知識

5.應用實例

使用決策樹預測隱形眼鏡類型(根據眼部狀況推薦眼鏡)
存在問題

決策樹能很好匹配實驗數據,但是匹配選項可能過多——過度匹配
解決:裁剪決策樹,去掉一些不必要的葉子結點(如果葉子結點只能增加少許信息,則可以刪除該節點,將他加入到其他葉子結點中)

'''
Created on Sep 20, 2018
Decision Tree Source Code for Machine Learning in Action Ch. 3
@author: Peter Harrington
'''
from math import log
import operator

def createDataSet():
    dataSet = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no']]
    labels = ['no surfacing','flippers'] #特徵值:不浮出水面是否可以生存;是否有腳蹼;
    #change to discrete values
    return dataSet, labels

#計算給定數據集的香農熵
def calcShannonEnt(dataSet):
    numEntries = len(dataSet)#計算數據集中實例總數
    labelCounts = {}
    for featVec in dataSet: #統計各個類別及出現的次數
        currentLabel = featVec[-1] #獲取數據集中最後一列,是否屬於魚類
        if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
    shannonEnt = 0.0
    for key in labelCounts:
        prob = float(labelCounts[key])/numEntries #魚與非魚的概率
        shannonEnt -= prob * log(prob,2) #log base 2
    return shannonEnt
    

#按照給定特徵劃分數據集
def splitDataSet(dataSet, axis, value):  #參數:帶劃分的數據集;劃分數據的特徵;需要返回的特徵的值
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value: #按照某個特徵劃分數據集,需要將所有符合元素抽取出來
            reducedFeatVec = featVec[:axis]     #chop out axis used for splitting
            reducedFeatVec.extend(featVec[axis+1:])  #extend將列表中的每個元素加進來; append()將整個列表當做一個元素加進來
            retDataSet.append(reducedFeatVec)
    return retDataSet


#遍歷整個數據集,循環計算香農熵和splitDataSet()函數,找到最好的劃分方式
#選擇最好的數據集劃分方式    
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1      #獲取特徵的個數,最後一列爲標籤 the last column is used for the labels
    baseEntropy = calcShannonEnt(dataSet)  #整個數據集原始香農熵(無序度量值)
    bestInfoGain = 0.0; bestFeature = -1
    for i in range(numFeatures):       #循環遍歷所有特徵
        featList = [example[i] for example in dataSet]#獲取這一列特徵的所有值create a list of all the examples of this feature
        uniqueVals = set(featList)       #獲取某一列獨立的特徵值 get a set of unique values
        newEntropy = 0.0
        for value in uniqueVals: #遍歷當前特徵中的所有唯一的屬性值
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet)/float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet)     
        infoGain = baseEntropy - newEntropy     #calculate the info gain; ie reduction in entropy
        if (infoGain > bestInfoGain):       #compare this to the best gain so far
            bestInfoGain = infoGain         #if better than current best, set to best
            bestFeature = i
    return bestFeature                      #returns an integer

#葉子節點中類標籤不唯一,採取多數表決機制
def majorityCnt(classList):
    classCount={} #存儲類標籤及其次數
    for vote in classList:
        if vote not in classCount.keys(): classCount[vote] = 0
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0] #返回次數最多的類別


#創建樹
def createTree(dataSet,labels): # 參數:數據集,標籤列表
    classList = [example[-1] for example in dataSet] #獲取標籤
    #if是終止條件
    if classList.count(classList[0]) == len(classList): #類別完全相同則停止劃分
        return classList[0]#stop splitting when all of the classes are equal
    if len(dataSet[0]) == 1: #遍歷完所有特徵時返回出現次數最多的
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)
    bestFeatLabel = labels[bestFeat] #返回特徵名
    myTree = {bestFeatLabel:{}} #存儲樹的所有信息,嵌套代表葉子節點信息的字典數據
    del(labels[bestFeat]) #刪除已劃分過數據集的特徵
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        subLabels = labels[:]       #copy all of labels, so trees don't mess up existing labels
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
    return myTree                            
    
#使用決策樹的分類函數
def classify(inputTree,featLabels,testVec): #訓練好的樹,特徵標籤列表,待測試數據
    firstStr = list(inputTree.keys())[0] #獲得樹的第一個鍵,第一個劃分的特徵
    secondDict = inputTree[firstStr] #獲得內嵌的第二個字典
    featLabels=['no surfacing','flippers']
    featIndex = featLabels.index(firstStr) #確定劃分標籤的索引
    key = testVec[featIndex] #獲得測試集上的特徵
    valueOfFeat = secondDict[key] #測試集在這個劃分特徵上的取值
    if isinstance(valueOfFeat, dict): # 判斷當前特徵值對應的是否是個葉節點。是葉節點則對應一個值,不是葉節點則對應一個字典
        #sinstance() 函數來判斷一個對象是否是一個已知的類型
        classLabel = classify(valueOfFeat, featLabels, testVec)
    else: classLabel = valueOfFeat  #是葉節點,返回當前節點的分類標籤
    return classLabel

#使用pickle模塊存儲決策樹
def storeTree(inputTree,filename):
    import pickle
    fw = open(filename,'wb')
    pickle.dump(inputTree,fw)
    fw.close()
    
def grabTree(filename):
    import pickle
    fr = open(filename,'rb')
    return pickle.load(fr)
    

if __name__=="__main__":
    dataSet, labels=createDataSet()
    
    
    print("香農熵=",calcShannonEnt(dataSet)) #計算數據的香農熵
    print(splitDataSet(dataSet,0,1)) #根據某個特徵劃分得到的數據集
    print(chooseBestFeatureToSplit(dataSet)) #選擇最好的特徵
    myTree=createTree(dataSet,labels)
    print(myTree)
    print(classify(myTree,labels,[1,1]))  #測試決策樹的分類效果
    storeTree(myTree,'decisionTreeStorage.txt')
    grabTree('decisionTreeStorage.txt')
    
    
    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章