機器學習-集成學習(Bagging算法之隨機森林)

一,介紹

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:是否有放回的採樣。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章