1. 程式人生 > >【python和機器學習入門2】決策樹2——決策樹構建

【python和機器學習入門2】決策樹2——決策樹構建

參考部落格:決策樹實戰篇之為自己配個隱形眼鏡 (po主Jack-Cui,《——大部分內容轉載自

                 

參考書籍:《機器學習實戰》——第三章

目錄

一 構建決策樹

1.1 決策樹構建原理

1.2 決策樹結構

1.3決策樹構建關鍵程式碼

1.4 構建完整程式碼

1.5 使用構建的決策樹進行預測分類

二 決策樹的儲存


《——決策樹概念子模組等見前篇

一 構建決策樹

依舊是用貸款demo,資料如下

本章使用ID3演算法進行決策樹劃分,每次劃分消耗一個特徵屬性。

1.1 決策樹構建原理

決策樹構建工作原理:從原始資料集開始,基於最好的屬性值劃分資料集,由於特徵值可能多於兩個,因此可能存在大於兩個分支的資料集劃分。第一次劃分之後,資料集被向下傳遞到樹的分支的下一個結點。在這個結點上,我們可以再次劃分資料。因此我們可以採用遞迴的原則處理資料集。

 使用ID3演算法的核心是在決策樹各個結點上對應資訊增益準則選擇特徵,遞迴地構建決策樹。具體方法是:從根結點(root node)開始,對結點計算所有可能的特徵的資訊增益,選擇資訊增益最大的特徵作為結點的特徵,由該特徵的不同取值建立子節點;再對子結點遞迴地呼叫以上方法,構建決策樹;直到所有特徵的資訊增益均很小或沒有特徵可以選擇為止,最後得到一個決策樹。ID3相當於用極大似然法進行概率模型的選擇。

遞迴建立決策樹時,遞迴有兩個終止條件:第一個停止條件是所有的類標籤完全相同,則直接返回該類標籤;第二個停止條件是使用完了所有特徵,仍然不能將資料劃分僅包含唯一類別的分組,即決策樹構建失敗,特徵不夠用。此時說明資料緯度不夠,由於第二個停止條件無法簡單地返回唯一的類標籤,通常採用多數表決即挑選出現數量最多的類別作為返回值。

根據該構建原理對貸款demo進行資料集劃分,從上篇我們知道根的資訊增益最大是“是否有房”特徵屬性,根據這個屬性可以劃分2個數據集D1、D2如下,且D1資料集中的貸款類別全為“是”,即全為同一類,該資料集結點即可停止劃分。

1.2 決策樹結構

貸款demo的資訊增益計算過程如下,前4行為第一次劃分資料集,資訊增益最高為第3個,取為根節點,刪除該特徵後再次計算,得到第2個特徵資訊增益最高,取為第二個劃分特徵。

可以知道第一個劃分特徵為“有自己的房子”,第二個劃分特徵為“有工作” ,且劃分之後子集正好都是同一類,即終止節點。故決策樹構建完成如下:

 

使用字典儲存決策樹結構,如

{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}

字典關鍵字(key)‘有自己的房子’是第一個劃分資料集的特徵名稱,它的值(value)是另一個字典。value要麼是類標籤,要麼是另一個字典。如果值是類標籤則該子節點是葉子節點;如果值是另一個字典,則子節點是一個判讀節點,這種格式不斷重複構成了整棵樹。

1.3決策樹構建關鍵程式碼

(這部分程式碼解析來自po主Jack-Cui,寫的很詳細 我就扣過來了)

函式createTree(dataset,labels)返回建立的字典決策樹,

子模組程式碼詳細解釋見前篇

虛擬碼(遞迴建樹函式createTree):

判斷是否為兩類終止型別,如果是則返回分類(majorityCnt

若不是則向下建樹:

            找最優特徵(chooseBestFeatureToSpit()《——splitdataset、calShannonEnt)、

            ID3消耗特徵(del()

            獲取該特徵的屬性值並迴圈劃分子集(splitdataset)、

            子集遞迴建樹 (createTree

list.count() 方法用於統計某個元素在列表中出現的次數。

del() 刪除元素。注意del 是刪除引用而不是刪除物件,物件由自動垃圾回收機制(GC)刪除

def createTree(dataSet, labels, featLabels):
    classList = [example[-1] for example in dataSet]            #取分類標籤(是否放貸:yes or no)
    if classList.count(classList[0]) == len(classList):            #如果類別完全相同則停止繼續劃分
        return classList[0]
    if len(dataSet[0]) == 1:                                    #遍歷完所有特徵時返回出現次數最多的類標籤
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)                #選擇最優特徵
    bestFeatLabel = labels[bestFeat]                            #最優特徵的標籤
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel:{}}                                    #根據最優特徵的標籤生成樹
    del(labels[bestFeat])                                        #刪除已經使用特徵標籤
    featValues = [example[bestFeat] for example in dataSet]        #得到訓練集中所有最優特徵的屬性值
    uniqueVals = set(featValues)                                #去掉重複的屬性值
    for value in uniqueVals:                                    #遍歷特徵,建立決策樹。                       
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree

 

1.4 構建完整程式碼

#!/usr/bin/env python
#_*_coding:utf-8_*_
import numpy as np
import json
import operator
from math import log

''''''
def createDataSet():
    dataSet = [[0, 0, 0, 0, 'no'],         #資料集
            [0, 0, 0, 1, 'no'],
            [0, 1, 0, 1, 'yes'],
            [0, 1, 1, 0, 'yes'],
            [0, 0, 0, 0, 'no'],
            [1, 0, 0, 0, 'no'],
            [1, 0, 0, 1, 'no'],
            [1, 1, 1, 1, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [2, 0, 1, 2, 'yes'],
            [2, 0, 1, 1, 'yes'],
            [2, 1, 0, 1, 'yes'],
            [2, 1, 0, 2, 'yes'],
            [2, 0, 0, 0, 'no']]
    labels = ['年齡', '有工作', '有自己的房子', '信貸情況']        #特徵屬性
    return dataSet, labels                #返回資料集和特徵屬性

'''經驗熵'''
def calShannonEnt(dataset):
    m = len(dataset)
    lableCount = {}
    '''計數'''
    for data in dataset:
        currentLabel = data[-1]
        if currentLabel not in lableCount.keys():
            lableCount[currentLabel] = 0
        lableCount[currentLabel] += 1
    '''遍歷字典求和'''
    entropy = 0
    for label in lableCount:
        p = float(lableCount[label]) / m
        entropy -= p * log(p,2)
    return entropy

'''第i個特徵根據取值value劃分子資料集'''
def splitdataset(dataset,axis,value):
    subSet = []
    for data in dataset:
        if(data[axis] == value):
            data_x = data[:axis]
            data_x.extend(data[axis+1:])
            subSet.append(data_x)
    return subSet

'''遍歷資料集求最優IG和特徵'''
def chooseBestFeatureToSpit(dataSet):
    feature_num = len(dataSet[0])-1
    origin_ent = calShannonEnt(dataSet)
    infoGain = 0.0
    best_infogain = 0.0
    for i in range(feature_num):
        fi_all = [data[i] for data in dataSet]
        fi_all = set(fi_all)
        #print fi_all
        subset_Ent = 0
        '''遍歷所有可能value'''
        for value in fi_all:
            #劃分子集
            #print i,value
            subset = splitdataset(dataSet,i,value)
            #print subset
            #計運算元集熵
            p = float(len(subset)) / len(dataSet)
            subset_Ent += p * calShannonEnt(subset)
        #計算資訊增益
        infoGain = origin_ent - subset_Ent
        #記錄最大IG
        print  "第 %d 個特徵的資訊增益為 %f" % (i,infoGain)
        if(infoGain > best_infogain):
            best_feature = i
            best_infogain = infoGain
    return best_feature

'''計數並返回最多類別'''
def majorityCnt(classList):
    classCount = {}
    for class_ in classList:
        if(class_ not in classCount.keys()):
            classCount[class_] = 0
        classCount[class_] += 1
    classSort = sorted(classCount.iteritems(),key = operator.itemgetter(1),reverse=True)
    return classSort[0][0]

'''向下遞迴建立樹 '''
def createTree(dataSet,labels,feaLabels):
    '''資料集所有類別'''
    classList = [example[-1] for example in dataSet]
    '''判斷是否屬於2個終止型別'''
    '''1 全屬一個類'''
    if(len(classList) == classList.count(classList[0])):
        return classList[0]
    '''2 只剩1個特徵屬性'''
    if(len(dataSet[0]) == 1):
        majorClass = majorityCnt(classList)
        return majorClass
    '''繼續劃分'''
    best_feature = chooseBestFeatureToSpit(dataSet)#最優劃分特徵 下標號
    best_feaLabel = labels[best_feature]
    feaLabels.append(best_feaLabel) #儲存最優特徵
    del(labels[best_feature])#特徵屬性中刪去最優特徵《——ID3消耗特徵
    feaValue = [example[best_feature] for example in dataSet]
    feaValue = set(feaValue) #獲取最優特徵的屬性值列表
    deci_tree = {best_feaLabel:{}}#子樹的根的key是此次劃分的最優特徵名,value是再往下遞迴劃分的子樹
    for value in feaValue:
        subLabel = labels[:]
        subset = splitdataset(dataSet,best_feature,value)
        deci_tree[best_feaLabel][value] = createTree(subset,subLabel,feaLabels)
    return deci_tree

if __name__ == '__main__':
    dataSet, labels = createDataSet()
    feaLabels = []
    print json.dumps(mytree,ensure_ascii=False)

決策樹構建結果

1.5 使用構建的決策樹進行預測分類

決策樹構建完畢後,接下來使用進行實際資料的分類。在執行資料分類的時候,程式比較測試資料和決策樹上的值,比如決策樹根為“有自己的房子”,讀取測試資料該標籤上的值,根據值進入到下一節點,遞迴執行該過程直到進入葉子節點,最後將測試資料預測的分類定義為最終走到的葉子節點所屬的型別。

type() 只有一個引數時返回物件的型別 屬性值__name__獲取型別名

list.index()返回元素索引

'''預測分類'''
'''feaLabels,testdata特徵對應順序應一樣'''
def classify(inputTree,feaLabels,testdata):
    firstStr = inputTree.keys()[0]
    #print firstStr
    secondDict = inputTree[firstStr]
    #print secondDict
    '''獲取測試資料在此特徵上的值'''
    testvalue = testdata[feaLabels.index(firstStr)]
    for value in secondDict.keys():
        if(value == testvalue):
            if(type(secondDict[value]).__name__ == 'dict'):
                 classLabel  = classify(secondDict[value],feaLabels,testdata)
            else:
                classLabel = secondDict[value]
    return classLabel

if __name__ == '__main__':
    dataSet, labels = createDataSet()
    feaLabels = []
    mytree = createTree(dataSet,labels,feaLabels)
    print feaLabels
    print json.dumps(mytree,ensure_ascii=False)
    testdata = [0,1]
    classresult =  classify(mytree,feaLabels,testdata)
    if(classresult == 'yes'):
        print "准許貸款"
    else: print "拒絕貸款"

二 決策樹的儲存

構造決策樹是很耗時的任務,即使處理很小的資料集,如前面的樣本資料,也要花費幾秒的時間,如果資料集很大,將會耗費很多計算時間。然而用建立好的決策樹解決分類問題,則可以很快完成。因此,為了節省計算時間,最好能夠在每次執行分類時呼叫已經構造好的決策樹。為了解決這個問題,需要使用Python模組pickle序列化物件。序列化物件可以在磁碟上儲存物件,並在需要的時候讀取出來。

'''儲存決策樹'''
def storeTree(inputTree,filename):
    import pickle
    fw = open(filename,'w')
    pickle.dump(inputTree,fw)
    fw.close()

'''讀取決策樹'''
def grabTree(filename):
    import pickle
    fr = open(filename)
    return pickle.load(fr)

三 總結 

決策樹的一些優點:

  • 易於理解和解釋,決策樹可以視覺化。
  • 幾乎不需要資料預處理。其他方法經常需要資料標準化,建立虛擬變數和刪除缺失值。決策樹還不支援缺失值。
  • 使用樹的花費(例如預測資料)是訓練資料點(data points)數量的對數。
  • 可以同時處理數值變數和分類變數。其他方法大都適用於分析一種變數的集合。
  • 可以處理多值輸出變數問題。
  • 使用白盒模型。如果一個情況被觀察到,使用邏輯判斷容易表示這種規則。相反,如果是黑盒模型(例如人工神經網路),結果會非常難解釋。
  • 即使對真實模型來說,假設無效的情況下,也可以較好的適用。

決策樹的一些缺點

  • 決策樹學習可能建立一個過於複雜的樹,並不能很好的預測資料。也就是過擬合。修剪機制(現在不支援),設定一個葉子節點需要的最小樣本數量,或者數的最大深度,可以避免過擬合。
  • 決策樹可能是不穩定的,因為即使非常小的變異,可能會產生一顆完全不同的樹。這個問題通過decision trees with an ensemble來緩解。
  • 學習一顆最優的決策樹是一個NP-完全問題under several aspects of optimality and even for simple concepts。因此,傳統決策樹演算法基於啟發式演算法,例如貪婪演算法,即每個節點建立最優決策。這些演算法不能產生一個全家最優的決策樹。對樣本和特徵隨機抽樣可以降低整體效果偏差。
  • 概念難以學習,因為決策樹沒有很好的解釋他們,例如,XOR, parity or multiplexer problems.
  • 如果某些分類佔優勢,決策樹將會建立一棵有偏差的樹。因此,建議在訓練之前,先抽樣使樣本均衡。