机器学习——集成学习

一,个体与集成

集成学习通过构建并结合多个学习器来完成学习任务,也被称为多分类系统,基于委员会的学习。

在这里插入图片描述
上图为集成学习的一般结构。若集成中包含同类的个体学习器,则该集成称为同质集成,其中的个体学习器,称之为“基学习器”,反之称为异质集成,其中的个体学习器称为“组件学习器”。

说明一个概念弱学习器常指泛化性能略优于随机猜测的学习器;例如在二分类问题上精度略高于50%的分类器。

集成学习通过将多个学习器进行结合,常获得比单一学习器显著优越的泛化性能,这对“弱学习器”尤为明显。

一般经验中,如果把好坏不一的东西掺杂在一起,那么最终结果很可能是整体效果比最坏的东西要好一些,但又比最好的那个要坏一些,那么这种情况下不如就让最好的单独去工作,而不要参与混合。但是集成学习还是对多个学习器进行了结合,那它怎么保证整体的效果会比最好的那个单一学习器的效果更好呢。

用一个简单的例子来进行说明:在一个二分类任务重,假设三个分类器在三个测试样本上的表现如下图所示。假设集成学习的结果通过三个个体学习器用投票发(voting)产生,即“少数服从多数”,那么当三个个体学习器分别对三个测试例有不同的判别优势时,集成的效果也会不一样。
在这里插入图片描述
在(a)图中,每个分类器原本只有66.6%的精度,集成学习却达到了100%;(b)图中,每个分类器都是一样的,集成之后性能没有任何提高;在(c)图中,每个分类器的精度只有33.3%,集成之后结果反而变得更糟。
学习器间具有差异性,好的集成的个体学习器应该“好而不同”。

数学分析:

考虑二分类问题 y ∈ y\in y{ − 1 , 1 -1 ,1 1,1} 和真实函数 f f f,假定基分类器的错误率为 ε \varepsilon ε ,即对每个分类器 h i h_i hi
P ( h i ( x ) ≠ f ( x ) ) = ε P(h_i(x)\neq f(x))=\varepsilon P(hi(x)̸=f(x))=ε
假设集成通过简单投票法结合T个分类器,若有超过半数的基分类器正确,则集成分类就正确:
H ( x ) = s i g n ( ∑ i = 1 T h i ( x ) ) H(x)=sign(\sum_{i=1}^{T}h_i(x)) H(x)=sign(i=1Thi(x))
假设分类器的错误率相互独立,则集成的错误率为:
P ( H ( x ) ≠ f ( x ) ) = ∑ k = 0 T / 2 ( T k ) ( 1 − ε ) k ε T − k ⩽ P(H(x)\neq f(x))=\sum_{k=0}^{T/2}\binom{T}{k}(1-\varepsilon)^k{\varepsilon}^{T-k}\leqslant P(H(x)̸=f(x))=k=0T/2(kT)(1ε)kεTkexp ( − 1 2 T ( 1 − 2 ε ) 2 ) (-\frac{1}{2}T(1-2\varepsilon)^2) (21T(12ε)2)

上式显示出,随着集成中个体分类器数目T的增大,集成的错误率将指数下降,最后趋于零。
但是,基学习机的误差之间不是相互独立的。事实上,个体学习器的“准确性”和“多样性”本身就存在冲突,准确率很高之后,要增加多样性就需牺牲准确率。

根据个体学习器的生成方式,目前的集成学习方法可分为两类即序列化方法,例如Boosting算法,以及并行化方法,例如Bagging和Random Forest算法。

二,AdaBoosting

2.1算法原理

AdaBoost算法是Boosting算法较为流行的版本,接下来将会对该算法进行详细描述,并给出python实现代码。
AdaBoost全称是adaptive boosting,该算法基本思想:多个结构较为简单,分类或预测精度较低的弱学习算法可以通过某种方式结合成具有较强学习能力的强学习算法。根据统计学习方法的三要素,AdaBoost 方法=加法模型+指数损失函数(策略)+前向分步 算法。其 运行过程如下:训练数据中的每个样本,赋予其一个权重,这些权重构成全会向量D,D的初始值为1/N,N为训练样本数,首先在训练集上训练处一个弱分类器,并计算该分类器的分类误差率,根据分类误差率计算该分类器在总的集成分类器中所占的比例系数alpha,根据alpha,标签真值,分类估计值来调整训练样本的权值分布,加大错误分类样本的权值,使其在下一次的分类器训练过程中受到更多关注,与此同时减少正确分类样本的权值,之后进行第二次分类器训练,循环指导训练错误率为0或者弱分类器数目达到用户的指定值为止。下图为AdaBoost算法的示意图:
在这里插入图片描述

2.2 基于单层决策树的AdaBoost算法python实现

算法实现伪代码
for i from(1,2,…,M)
1)找出最佳单层决策树:
将最小分类误差率minerror=inf
对数据集中的每一个特征:
对该特征的每个步长(找出决策阈值):
对每个不等号(>=,<):
建立一颗单层决策树(只包含树桩)并利用加权数据集并计算该决策树的分类误差率
如果分类误差率小于minerror,则将当前单层决策树设置成最佳单层决策树。
2)利用单层决策树的分类误差率计算该决策树的比例系数alpha
3)计算更新权重向量D
4)更新累计类别估计值,计算AdaBoost模型的错误率
5)如果错误率为0或者分类器数目i>M,则退出循环

python代码实现

# -*- coding: utf-8 -*-

import numpy as np

def loadSimData():
    '''
    输入:无
    功能:提供一个两个特征的数据集
    输出:带有标签的数据集
    '''
    datMat = np.matrix([[1. ,2.1],[2. , 1.1],[1.3 ,1.],[1. ,1.],[2. ,1.]])
    classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
    return datMat, classLabels

def stumpClassify(dataMatrix,dimen,thresholdValue,thresholdIneq):
    '''
    输入:数据矩阵,特征维数,某一特征的分类阈值,分类不等号
    功能:输出决策树桩标签
    输出:标签
    '''
    returnArray =  np.ones((np.shape(dataMatrix)[0],1))
    if thresholdIneq == 'lt':
        returnArray[dataMatrix[:,dimen] <= thresholdValue] = -1
    else:
        returnArray[dataMatrix[:,dimen] > thresholdValue] = -1
    return returnArray

def buildStump(dataArray,classLabels,D):
    '''
    输入:数据矩阵,对应的真实类别标签,特征的权值分布
    功能:在数据集上,找到加权错误率(分类错误率)最小的单层决策树,显然,该指标函数与权重向量有密切关系
    输出:最佳树桩(特征,分类特征阈值,不等号方向),最小加权错误率,该权值向量D下的分类标签估计值
    '''
    dataMatrix = np.mat(dataArray); labelMat = np.mat(classLabels).T
    m,n = np.shape(dataMatrix)
    stepNum = 10.0; bestStump = {}; bestClassEst = np.mat(np.zeros((m,1)))
    minError = np.inf
    for i in range(n):
        rangeMin = dataMatrix[:,i].min(); rangeMax = dataMatrix[:,i].max()
        stepSize = (rangeMax - rangeMin)/stepNum
        for j in range(-1, int(stepNum)+1):
            for thresholdIneq in ['lt', 'gt']:
                thresholdValue =  rangeMin + float(j) * stepSize
                predictClass = stumpClassify(dataMatrix,i,thresholdValue,thresholdIneq)
                errArray =  np.mat(np.ones((m,1)))
                errArray[predictClass == labelMat] = 0
                weightError = D.T * errArray
                #print "split: dim %d, thresh: %.2f,threIneq:%s,weghtError %.3F" %(i,thresholdValue,thresholdIneq,weightError)
                if weightError < minError:
                    minError = weightError
                    bestClassEst = predictClass.copy()
                    bestStump['dimen'] = i
                    bestStump['thresholdValue'] = thresholdValue
                    bestStump['thresholdIneq'] = thresholdIneq
    return bestClassEst, minError, bestStump

def adaBoostTrainDS(dataArray,classLabels,numIt=40):
    '''
    输入:数据集,标签向量,最大迭代次数
    功能:创建adaboost加法模型
    输出:多个弱分类器的数组
    '''
    weakClass = []#定义弱分类数组,保存每个基本分类器bestStump
    m,n = np.shape(dataArray)
    D = np.mat(np.ones((m,1))/m)
    aggClassEst = np.mat(np.zeros((m,1)))
    for i in range(numIt):
        print "i:",i
        bestClassEst, minError, bestStump = buildStump(dataArray,classLabels,D)#step1:找到最佳的单层决策树
        print "D.T:", D.T
        alpha = float(0.5*np.log((1-minError)/max(minError,1e-16)))#step2: 更新alpha
        print "alpha:",alpha
        bestStump['alpha'] = alpha
        weakClass.append(bestStump)#step3:将基本分类器添加到弱分类的数组中
        print "classEst:",bestClassEst
        expon = np.multiply(-1*alpha*np.mat(classLabels).T,bestClassEst)
        D = np.multiply(D, np.exp(expon))
        D = D/D.sum()#step4:更新权重,该式是让D服从概率分布
        aggClassEst += alpha*bestClassEst#steo5:更新累计类别估计值
        print "aggClassEst:",aggClassEst.T
        print np.sign(aggClassEst) != np.mat(classLabels).T
        aggError = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T,np.ones((m,1)))
        print "aggError",aggError
        aggErrorRate = aggError.sum()/m
        print "total error:",aggErrorRate
        if aggErrorRate == 0.0: break
    return weakClass

def adaTestClassify(dataToClassify,weakClass):
    dataMatrix = np.mat(dataToClassify)        
    m =np.shape(dataMatrix)[0]
    aggClassEst = np.mat(np.zeros((m,1)))
    for i in range(len(weakClass)):
        classEst = stumpClassify(dataToClassify,weakClass[i]['dimen'],weakClass[i]['thresholdValue']\
                                 ,weakClass[i]['thresholdIneq'])
        aggClassEst += weakClass[i]['alpha'] * classEst
        print aggClassEst
    return np.sign(aggClassEst)
if __name__  ==  '__main__':
    D =np.mat(np.ones((5,1))/5)
    dataMatrix ,classLabels= loadSimData()
    bestClassEst, minError, bestStump = buildStump(dataMatrix,classLabels,D)
    weakClass = adaBoostTrainDS(dataMatrix,classLabels,9)            
    testClass = adaTestClassify(np.mat([0,0]),weakClass)

三,Bagging与随机森林

欲得到泛化能力强的集成,集成中的个体学习器应尽可能相互独立;虽然“独立”在现实任务中无法完成,但是可以设法使基学习器尽可能具有较大的差异。 给定一个训练集,一个可能的做法是对训练样本进行采样,产生若干个不同的子集,再从每个训练子集中训练出一个基学习器。这样,由于训练数据的不同得到的基学习器可能会具有比较大的差异。

但是,为了更好地集成,所需要的个体学习器差异不能太大。

3.1 Bagging算法

Bagging算法是并行式集成学习方法最著名的代表,它是基于自主采样法。
基本流程:给定包含m个样本的数据集,随机取出一个样本放入采样集中,再把它放回到原始数据集中,重复m次,得到含m个样本的采样集,可知原始数据集中有63.2%的样本出现在采样集中,有36.8%的样本不会出现在采样集中,这些不在采样集中的样本可以作为验证集对泛化性能进行包外估计(out-of-bag estimate)。进行同样的操作进行T次得到T个每个含m个样本的采样集,基于每个采样集训练一个基学习器,再将基学习器进行组合,一般使用多数投票或求均值的方式来统计最终的分类结果。

Bagging主要关注降低方差,因此在不剪枝决策树、神经网络等易受样本扰动的学习器上效果更加明显。
Bagging方法的训练过程
在这里插入图片描述
Bagging方法的预测过程
在这里插入图片描述
算法描述:
在这里插入图片描述

3.2 随机森林

随机森林(Random Forest)是Bagging的一个扩展变体。RF在以决策树为基学习器构建Bagging集成的基础上,进一步在决策树训练过程中引入随机属性选择。具体来说,传统决策树在划分属性时是在当前节点的属性集合(假设有d个属性)中选择一个最优属性;而在RF中,对及决策树的每个节点,先从该节点的属性集合中随机选择一个包含k个属性的子集,再从子集中选择一个最优属性进行划分。这里的参数k控制了随机性的引入程度:若令k=d,则基决策器的构建与传统决策树相同;若令k=1,则是随机选择一个属性用于划分;一般情况,推荐值k=log d。

随机森林是一种有监督学习算法,是以决策树为基学习器的集成学习算法。随机森林非常简单,易于实现,计算开销也很小,但是它在分类和回归上表现出非常惊人的性能,因此,随机森林被誉为“代表集成学习技术水平的方法”。

随机森林的构建过程
1,从原始训练集中使用Bootstraping方法随机有放回采样取出m个样本,共进行n_tree次采样。生成n_tree个训练集
2,对n_tree个训练集,我们分别训练n_tree个决策树模型
3,对于单个决策树模型,假设训练样本特征的个数为n,那么每次分裂时根据信息增益/信息增益比/基尼指数 选择最好的特征进行分裂
4,每棵树都已知这样分裂下去,知道该节点的所有训练样例都属于同一类。在决策树的分裂过程中不需要剪枝
5,将生成的多颗决策树组成随机森林。对于分类问题,按照多棵树分类器投票决定最终分类结果;对于回归问题,由多颗树预测值的均值决定最终预测结果

注意:OOB(out-of-bag ):每棵决策树的生成都需要自助采样,这时就有1/3的数据未被选中,这部分数据就称为袋外数据。

在这里插入图片描述
scikit-learn随机森林类库概述

sklearn.ensemble模块包含了两种基于随机决策树的平均算法:RandomForest算法和Extra-Trees算法。这两种算法都采用了很流行的树设计思想:perturb-and-combine思想。这种方法会在分类器的构建时,通过引入随机化,创建一组各不一样(diverse)的分类器。这种ensemble方法的预测会给出各个分类器预测的平均。

在sklearn.ensemble库中,我们可以找到Random Forest分类和回归的实现:RandomForestClassifier和RandomForestRegression 有了这些模型后,我们的做法是立马上手操作,因为学习中提供的示例都很简单,但是实际中遇到很多问题,下面概述一下:

∙ \bullet 命名模型调教的很好了,可是效果离我们的想象总有些偏差?——模型训练的第一步就是要定要目标,往错误的方向走太多也是后退。
∙ \bullet 凭直觉调了某个参数,可是居然没有任何作用,有时甚至起到反作用?——定好目标后,接下来就是要确定哪些参数是影响目标的,其对目标是正影响还是负影响,影响的大小。
∙ \bullet 感觉训练结束遥遥无期,sklearn只是一个在小数据上的玩具?——虽然sklearn并不是基于分布式计算环境而设计的,但是我们还是可以通过某些策略提高训练的效率
∙ \bullet 模型开始训练了,但是训练到哪一步了呢?——目标,性能和效率都得了满足后,我们有时还需要有别的追求,例如训练过程的输出,袋外得分计算等等。
  在scikit-learn中,RF的分类类是RandomForestClassifier,回归类是RandomForestRegressor。当然RF的变种Extra Trees也有,分类类ExtraTreesClassifier,回归类ExtraTreesRegressor。由于RF和Extra Trees的区别较小,调参方法基本相同,本文只关注于RF的调参。

3.3 示例——利用随机森林进行特征选择,然后使用SVR进行训练

利用随机森林进行特征选择

import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
 
# url = 'http://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data'
url1 = pd.read_csv(r'wine.txt',header=None)
# url1 = pd.DataFrame(url1)
# df = pd.read_csv(url1,header=None)
url1.columns =  ['Class label', 'Alcohol', 'Malic acid', 'Ash',
              'Alcalinity of ash', 'Magnesium', 'Total phenols',
              'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins',
              'Color intensity', 'Hue', 'OD280/OD315 of diluted wines', 'Proline']
# print(url1)
 
# 查看几个标签
# Class_label = np.unique(url1['Class label'])
# print(Class_label)
# 查看数据信息
# info_url = url1.info()
# print(info_url)
 
# 除去标签之外,共有13个特征,数据集的大小为178,
# 下面将数据集分为训练集和测试集
from sklearn.model_selection import train_test_split
print(type(url1))
# url1 = url1.values
# x = url1[:,0]
# y = url1[:,1:]
x,y = url1.iloc[:,1:].values,url1.iloc[:,0].values
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.3,random_state=0)
feat_labels = url1.columns[1:]
# n_estimators:森林中树的数量
# n_jobs  整数 可选(默认=1) 适合和预测并行运行的作业数,如果为-1,则将作业数设置为核心数
forest = RandomForestClassifier(n_estimators=10000, random_state=0, n_jobs=-1)
forest.fit(x_train, y_train)
 
# 下面对训练好的随机森林,完成重要性评估
# feature_importances_  可以调取关于特征重要程度
importances = forest.feature_importances_
print("重要性:",importances)
x_columns = url1.columns[1:]
indices = np.argsort(importances)[::-1]
for f in range(x_train.shape[1]):
    # 对于最后需要逆序排序,我认为是做了类似决策树回溯的取值,从叶子收敛
    # 到根,根部重要程度高于叶子。
    print("%2d) %-*s %f" % (f + 1, 30, feat_labels[indices[f]], importances[indices[f]]))
 
 
# 筛选变量(选择重要性比较高的变量)
threshold = 0.15
x_selected = x_train[:,importances > threshold]
 
# 可视化
import matplotlib.pyplot as plt
plt.figure(figsize=(10,6))
plt.title("红酒的数据集中各个特征的重要程度",fontsize = 18)
plt.ylabel("import level",fontsize = 15,rotation=90)
plt.rcParams['font.sans-serif'] = ["SimHei"]
plt.rcParams['axes.unicode_minus'] = False
for i in range(x_columns.shape[0]):
    plt.bar(i,importances[indices[i]],color='orange',align='center')
    plt.xticks(np.arange(x_columns.shape[0]),x_columns,rotation=90,fontsize=15)
plt.show()

控制台输出结果:

RangeIndex: 178 entries, 0 to 177
Data columns (total 14 columns):
Class label                     178 non-null int64
Alcohol                         178 non-null float64
Malic acid                      178 non-null float64
Ash                             178 non-null float64
Alcalinity of ash               178 non-null float64
Magnesium                       178 non-null int64
Total phenols                   178 non-null float64
Flavanoids                      178 non-null float64
Nonflavanoid phenols            178 non-null float64
Proanthocyanins                 178 non-null float64
Color intensity                 178 non-null float64
Hue                             178 non-null float64
OD280/OD315 of diluted wines    178 non-null float64
Proline                         178 non-null int64
dtypes: float64(11), int64(3)
memory usage: 19.5 KB
 
 
重要性: [0.10658906 0.02539968 0.01391619 0.03203319 0.02207807 0.0607176
 0.15094795 0.01464516 0.02235112 0.18248262 0.07824279 0.1319868
 0.15860977]
 
 
 1) Color intensity                0.182483
 2) Proline                        0.158610
 3) Flavanoids                     0.150948
 4) OD280/OD315 of diluted wines   0.131987
 5) Alcohol                        0.106589
 6) Hue                            0.078243
 7) Total phenols                  0.060718
 8) Alcalinity of ash              0.032033
 9) Malic acid                     0.025400
10) Proanthocyanins                0.022351
11) Magnesium                      0.022078
12) Nonflavanoid phenols           0.014645
13) Ash                            0.013916

在这里插入图片描述
利用SVR进行训练

from sklearn.svm import SVR  # SVM中的回归算法
import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
# 数据预处理,使得数据更加有效的被模型或者评估器识别
from sklearn import preprocessing
from sklearn.externals import joblib
 
# 获取数据
origin_data = pd.read_csv('wine.txt',header=None)
X = origin_data.iloc[:,1:].values
Y = origin_data.iloc[:,0].values
print(type(Y))
# print(type(Y.values))
# 总特征  按照特征的重要性排序的所有特征
all_feature = [ 9, 12,  6, 11,  0, 10,  5,  3,  1,  8,  4,  7,  2]
# 这里我们选取前三个特征
topN_feature = all_feature[:3]
print(topN_feature)
 
# 获取重要特征的数据
data_X = X[:,topN_feature]
 
# 将每个特征值归一化到一个固定范围
# 原始数据标准化,为了加速收敛
# 最小最大规范化对原始数据进行线性变换,变换到[0,1]区间
data_X = preprocessing.MinMaxScaler().fit_transform(data_X)
 
# 利用train_test_split 进行训练集和测试集进行分开
X_train,X_test,y_train,y_test  = train_test_split(data_X,Y,test_size=0.3)
 
# 通过多种模型预测
model_svr1 = SVR(kernel='rbf',C=50,max_iter=10000)
 
 
# 训练
# model_svr1.fit(data_X,Y)
model_svr1.fit(X_train,y_train)
 
# 得分
score = model_svr1.score(X_test,y_test)
print(score)

控制台输出结果:

0.8211850237886935
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值