机器学习-集成学习(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:是否有放回的采样。

猜你喜欢

转载自blog.csdn.net/lyn5284767/article/details/81281302