1. 程式人生 > >機器學習實戰-簡單決策樹編寫

機器學習實戰-簡單決策樹編寫

#!/user/bin/env python
# !-*-coding:utf-8 -*-
# !Time :2018/9/28 4:12 PM
# !Author : hyCong
# [email protected]  : .py
from math import log
import operator
import treePlotter

# 計算熵
def calcShannonEnt(dataSet):
    numEntries = len(dataSet)  # 資料集的長度
    labelCounts = {}  # 用來存放不同這一特徵的不同型別的樣例個數
    for featVec in dataSet:
        currentLabel = featVec[-1]  # 獲取當前樣例的類別
        if currentLabel not in labelCounts.keys():  # 判斷當前掃到的類別在labelCounts字典中是否存在,如果存在則出現次數加以,否則初始化為1
            labelCounts[currentLabel] = 1
        else:
            labelCounts[currentLabel] += 1
    shannonEnt = 0.0  # 初始化香濃⤴️為0
    for key in labelCounts:
        prob = float(labelCounts[key]) / numEntries  # 計算當前key類別的樣例個數/總個數,即Pk
        shannonEnt -= prob * log(prob, 2)  # 使用熵的計算公式計算熵
    return shannonEnt


# 判斷簡單魚類的資料建立函式
def createDataSet():
    dataSet = [
        [1, 1, 'yes'],
        [1, 1, 'yes'],
        [1, 0, 'no'],
        [0, 1, 'no'],
        [0, 1, 'no']
    ]
    labels = ['no surfacing', 'flippers']
    return dataSet, labels


# 根據所給定的屬性和屬性值獲取相應的資料
def splitDataSet(dataSet, axis, value):  # 三個引數,資料集,所選定的屬性下標,需要返回的資料集的屬性的值
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:  # 如果符合要求,則將該行資料抽取出來(去除用於選擇的屬性列)
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis + 1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet


# ID3(迭代二分器)中的劃分屬性選擇方法:從候選的屬性中選出Gain最大的屬性進行劃分
def chooseBestFeatureToSplitInID3(dataSet):
    numFeatures = len(dataSet[0]) - 1  # 待選屬性的個數
    baseEntropy = calcShannonEnt(dataSet)  # Ent(D) 資料集的資訊熵
    bestInfoGain = 0.0;
    bestFeatur = -1  # 初始化最大資訊增益和相對應的屬性下標
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]  # 獲取第i個屬性的對應列
        uniqueVals = set(featList)  # 獲取屬性值集合
        newEntropy = 0.0  # 屬性資訊熵
        for value in uniqueVals:  # 針對第i種屬性的每一種屬性值,進行迴圈,計算其對應的資訊熵
            subDataSet = splitDataSet(dataSet, i, value)  # 抽取出i屬性的value屬性值得資料集
            prob = len(subDataSet) / float(len(dataSet))  # 計算Pk(v)
            newEntropy += prob * calcShannonEnt(subDataSet)  # Gain
        infoGain = baseEntropy - newEntropy  # i屬性的資訊增益
        if infoGain > bestInfoGain:  # 取出資訊增益最大的屬性
            bestInfoGain = infoGain
            bestFeatur = i
    return bestFeatur


# C4.5演算法中的劃分屬性選擇方法:使用啟發式,先從候選劃分屬性中找出資訊增益高於平均水平的那些屬性,再從中選出Gain_ratio資訊增益率最大的屬性
def chooseBestFeatureToSplitInC45(dataSet):
    numFeatures = len(dataSet[0]) - 1  # 待選屬性的個數
    baseEntropy = calcShannonEnt(dataSet)  # Ent(D) 資料集的資訊熵
    bestGainRatio = 0.0  # 最高資訊增益率
    gain_ratios = []  # 各屬性的資訊增益率
    gain_ratios_high = []  # 比平均資訊增益大的屬性的資訊增益率
    feats_high = []  # 比平均資訊增益大的屬性下標
    sumEntropy = 0.0  # 總資訊增益
    gainSet = []  # 各屬性的資訊增益
    bestFeatur = -1  # 初始化最大資訊增益和相對應的屬性下標
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]  # 獲取第i個屬性的對應列
        uniqueVals = set(featList)  # 獲取屬性值集合
        newEntropy = 0.0  # 屬性資訊熵
        IV = 0.0
        for value in uniqueVals:  # 針對第i種屬性的每一種屬性值,進行迴圈,計算其對應的資訊熵
            subDataSet = splitDataSet(dataSet, i, value)  # 抽取出i屬性的value屬性值得資料集
            prob = len(subDataSet) / float(len(dataSet))  # 計算Pk(v)
            newEntropy += prob * calcShannonEnt(subDataSet)  # Gain
            IV -= prob * log(prob, 2)  # 屬性i的固有值
        infoGain = baseEntropy - newEntropy  # i屬性的資訊增益
        gain_ratios.append(infoGain / IV)  # 記錄下屬性i的資訊增益率
        sumEntropy += infoGain  # 計算總資訊增益
        gainSet.append(infoGain)  # 將各屬性的資訊增益放入列表用於計算平均值
    avgEntropy = sumEntropy / numFeatures  # 資訊增益平均值
    for i in range(len(gainSet)):  # 迴圈找出大於平均資訊增益的屬性
        if gainSet[i] > avgEntropy:
            gain_ratios_high.append(gain_ratios[i])
            feats_high.append(i)
    for i in range(len(gain_ratios_high)):  # 找出增益率最大的屬性
        if gain_ratios_high[i] > bestGainRatio:
            bestGainRatio = gain_ratios_high[i]
            bestFeatur = feats_high[i]

    return bestFeatur


# CART決策樹演算法中劃分屬性的選擇:選擇屬性的基尼係數最小的進行劃分
def chooseBestFeatureToSplitInCART(dataSet):
    return 0


# 當候選屬性已經劃分完畢後,發現數據集仍然存在多個類別,則採用多數表決的方法進行決定分類
def majorityCnt(classList):
    classCount = {}  # 類別計數
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 1
        else:
            classCount[vote] += 1
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)  # 將統計好的各類別的樣本個數進行降序排序
    return sortedClassCount[0][0]  # 返回出現次數最多的類別


# 決策樹建立程式碼
def createTree(dataSet, labels):
    classList = [example[-1] for example in dataSet]  # 當前資料集的類別列表
    if classList.count(classList[0]) == len(classList):  # 構造樹的第一類返回:當剩下的資料集都屬於同一類時 ,無需向下劃分
        return classList[0]
    if len(dataSet[0]) == 1:  # 構造樹的第二類返回:當剩下的資料集的列數只剩下一列時,可發現所有屬性都已用完,咋返回樣例佔比較大的類別
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplitInID3(dataSet)  # 選取剩下的屬性中最優的屬性
    bestFeatLabel = labels[bestFeat]  # 最優屬性的名稱
    myTree = {bestFeatLabel: {}}  # 新建用來存放樹節點的字典
    del (labels[bestFeat])  # 刪除當前已選擇了的屬性名稱
    featValues = [example[bestFeat] for example in dataSet]  # 迴圈將資料集中bestFeature的所有屬性值取出
    uniqueVals = set(featValues)  # 將獲取的屬性值進行去重
    for vallue in uniqueVals:
        subLabels = labels[:]  # python在函式呼叫時,是引用傳值,而labels是列表型別,實質上是一個指標,若直接傳入,可能會導致操作錯誤。
        # 進入下一次迭代的入口,在進入子樹進行建立時,首先需要將資料集進行劃分,抽取出所有符合當前選擇的屬性的屬性值的資料,將其傳入下一次建立過程。
        myTree[bestFeatLabel][vallue] = createTree(splitDataSet(dataSet, bestFeat, vallue), subLabels)
    return myTree


classLabel = ''


# 針對測試向量,判斷其類別
def classify(inputTree, featLabels, testVec):
    global classLabel
    firstStr = inputTree.keys()[0]  # 獲取當前樹的樹根的屬性名
    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


# 使用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)
``