一,介紹
Bagging算法:假定有m個訓練集,我們採用自助採樣法,每次隨機抽取一個放入採樣集中,然後再把樣本放回訓練集,一共抽取m次,獲得一個用於訓練的採樣集(裏面有m個樣本)。根據需要我們一共抽取T個採樣集,學習出T個基學習器。
在進行預測時,對於分類任務採用簡單投票發;迴歸任務採用簡單平均法。
隨機森林:隨機森林是Bagging算法的擴展。在以決策樹爲基學習器構建bagging集成的基礎上,進一步在決策樹的訓練中,引入隨機屬性選擇。其基本思想就是構造很多棵決策樹,形成一個森林,每一棵樹都會給出自己的分類選擇,並由此進行“投票”,森林整體的輸出結果將會是票數最多的分類選項。
在構建單個決策樹時,不同於傳統決策樹,在構建子節點時,不是選擇所有屬性進行判定,而是隨機抽取k個屬性進行葉節點構建,一般情況下,推薦k=log2(d)。
二,代碼實現
訓練/測試數據:
1,青綠,蜷縮,濁響,清晰,凹陷,硬滑,是 2,烏黑,蜷縮,沉悶,清晰,凹陷,硬滑,是 3,烏黑,蜷縮,濁響,清晰,凹陷,硬滑,是 4,青綠,蜷縮,沉悶,清晰,凹陷,硬滑,是 5,淺白,蜷縮,濁響,清晰,凹陷,硬滑,是 6,青綠,稍蜷,濁響,清晰,稍凹,軟粘,是 7,烏黑,稍蜷,濁響,稍糊,稍凹,軟粘,是 8,烏黑,稍蜷,濁響,清晰,稍凹,硬滑,是 9,烏黑,稍蜷,沉悶,稍糊,稍凹,硬滑,否 10,青綠,硬挺,清脆,清晰,平坦,軟粘,否 11,淺白,硬挺,清脆,模糊,平坦,硬滑,否 12,淺白,蜷縮,濁響,模糊,平坦,軟粘,否 13,青綠,稍蜷,濁響,稍糊,凹陷,硬滑,否 14,淺白,稍蜷,沉悶,稍糊,凹陷,硬滑,否 15,烏黑,稍蜷,濁響,清晰,稍凹,軟粘,否 16,淺白,蜷縮,濁響,模糊,平坦,硬滑,否 17,青綠,蜷縮,沉悶,稍糊,稍凹,硬滑,否
下面有自己寫和用sklearn庫直接實現兩種方式:
自寫:
import math import random import operator # 加載數據 def loadDataSet(filename): dataMat=[] fr = open(filename) for line in fr.readlines(): lineArr = line.strip().split(',') dataMat.append(lineArr) labelMat = ['編號', '色澤', '根蒂', '敲聲', '紋理', '頭部', '觸感', '好瓜'] return dataMat,labelMat # 自助採樣法,dataMat訓練集數據;times採樣集個數 def bootstrapSampling(dataMat,times): totalsampleData = [] for i in range(times): sampleData = [] for j in range(17): sampleData.append(dataMat[random.randint(0,16)]) # 從17個樣本中隨機抽取一個加入採樣集 totalsampleData.append(sampleData) # 將所有采樣集數據放入,作爲訓練數據 return totalsampleData # 選取信息增益最高的列 def chooseBestFeatureToSplit(dataSet,randinfo): baseEnt = calcShannonEnt(dataSet) bestInfoGain = 0.0;bestFeature = [] length = 0 if (len(randinfo) ==0): numFeatures = len(dataSet[0]) - 1 for i in range(1,numFeatures): # 遍歷獲取的屬性 featList = [example[i] for example in dataSet] uniqueVals = set(featList) newEntropy = 0.0 for value in uniqueVals: subDataSet = splitSubDataSet(dataSet, i, value) prob = len(subDataSet) / float(len(dataSet)) newEntropy += prob * calcShannonEnt(subDataSet) infoGain = baseEnt - newEntropy # 計算信息增益 if (infoGain >= bestInfoGain): # 保存信息增益最高的列 bestInfoGain = infoGain bestFeature = i else: for i in range(len(randinfo)): # 遍歷獲取的屬性 featList = [example[randinfo[i]] for example in dataSet] uniqueVals = set(featList) newEntropy = 0.0 for value in uniqueVals: subDataSet = splitSubDataSet(dataSet, randinfo[i], value) prob = len(subDataSet) / float(len(dataSet)) newEntropy += prob * calcShannonEnt(subDataSet) infoGain = baseEnt - newEntropy # 計算信息增益 if (infoGain >= bestInfoGain): # 保存信息增益最高的列 bestInfoGain = infoGain bestFeature = randinfo[i] return bestFeature # 計算信息熵Ent(D)=-Σp*log2(p) def calcShannonEnt(dataSet): numEntries = len(dataSet) labelCounts = {} for featVec in dataSet: currentLabel = featVec[-1] #獲取類別 if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 #新key加入字典賦值爲0 labelCounts[currentLabel] += 1 shannonEnt = 0.0 for key in labelCounts: prob = float(labelCounts[key]) / numEntries shannonEnt -= prob * math.log2(prob) # 計算信息熵 return shannonEnt #獲取特徵值數據集 # dataSet --整個數據集 # axis --數據列 # value --類別 def splitSubDataSet(dataSet, axis, value): retDataSet = [] for featVec in dataSet: if featVec[axis] == value: retDataSet.append([featVec[axis],featVec[-1]]) return retDataSet 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] #除去劃分完成的決策樹數據量 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 # 創建決策樹 def createTree(dataSet,labels,randinfo,k): classList = [example[-1] for example in dataSet] if classList.count(classList[0]) == len(classList): return classList[0]#當所有類都相同則不在分類 if (len(dataSet[0]) == 3): #沒有更多特徵值時不再分類 return majorityCnt(classList) bestFeat = chooseBestFeatureToSplit(dataSet,randinfo) #選取信息增益最大的特徵值 bestFeatLabel = labels[bestFeat] #獲取特徵值列頭名 myTree = {bestFeatLabel:{}} featValues = [example[bestFeat] for example in dataSet] uniqueVals = set(featValues) # 獲取特徵值分類 del(labels[bestFeat]) # 刪除已經建立節點的特徵值 sam = [] if (len(labels) > 3): # 特徵值大於2個則隨機選擇節點,否則全部選取 sam = random.sample(range(1, len(labels) - 1), k) for value in uniqueVals: subLabels = labels[:] # 複製出建立節點外的所有特徵值 myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels,sam,k) #建立子節點 return myTree # 創建隨機森林,totalsampleData訓練集;labelMat列名 def createRandomForest(totalsampleData,labelMat): k = int(math.log2(len(labelMat)-2)) # 計算隨機屬性個數(-2是去除第一列編號和最後一列分類標籤) randomForest = [] for dataSet in totalsampleData: # 遍歷創建隨機數,以構成隨機森林 randinfo = random.sample(range(1, len(labelMat) - 2), k) # 獲取k個隨機屬性 tmplabel = labelMat.copy() randomForest.append(createTree(dataSet,tmplabel,randinfo,k)) return randomForest # 決策樹進行分類 def classify(inputTree,featLabels,testVec): firstStr = list(inputTree.keys())[0] # 獲取第一個節點 secondDict = inputTree[firstStr] # 獲取剩餘節點 featIndex = featLabels.index(firstStr) key = testVec[featIndex] # 獲取測試數據分支 valueOfFeat = {} if(key in secondDict): # 存在則進入分支 valueOfFeat = secondDict[key] else: # 不存在則返回空(實際上是需要再訓練決策樹的,這裏簡單處理) return "None" if isinstance(valueOfFeat, dict): classLabel = classify(valueOfFeat, featLabels, testVec) else: classLabel = valueOfFeat return classLabel def voteClassify(randomForest,featLabel,testVec): terclass = [] for tree in randomForest: classlabel = classify(tree,featLabel,testVec) terclass.append(classlabel) set(terclass) yes = terclass.count('是') no = terclass.count('否') if(yes>no): return '是' else: return '否' if __name__=='__main__': dataMat,labelMat = loadDataSet('watermelon.txt') totalsampleData = bootstrapSampling(dataMat,5) randomForest = createRandomForest(totalsampleData,labelMat) testData, testlabel = loadDataSet('watermelon.txt') for data in testData: result = voteClassify(randomForest,testlabel,data) print("輸入:%s,結果:%s"%(data,result))
sklearn庫實現:
import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.cross_validation import train_test_split from sklearn.feature_extraction import DictVectorizer from sklearn.tree import DecisionTreeClassifier if __name__=='__main__': f = open('watermelon.csv') data = pd.read_csv(f) x = data[['色澤','根蒂','敲聲','紋理','頭部','觸感']] y = data['好瓜'] x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.30, random_state=33) print(x_test) # 使用特徵轉換器進行特徵抽取 vec = DictVectorizer() # 類別型的數據會抽離出來 數據型的會保持不變 x_train = vec.fit_transform(x_train.to_dict(orient="record")) x_test = vec.transform(x_test.to_dict(orient="record")) # 初始化隨機森林分類器 rfc = RandomForestClassifier() # 訓練 rfc.fit(x_train, y_train) # 預測 rfc_y_predict = rfc.predict(x_test) print(dtc_y_predict)
RandomForestClassifier()函數解釋:
criterion參數:有兩個取值"gini"或者"entropy",分別表示採用基尼不純度或者信息增益來劃分決策樹,默認參數是"gini"。
splitter參數:有兩個取值"best"和"random",分別表示選取最好的和隨機選擇屬性後,在從中選擇好的,默認參數是"best"。
max_depth參數:允許樹的最大深度,默認是None。
min_samples_split參數:根據屬性劃分節點時,每個劃分最少的樣本數。可以設置爲int,表示最小几個;或者float佔總數百分比,默認是2個。
min_samples_leaf參數:葉子節點最少的樣本數。可以設置爲int,表示最小几個;或者float佔總數百分比,默認是1個。
min_weight_fraction_leaf參數: 葉子節點所需要的最小權值。默認是0。
max_features參數:選擇最適屬性時劃分的特徵不能超過此值。如果是int則表示個數;如果是float則爲特徵值百分比;爲str則:
- If "auto", then `max_features=sqrt(n_features)`. - If "sqrt", then `max_features=sqrt(n_features)`. - If "log2", then `max_features=log2(n_features)`. - If None, then `max_features=n_features`.
n_estimators:決策樹的個數。 bootstrap:是否有放回的採樣。