機器學習之決策樹(一)
1、演算法介紹
決策樹是一種基本的分類和迴歸方法,決策樹模型呈樹形結構,在分類問題中,表示基於特徵對例項進行分類的過程。決策樹學習通常包括三個步驟:特徵選擇、決策樹的生成和決策樹的修剪。決策樹的本質是從訓練資料集中歸納出一組分類規則。本文主要是對決策樹的ID3演算法的介紹,後文會介紹C4.5和CART演算法。
2、演算法優缺點
優點:計算複雜度不高,結果易於理解,對於中間值的缺失不敏感,可以處理不相關特徵。
缺點:可能會產生過擬合問題。
適用於標稱型(ID3和C4.5)和數值型(CART演算法)
3、特徵選擇
熵:
設X是一個取有限個值的離散隨機變數,其概率分佈為:
則隨機變數X的熵定義為:條件熵:
條件熵H(Y|X):表示在己知隨機變數X的條件下隨機變數Y的不確定性,定義為X給定條件下Y的條件概率分佈的熵對X的數學期望:
資訊增益:
資訊增益:特徵A對訓練資料集D的資訊增益,g(D,A), 定義為集合D的經驗熵H(D)與特徵A給定條件下D的經驗條件熵H(D|A)之差,即g(D,A)=H(D)-H(D|A)
因此,選擇資訊增益最優,也即是資訊增益最大的特徵作為節點,然後再迴圈構建決策樹。
4、數學例子
首先計算熵:
樣本總共有15條記錄,其中不買的有5條記錄,買的有10條記錄。
所以H(D) = -5/15log2(5/15)-10/15log2(10/15) = 0.92
再計算條件熵:
年齡:青年有5條記錄,其中不買的為3條,買的記錄為2條
中年有4條記錄,其中不買的為0條,買的記錄為4條
老年有6條記錄,其中不買的為2條,買的記錄為4條
則條件熵為5/15*(-3/5*log(3/5,2)-2/5*log(2/5,2)) + 4/15*(0 - 4/4*log(4/4,2)) + 6/15*(-2/6*log(2/6,2)-4/6*log(4/6,2)) = 0.69
所以年齡的資訊增益為0.23
當特徵值取收入,學生,信譽時,資訊增益分別為:0.03,0.17,0.03
故選擇年齡為第一次迭代的節點,然後再重複上述步驟,最後完成樹的構建。
5、ID3程式碼實現
from sklearn.model_selection import train_test_split import math def getData(): #獲取資料集,資料來自uci的mushroom.data data_x = [];data_y = [];train = [] with open('mushroom.data') as f: for line in f: line = line.strip().split(',') data_x.append(line[1:]) data_y.append(line[0]) train_x,test_x,train_y,test_y = train_test_split(data_x,data_y,test_size=0.8,random_state=0) for x,y in zip(train_x,train_y): x.append(y) train.append(x) return train,test_x,test_y def calI(prob): '''封裝對數計算公式''' I = 0.0 for p in prob: if p != 0: # p=0時不能計算log I += -p * math.log(p, 2) return I def calInfo(dataSet): '''計算資訊熵''' values = [value[-1] for value in dataSet] uniqueVal = set(values) prob = [] for val in uniqueVal: #計算p prob.append(values.count(val) / float(len(values))) return calI(prob) 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)#把符合返回的值那些返回(axis那一列不新增) return retDataSet def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0]) - 1 #計算特徵的數目 baseEntropy = calInfo(dataSet) #夏農熵 bestInfoGain = 0.0 #最好的資訊增益 bestFeature = -1 #最好的資訊增益對應的特徵 for i in range(numFeatures): featList = [example[i] for example in dataSet]#獲得該列所可能取的值 uniqueVals = set(featList) #去重 newEntropy = 0.0 #條件熵 for value in uniqueVals: subDataSet = splitDataSet(dataSet, i, value)#劃分資料集 prob = len(subDataSet)/float(len(dataSet)) newEntropy += prob * calInfo(subDataSet) infoGain = baseEntropy - newEntropy #計算資訊增益 if (infoGain > bestInfoGain): #選擇最好的資訊增益 bestInfoGain = infoGain bestFeature = i return bestFeature #返回列 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=lambda x:x[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 = 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[:] myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels) return myTree def classify(inputTree,featLabels,testVec): firstStr = list(inputTree.keys())[0] secondDict = inputTree[firstStr] featIndex = featLabels.index(firstStr) key = testVec[featIndex] valueOfFeat = secondDict[key] if isinstance(valueOfFeat, dict): classLabel = classify(valueOfFeat, featLabels, testVec) else: classLabel = valueOfFeat return classLabel def correct_rate(test_y,predict_y): #返回測試集的正確率 length = len(test_y) score = 0 for num in range(length): if test_y[num] == predict_y[num]: score += 1 rate = str((score/float(length)) *100) + '%' return rate if __name__ == '__main__': train,test_x,test_y = getData() labels = ['cap-shape','cap-surface','cap-color','bruises','odor','gill-attachment','gill-spacing','gill-size','gill-color','stalk-shape','stalk-root','stalk-surface-above-ring','stalk-surface-below-ring','stalk-color-above-ring','stalk-color-below-ring','veil-type','veil-color','ring-number','ring-type','spore-print-color','population','habitat'] label = labels[:] mytree = createTree(train, labels) predict_y = [] for x in test_x: y = classify(mytree,label,x) predict_y.append(y) print('正確率:',correct_rate(test_y,predict_y))
博主在做交叉驗證時出了問題,探索了很久,發現原因在於一開始選的資料集太過單一,導致樹的構建不完整,在驗證模型正確率時出現錯誤,因此改用了mushroom.data,此資料集在uci網站可以下載,共有8124個記錄。
參考書籍:
《統計學習方法》--李航
《機器學習實戰》--Peter