在之前的決策樹到集成學習里我們說了決策樹和集成學習的基本概念(用了adaboost昨晚集成學習的例子),其后我們分別學習了決策樹分類原理和adaboost原理和實現,
上兩篇我們學習了cart(決策分類樹),決策分類樹也是決策樹的一種,也是很強大的分類器,但是cart的深度太深,我們可以指定cart的深度使得cart變成強一點的弱分類器。
在決策樹到集成學習我們提到,單棵復雜的決策樹可以達到100%,而簡單的集成學習只能有85%的正確率,下面我們嘗試用強一點的弱分類器來看下集成學習的效果有沒有提升。
首先我們要得到可以指定深度得到一棵cart(到達深度后直接多數表決,返回生成的cart)
上兩篇我們只是學習了cart的原理和實現,我們這里還需要實現如果用cart做預測,為了方便做預測,我們保存cart的時候,節點不在用特征的名稱(例如這里的x,y),而是統一改成特征的下標,從0開始,並且節點加上了當前分支的閥值,例如下面的cart(當然,我們可以在最后畫圖的時候在特征名稱轉換過來)
下圖左邊是原始的cart,右邊是指定深度的cart
我們重新回顧一下adaboost的整體流程:
- 對數據集找到最優的弱分類器
- 用最優的分類器預測,記錄預測結果,並相應修改樣本的權重
- 重復上面兩個步驟直到錯誤率為零或者到達最大的迭代次數
如何結合cart和adaboost呢?
cart采用的是gini系數來選擇二分特征,而adaboost是通過改變樣本的權重來實現弱分類器的迭代,如何將adaboost的改變樣本權重的思想融合到cart中去呢?
嘗試采用重復樣本的方法提高樣本的權重
下面直接看實現的代碼吧,我覺得我的注釋應該夠詳細了
# cart_adaboost.py # coding:utf8 from itertools import * import operator,time,math import matplotlib.pyplot as plt def calGini(dataSet):#計算一個數據集的gini系數 numEntries = len(dataSet) labelCounts={} for featVec in dataSet: currentLabel = featVec[-1] if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 labelCounts[currentLabel] += 1 gini=1 for label in labelCounts.keys(): prop=float(labelCounts[label])/numEntries gini -=prop*prop return gini def splitDataSet(dataSet, axis, value,threshold):#根據特征、特征值和方向划分數據集 retDataSet = [] if threshold == 'lt': for featVec in dataSet: if featVec[axis] <= value: retDataSet.append(featVec) else: for featVec in dataSet: if featVec[axis] > value: retDataSet.append(featVec) return retDataSet # 由於是連續值,如果還是兩兩組合的話,肯定爆炸 # 並且連續值根本不需要組合,只需要給定值,就可以從這個分開兩份 # 返回最好的特征以及特征值 def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0]) - 1 bestGiniGain = 1.0; bestFeature = -1;bsetValue="" for i in range(numFeatures): #遍歷特征 featList = [example[i] for example in dataSet]#得到特征列 uniqueVals = list(set(featList)) #從特征列獲取該特征的特征值的set集合 uniqueVals.sort() for value in uniqueVals:# 遍歷所有的特征值 GiniGain = 0.0 # 左增益 left_subDataSet = splitDataSet(dataSet, i, value,'lt') left_prob = len(left_subDataSet)/float(len(dataSet)) GiniGain += left_prob * calGini(left_subDataSet) # 右增益 right_subDataSet = splitDataSet(dataSet, i, value,'gt') right_prob = len(right_subDataSet)/float(len(dataSet)) GiniGain += right_prob * calGini(right_subDataSet) # print GiniGain if (GiniGain < bestGiniGain): #比較是否是最好的結果 bestGiniGain = GiniGain #記錄最好的結果和最好的特征 bestFeature = i bsetValue=value return bestFeature,bsetValue def majorityCnt(classList):#多數表決 classCount={} for vote in classList: if vote not in classCount.keys(): classCount[vote] = 0 classCount[vote] += 1 sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) return sortedClassCount[0][0] # 這里用做生成弱分類器的cart def createTree(dataSet,depth=3):#生成一棵指定深度的cart classList = [example[-1] for example in dataSet] if depth==0:#如果到達指定深度,直接多數表決 return majorityCnt(classList) if classList.count(classList[0]) == len(classList): return classList[0]#所有的類別都一樣,就不用再划分了 if len(dataSet) == 1: #如果沒有繼續可以划分的特征,就多數表決決定分支的類別 return majorityCnt(classList) bestFeat,bsetValue = chooseBestFeatureToSplit(dataSet) bestFeatLabel=str(bestFeat)+":"+str(bsetValue)#用最優特征+閥值作為節點,方便后期預測 if bestFeat==-1: return majorityCnt(classList) myTree = {bestFeatLabel:{}} featValues = [example[bestFeat] for example in dataSet] uniqueVals = list(set(featValues)) # print bsetValue myTree[bestFeatLabel]['<='+str(round(float(bsetValue),3))] = createTree(splitDataSet(dataSet, bestFeat, bsetValue,'lt'),depth-1) myTree[bestFeatLabel]['>'+str(round(float(bsetValue),3))] = createTree(splitDataSet(dataSet, bestFeat, bsetValue,'gt'),depth-1) return myTree def translateTree(tree,labels): if type(tree) is not dict: return tree root=tree.keys()[0] feature,threshold=root.split(":")#取出根節點,得到最優特征和閥值 feature=int(feature) myTree={labels[feature]:{}} for key in tree[root].keys(): myTree[labels[feature]][key]=translateTree(tree[root][key], labels) return myTree def predict(tree,sample): if type(tree) is not dict: return tree root=tree.keys()[0] feature,threshold=root.split(":")#取出根節點,得到最優特征和閥值 feature=int(feature) threshold=float(threshold) if sample[feature]>threshold:#遞歸預測 return predict(tree[root]['>'+str(round(float(threshold),3))], sample) else: return predict(tree[root]['<='+str(round(float(threshold),3))], sample) #用cart對數據集做預測, def cartClassify(dataMatrix,tree): errorList=ones((shape(dataMatrix)[0],1))# 返回預測對或者錯,而不是返回預測的結果(對為0,錯為1,方便計算預測錯誤的個數) predictResult=[]#記錄預測的結果 classList = [example[-1] for example in dataSet] for i in range(len(dataMatrix)): res=predict(tree,dataMatrix[i]) errorList[i]=res!=classList[i] predictResult.append([int(res)] ) # print predict(tree,dataMatrix[i]),classList[i] return errorList,predictResult #記錄弱分類器,主要調整樣本的個數來達到調整樣本權重的目的,訓練弱分類器由createTree函數生成 def weekCartClass(dataSet,weiths,depth=3): min_weights = weiths.min()#記錄最小權重 newDataSet=[] for i in range(len(dataSet)):#最小權重樣本數為1,權重大的樣本對應重復math.ceil(float(array(weiths.T)[0][i]/min_weights))次 newDataSet.extend([dataSet[i]]*int(math.ceil(float(array(weiths.T)[0][i]/min_weights)))) bestWeekClass={} dataMatrix=mat(dataSet); m,n = shape(dataMatrix) bestClasEst = mat(zeros((m,1))) weekCartTree = createTree(newDataSet,depth) errorList,predictResult=cartClassify(dataSet, weekCartTree) weightedError = weiths.T*errorList#記錄分錯的權重之和 bestWeekClass['cart']=weekCartTree return bestWeekClass,predictResult,weightedError def CartAdaboostTrain(dataSet,num=1,depth=3): weekCartClassList=[] classList = mat([int(example[-1]) for example in dataSet]) m=len(dataSet) weiths=mat(ones((m,1))/m) #初始化所有樣本的權重為1/m finallyPredictResult = mat(zeros((m,1))) for i in range(num): bestWeekClass,bestPredictValue,error=weekCartClass(dataSet,weiths,depth)#得到當前最優的弱分類器 alpha = float(0.5*log((1.0-error)/max(error,1e-16)))#根據error計算alpha bestWeekClass['alpha'] = alpha expon = multiply(-1*alpha*mat(classList).T,bestPredictValue) weiths = multiply(weiths,exp(expon)) weiths = weiths/weiths.sum() finallyPredictResult += alpha*mat(bestPredictValue) nowPredictError = multiply(sign(finallyPredictResult) != mat(classList).T,ones((m,1))) errorRate = nowPredictError.sum()/m print "total error: ",errorRate bestWeekClass['error_rate']=errorRate weekCartClassList.append(bestWeekClass) if errorRate == 0.0: break return weekCartClassList, finallyPredictResult from treePlotter import createPlot import numpy as np from numpy import * filename="sample" dataSet=[];labels=[]; with open(filename) as f: for line in f: fields=line.strip("\n").split("\t") t=[float(item) for item in fields[0:-1]] t.append(int(fields[-1])) dataSet.append(t) labels=['x','y'] weekCartClass, finallyPredictResult=CartAdaboostTrain(dataSet,10,4) print finallyPredictResult.T
下面我們看下實驗的結果
由於完整的cart有8層,所以CartAdaboostTrain(dataSet,10,8),只需要一顆cart就可以100%的正確率
cart深度改為7,需要三棵cart
total error: 0.01 total error: 0.01 total error: 0.0
cart改為6,也是三棵cart,但是我們可以發現只有一棵cart的時候正確率是98%
total error: 0.02 total error: 0.02 total error: 0.0
cart改為5,同樣也是三棵cart,但是我們可以發現只有一棵cart的時候正確率是95%
total error: 0.05 total error: 0.07 total error: 0.0
cart改為4,這個時候需要5棵cart,同時我們可以發現只有一棵cart的時候正確率只有90%
total error: 0.1 total error: 0.1 total error: 0.06 total error: 0.05 total error: 0.0
cart改為3,居然發現10棵cart還不行,居然需要12棵cart了,一開始的正確也只有89%
total error: 0.11 total error: 0.11 total error: 0.11 total error: 0.06 total error: 0.1 total error: 0.06 total error: 0.03 total error: 0.04 total error: 0.01 total error: 0.02 total error: 0.04 total error: 0.0
cart改為2時,cart的棵數到到了53棵才能達到100%的正確性,一開始的正確也只有86%
而cart改為1成為決策樹墩的時候,用了200棵cart才達到95%的正確率
到這里我們可以說成功了實現了使用指定深度的cart作為弱分類器的可以調整cart深度和迭代次數的adaboost算法。
最后我們看下cart深度為3時,每棵cart的權重alpha值以及每個樣本最后被預測的值,可以發現最后的預測值不是直接的-1和1,而是差異很大的一些值,這也算是集成學習和單純決策樹的一個區別吧
1.05 0.43 0.54 0.6 0.57 0.68 0.57 0.71 0.66 0.33 0.59 0.53
[-4.22, -2.86, -4.22, -4.22, -4.22, -4.22, -3.97, -6.19, -6.19, -6.19, -5.1, -5.1, -5.1, -3.97, -3.97, -2.6, -1.46, -2.6, -2.82, -2.6, -2.0, -2.0, -2.0, -2.0, -2.0, -3.04, -2.75, -0.95, -2.75, -2.86, -2.86, -4.17, -2.86, -4.22, -4.22, -1.16, -0.98, -1.03, -2.82, -3.97, -2.0, -1.53, -0.85, -1.98, -1.16, -0.85, -0.85, -1.63, -0.99, -1.53, 4.25, 3.92, 2.89, 2.89, 2.64, 3.95, 3.95, 4.98, 4.47, 4.47, 4.47, 4.8, 4.47, 6.19, 6.19, 6.19, 5.1, 6.19, 1.45, 2.43, 4.8, 3.38, 1.45, 5.34, 4.15, 4.47, 1.58, 4.25, 2.56, 3.62, 3.62, 2.56, 4.98, 0.38, 0.51, 4.12, 2.64, 2.43, 4.47, 4.47, 4.47, 2.81, 0.38, 0.51, 1.45, 2.43, 3.83, 2.58, 4.17, -0.77]