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')