1. 程式人生 > >決策樹 (二)

決策樹 (二)

# -*- coding: utf-8 -*-
"""
熵定義為資訊的期望值。
熵:表示隨機變數的不確定性。
條件熵:在一個條件下,隨機變數的不確定性。
資訊增益:熵 - 條件熵
在一個條件下,資訊不確定性減少的程度!
如果選擇一個特徵後,資訊增益最大(資訊不確定性減少的程度最大),那麼我們就選取這個特徵。
"""
from  math import log
import operator
"""
函式說明:建立測試集
Parameter:
    無
Returns:
    dataSet 資料集
    Labels  分類屬性
"""

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 = ['不放貸', '放貸']
    Labels = ['年齡', '有工作', '有自己的房子', '信貸情況']
    return dataSet, Labels

"""
函式說明:計算給定資料集的經驗熵(夏農熵)
Parameters:
    dataSet 資料集
Returns:
    shannonEnt 經驗熵
"""
def calcShannonEnt(dataSet):
    #返回資料集的行數
    numEntirs = 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]) / numEntirs
        #
        shannonEnt -= prob * log(prob, 2)
    return shannonEnt

"""
函式說明:按照給定特徵劃分資料集
Parameters:
    dataSet 待劃分的資料集
    axis 劃分資料集的特徵
    value 需要返回的特徵值
Returns:
    retDataSet 返回的資料集列表
        
"""
def splitDataSet(dataSet, axis, value):
    #返回的資料集列表
    retDataSet = []
    for featVec in dataSet:
       if featVec[axis] == value:
            #去掉axis特徵
            reducedFeatVec = featVec[:axis]
            #reducedFeatVec = []
            #將符合條件的新增到返回的資料集
            reducedFeatVec.extend(featVec[axis+1 : ])
            retDataSet.append(reducedFeatVec)
    return retDataSet
        
"""
函式說明:選擇最優特徵
Paramaters:
    dataSet
Returns:
    beatFeature 資訊增益最優的特徵的索引值
"""
def chooseBestFeatureToSplit(dataSet):
    #特徵數量
    numFeatures = len(dataSet[0]) - 1
    #計算資料集的夏農熵
    baseEntropy = calcShannonEnt(dataSet)
    #資訊增益
    bestInfoGain = 0.0
    #最優特徵的索引值
    bestFeature = -1
    for i in range(numFeatures):
        #獲取dataSet的第i個所有特徵
        #將dataSet中的資料先按行依次放入example中,
        #然後取得example中的example[i]元素,放入列表featList中
        #相當於取所有行的第一個值
        #之所以這樣取,是因為dataSet是個列表,而不是矩陣,矩陣取第一列有方法
        featList = [ example[i] for example in dataSet]
        #建立集合set,元素不可重複
        uniqueVals = set(featList)
        #經驗條件熵
        newEntropy = 0.0
        #計算資訊增益
        for value in uniqueVals:
            #subDataSet是劃分後的子集
            subDataSet = splitDataSet(dataSet, i, value)
            #計運算元集的概率
            prob = len(subDataSet) / float(len(dataSet))
            #計算經驗條件熵
            newEntropy += prob * calcShannonEnt(subDataSet)
            
        #資訊增益
        infoGain = baseEntropy - newEntropy
        #列印每個特徵的資訊增益
        #print("第%d個特徵的增益為:%.3f" % (i, infoGain))
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature

"""
函式說明:統計classList中出現次數最多的元素(類標籤)
Parameters:
    classList 類標籤列表
Returns:
    sortedClassCount[0][0] 出現次數最多的元素(類標籤)
"""
def majorityCnt(classList):
    classCount = {}
    #統計classList中每個元素出現的次數
    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]

"""
函式說明:建立決策樹
Parameters:
    dataSet 訓練集
    Labels 分類標籤
    featLabels 儲存選擇的最優特徵標籤
Returns:
    myTree 決策樹
"""
def createTree(dataSet, Labels, featLabels):
    #取dataSet每行的最後一列的元素構成新的列表
    #相當於取dataSet最後一列的值
    classList = [example[-1] for example in dataSet]
    #若類別完全相同就停止劃分
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    #遍歷完所有特徵值返回出現次數最多的標籤
    if len(dataSet[0]) == 1 or len(Labels) == 0:
        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
"""
函式說明:使用決策樹分類
Parameters:
    imputTree 已經生成的決策樹
    featLabels 儲存選擇的最優特徵標籤
    testVec 測試集 順序對應最優特徵標籤
Returns:
    classLabel 分類結果
"""
def classify(inputTree, featLabels, testVec):
    #獲取決策樹節點
    firstStr = next(iter(inputTree))
    #下一個字典
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)
    for key in secondDict.keys():
        if testVec[featIndex] == key:
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], featLabels, testVec)
            else:
                classLabel = secondDict[key]
    return classLabel
    
if __name__ == '__main__':
    dataSet, Labels = createDataSet()
    featLabels = []
    myTree = createTree(dataSet, Labels, featLabels)
    testVec = [0, 1]
    result = classify(myTree, featLabels, testVec)
    if result == 'yes':
        print('放貸')
    else:
        print('不放貸')