機器學習實戰-簡單決策樹編寫
阿新 • • 發佈:2018-12-12
#!/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) ``