【机器学习实践】用Python实现集成学习中的AdaBoost和随机森林(RF)

        这周学习了周志华《机器学习》第8章”集成学习“的知识,对AdaBoost和Bagging两种算法有了一定的了解,而随机森林又是Bagging的一个拓展变体。为了巩固本章知识和加强自身的Python代码能力,因此用Python简单实现了这两种算法。

       首先简述两种算法的实现步骤,以便日后的复习使用。

        AdaBoost是Boosting族的典型代表,基本思想就是每次从训练样本中训练出一个学习器,根据学习器的表现调整训练样本的分布来训练下一个基学习器,如此迭代,直至产生事先指定的基学习器数目。基本步骤如下:

  1. 设要产生T个基学习器,初始化训练样本权重D_1,一般初始化时每个样本的权重相等,设共有m个训练样本,则初始化权重为1/m;
  2. 根据训练样本和权重训练出第t个基学习器h_t (x)
  3. 根据 h_t (x)预测值和f(x)真实值 计算错误率ϵ_1ϵ_1\epsilon _t,如果 \epsilon _t大于0.5,则说明该基学习器的性能不好,需要重新训练。若\epsilon _t 符合要求,则通过以下公式计算该基学习器的权重α_j\alpha _t
                                                                    \alpha _t=1/2 ln((1-\epsilon _t) / \epsilon _t )
  4. 然后根据得到的\alpha _t ,按以下公式计算得到调整后的样本权重D_{t+1}D_{t+1} = \frac{D_{t}(x)exp(-\alpha _{t}f(x)h_{t}(x))}{Z_{t}}

      其中,Z_{t}为归一化常数,Z_{t} = 2\sqrt{\epsilon _{t}(1-\epsilon _{t})}
  5. 重复2-4步,直至产生T个基学习器。
  6. 最后集成后输出为H(x)=sign(\sum _{t=1}^T\alpha _t h_t (x) )

        AdaBoost的简单实现代码如下  ,为了突出AdaBoost的思想,简化学习器的训练过程,代码中学习器的训练过程只是根据样本分布在三个弱学习器中错误率最低的一个。训练数据集使用了周志华《机器学习》中的西瓜数据集3.0α。

import numpy as np
import math

def loadTrainData():
    #西瓜数据集3.0α
    dataSet=[[0.697, 0.460],
             [0.774, 0.376],
             [0.634, 0.264],
             [0.608, 0.318],
             [0.556, 0.215],
             [0.403, 0.237],
             [0.481, 0.149],
             [0.437, 0.211],
             [0.666, 0.091],
             [0.243, 0.267],
             [0.245, 0.057],
             [0.343, 0.099],
             [0.639, 0.161],
             [0.657, 0.198],
             [0.360, 0.370],
             [0.593, 0.042],
             [0.719, 0.103]]
    #1代表好瓜,-1代表坏瓜    
    classLabel=[1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1]    
    return dataSet, classLabel

#弱学习器1,单一使用准确率约为0.706
def h1(x):
    if x[0] <= 0.37:
        return -1
    else:
        return 1

#弱学习器2,单一使用准确率约为0.824
def h2(x):
    if x[1] <= 0.2:
        return -1
    else:
        return 1
#弱学习器3,单一使用准确率约为0.765
def h3(x):
    if (x[0] + x[1]) <= 0.9:
        return -1
    else:
        return 1

#根据样本分布和弱学习器的输出计算错误率
def getErrorRate(predVals, classLabel, w = 1):
    predLabel = np.array(predVals)
    diff = (predLabel - classLabel) ** 2
    diff = np.where(diff == 0, 0, 1)
    error = sum(diff * w)
    return error
    
#将训练过程简化为在三个弱学习器中选择错误率最小者 
def chooseBestLearner(dataset, classLabel, w):
    temp = [h1(x) for x in dataset]
    e1 = getErrorRate(temp, classLabel, w)
    temp = [h2(x) for x in dataset]
    e2 = getErrorRate(temp, classLabel, w)
    temp = [h3(x) for x in dataset]
    e3 = getErrorRate(temp, classLabel, w)
    if min(e1, e2, e3) == e1:
        return 'h1', e1
    elif min(e1, e2, e3) == e2:
        return 'h2', e2
    else:
        return 'h3', e3
        
#更新权值和样本分布   
def paraUpdata(dataset, classLabel, T):
    w = np.ones(len(dataset))
    w = w / sum(w)
    a = np.zeros(T)
    Ht = []
    label = np.array(classLabel)
    for i in range(T):
        hi, e = chooseBestLearner(dataset, label, w)
        Ht.append(hi)
        if hi == 'h1':
            h = h1
        elif hi == 'h2':
            h = h2
        else:
            h = h3       
        a[i] = 1/2 * math.log((1 - e) / e, math.e)
        for j in range(len(w)):
            w[j]= w[j] * math.exp(-a[i] * label[j] * h(dataset[j]))\
                                     / (2 * math.sqrt(e * (1 - e)))           
    return Ht, a

#根据输入数据预测结果,+1为好瓜,-1为坏瓜
def getResultClass(inputdata, Ht, a):
    result = 0
    if len(Ht) != len(a):
        print('训练参数有误')
        return
    for i in range(len(Ht)):
        if Ht[i] == 'h1':
            h = h1
        elif Ht[i] == 'h2':
            h = h2
        else:
            h = h3
        result += a[i] * h(inputdata)
    return np.sign(result)

#AdaBoost预测,给出输出结果,没有输入数据时输出测试准确率
def adaBoostPred(T, inputdata = -1):
    #获取训练样本和其真实类别
    dataSet, classLabel = loadTrainData()     
    #获取T个基学习器和其权重值                             
    Ht, a = paraUpdata(dataSet, classLabel, T)                                 
    if inputdata == -1:      
        results = []             #记录测试结果                                               
        for x in dataSet:
            results.append(getResultClass(x, Ht, a))
        correct_rate = 1 - getErrorRate(results, classLabel, 1 / len(classLabel)) 
        print('测试准确率为:%.2f'%correct_rate)
    else:
        result = getResultClass(inputdata, Ht, a)
        if result == 1:
            print('好瓜')
        elif result == -1:
            print('坏瓜')

#测试代码
inputdata = [0.589, 0.697]
adaBoostPred(3, inputdata)
adaBoostPred(3)

        AdaBoost的 测试代码的运行结果如下,当迭代次数为3时,准确率约为0.94,而3个基学习器单一使用的准确率分别约为0.706、0.824和0.765。可见当迭代次数为3时,已经获得不错的集成效果。

        

      

        Bagging是并行式集成学习方法最著名的代表,基本思想都是通过样本扰动使基学习器具备多样性,基于自助采样法。随机森林是Bagging的一个扩展变体。随机森林在以决策树为基学习器构建Bagging集成的基础上,在决策树训练的过程中引入了随机属性选择。即假设当前结点的属性集合大小为d,则在选择最优划分属性之前,先从这d个属性中随机抽取k个属性,再从这k个属性中选择最优划分属性,通常k = log_2{d}。除了在保留Bagging的通过样本扰动增加学习器多样性,随机森林还通过引入输入属性扰动来增加学习器的多样性,其训练效sui要高于Bagging。随机森林的基本步骤如下:

  1. 设原始训练数据集大小为m,使用自助采样法,可采用出T个含m个训练样本的采样集;
  2. 使用第t个采样集作为训练样本生成第t棵决策树h_{t};(在生成决策树时,在某个结点的选择最优划分属性之前,假设此时有d个属性,则先从中抽取k = log_2{d}个属性作为候选属性,然后最优划分属性从候选属性中选出)
  3. 迭代执行第2步,直至生成T棵决策树;
  4. 最后集成T棵决策树的结果,使用相对多数投票法,输出为T棵决策树预测结果中出现最多的那个值;

      若是实现Bagging,则上述步骤2不要括号部分!

      随机森林的代码如下,其中在决策树的构造的基本思想上,参考了这个博客

       https://blog.csdn.net/csqazwsxedc/article/details/65697652

# -*- coding: utf-8 -*-
"""
Created on Fri Nov 16 20:21:13 2018

@author: Administrator
"""
import numpy as np
import math 
import operator
def loadTrainData () :
    #西瓜数据集2.0
    postingList = [['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
                   ['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
                   ['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
                   ['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
                   ['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
                   ['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '好瓜'],
                   ['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘', '好瓜'],
                   ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑', '好瓜'],
                   ['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜'],
                   ['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘', '坏瓜'],
                   ['浅白', '硬挺', '清脆', '稍糊', '平坦', '硬滑', '坏瓜'],
                   ['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘', '坏瓜'],
                   ['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑', '坏瓜'],
                   ['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑', '坏瓜'],
                   ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '坏瓜'],
                   ['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑', '坏瓜'],
                   ['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜']]
    
    property = ['色泽', '根蒂', '敲声', '纹理', '脐部', '触感']       #属性集            
    classLabel = set([example[-1] for example in postingList])
    classLabel = list(classLabel)                                   #类别集
    return postingList, property, classLabel

#自助采样法采样生成T个训练集和测试集
def createSample(dataSet, T):
    trainSample=[]              #记录训练数据集,共T组
    testSample=[]               #记录分组测试集,共T组                                               
    for i in range(T):
        temp=[]
        #在原数据集中随机采样
        for j in range(len(dataSet)):         
            index = np.random.randint(0,len(dataSet))                          
            temp.append(dataSet[index])
        trainSample.append(temp)
        #原数据集和训练数据集的差集为测试数据集集
        remainData = [item for item in dataSet if item not in temp]    
        #对于训练集与原始数据集的差集的长度小于2的偶然情况
        #任意选择原数据集中的两个数据加入作为测试集       
        if len(remainData) < 2:                                                
            for k in range(2):                                                 
                extData = dataSet[np.random.randint(0, len(dataSet))]
                remainData.append(extData)                                    
        testSample.append(remainData)
    return trainSample, testSample

#计算数据集的信息熵
def calcInfoEntropy(dataSet):                                                  
    datalength = len(dataSet)                                                  
    labelCount = {}              #字典用类别作为key,value为出现次数,统计类别概率
    for data in dataSet:
        label = data[-1]         #获取该条数据的类别
        if label  not in labelCount.keys():
            labelCount[label] = 0
        labelCount[label] += 1
    InfoEntropy = 0              #记录信息熵
    for key in labelCount:
        pk = float (labelCount[key]) / datalength
        InfoEntropy -= pk * math.log(pk, 2)
    return InfoEntropy

#获取按某属性值分类之后的数据集       
def splitDataSet(dataSet, axis, value):                                        
    new_DataSet=[]               #记录分类后的数据集
    for data in dataSet:
        if data[axis] == value:
            ret_data = data[:axis]
            ret_data.extend(data[axis+1:])
            new_DataSet.append(ret_data)
    return new_DataSet

#选择最优的划分属性,返回其下标
def chooseBestPropertyToSplit(dataSet):  
    numFeatures = len(dataSet[0])-1
    baseEntropy = calcInfoEntropy(dataSet)           #计算分类前数据集的原始熵
    bestInfoGain = 0                                 #记录最大的信息增益
    bestFeature = -1                                 #记录最佳分类属性的下标
    choosedIndex = []                                #记录被选中的属性
    #按随机森林的特性,在划分最佳划分属性之前,在原来数据属性集中随机选出候选的属性集
    for i in range(math.floor(math.log(numFeatures, 2)) + 1):
        index = np.random.randint(0, numFeatures)
        while index in choosedIndex:
            index = np.random.randint(0, numFeatures)
        choosedIndex.append(index)
    #计算每候选属性相应的信息增益
    for i in choosedIndex:
        featList = [example[i] for example in dataSet]       #某属性的取值集合
        featLabels = set(featList)
        newEntropy = 0                                       #记录分支结点的信息熵
        for value in featLabels:
            subDataSet = splitDataSet(dataSet,i,value)
            pb = len(subDataSet) / float(len(dataSet))
            newEntropy += pb * calcInfoEntropy(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
    #按字典中的value排序,降序
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(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)
    bestPropertyIndex = chooseBestPropertyToSplit(dataSet)     #选择最优划分属性
    bestPropertyLabel = labels[bestPropertyIndex]
    myTree = {bestPropertyLabel:{}}               #分类结果以字典形式保存
    del(labels[bestPropertyIndex])                #更新属性集,去除最佳划分属性
    featValues = [example[bestPropertyIndex] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:                      #递归构造该属性结点的分支
        subLabels = labels[:]
        myTree[bestPropertyLabel][value] = createTree(splitDataSet\
                            (dataSet, bestPropertyIndex, value), subLabels)
    return myTree 

#递归查找决策树,返回查找结果
def seekTree(inputdata, Tree, classLabel):
    classLabel = list(classLabel)  
    if len(Tree.keys()) != 1:         #检查决策树构造
        return '决策树有误'
    for key in Tree.keys():
        property = key    
    subTree = Tree[property]     
    #查找某个属性结点的分支子树                                              
    for value in subTree.keys():
        if value in inputdata:        #根据输入数据查找
            if subTree[value] in classLabel:        #检查决策树是否到叶子结点
                return subTree[value]
            else:
                #未到叶子结点则继续递归查找
                return seekTree(inputdata, subTree[value], classLabel)         
    return '输入属性有误'

#获取多个决策树的投票结果,规则是少数服从多数
def getVoteResult(inputdata, trees, classLabel):                              
    results = []             #记录投票结果
    for tree in trees:
        results.append(seekTree(inputdata, tree, classLabel))  
    voteResult = majorityCnt(results)
    return voteResult

#随机森林预测,输入inputdata则给出RF预测结果,
#若不输入则输出各决策树和随机森林的测试准确率    
def RFdect(T, inputdata = -1):                                                 
    dataSet, property, classLabel = loadTrainData()
    trainSample, testSample = createSample(dataSet, T)    
    trees=[]          #记录决策树
    #根据自助采样的训练样本训练决策树,每组样本对应一个决策树
    for train_x in trainSample: 
        co_property = property[:]
        tree = createTree(train_x, co_property)
        trees.append(tree)
    if inputdata != -1:
        #根据输入数据给出RF预测结果
        pre_result = getVoteResult(inputdata, trees, classLabel)               
        return pre_result
    else:
        #计算各决策树的准确率
        for i in range(len(trees)): 
            correct_numi = 0             #记录决策树预测正确的次数
            for j in range(len(testSample[i])):    
                #预测结果
                pre_result = seekTree(testSample[i][j], trees[i], classLabel) 
                #真实结果
                real_result = testSample[i][j][-1]                             
                if pre_result == real_result:
                    correct_numi += 1
            correct_ratei = correct_numi / len(testSample[i])       #计算准确率
            print('决策树{num}测试准确率为:+ {correct_rate}'.format\
                  (num = i + 1, correct_rate = correct_ratei))
        #计算随机森林的准确率   
        correct_num = 0             #记录RF预测正确的次数                                
        for i in range(len(dataSet)):
            pre_result = getVoteResult(dataSet[i], trees, classLabel)
            if pre_result == dataSet[i][-1]:
                correct_num += 1
        correct_rate = correct_num / len(dataSet)
        print('随机森林的测试准确率为%.2f' % correct_rate)
        return '测试样本的测试结果如上'

#测试代码
inputdata = ['浅白', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑']  
print(RFdect(6,inputdata))       
print('-----------------------------------')
print(RFdect(6))

       代码运行结果如下,可见当基学习器的数量为6时,随机森林在该测试数据集中的准确率达到100%,而6个决策树单一使用的准确率都比较低。虽然本例中各个决策树的测试数据集可能偏小造成决策树的准确率偏低,但是效果都是不如随机森林的。说明了随机森林有不错的集成效果。

       

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值