集成学习——Bagging和Boosting

简介

集成学习,顾名思义就是将多种学习器或算法结合在一起,共同做出决策。这符合人类集思广益的做法,在业界也是应用最为广泛的方法之一。注意,集成学习的本质是如何找到不同的模型,并且将它们有机地进行集成的方法。它是一种方法论,而不是一种具体的算法。

集成学习是一大类模型融合策略和方法的统称,其中包含多种集成学习的思想。

集成学习的核心是要求各个子模型之间必须具有多样性,或者说差异性,同时子模型本身要具有超过随机猜测的准确性,即“好而不同”,否则集成是没有意义的。集成模型如下图所示:


集成学习的思想

数据集行列采样

把训练数据集看成一张二维表,如下图所示,其中不同的矩形框住了不同的数据子集,如果使用这些数据子集就能训练出不同的模型。“行”上的变化,称为行采样。“列”上的变化,称为随机采列,如随机森林的随机取列的方法。通过随机样本和随机特征能够得到不同的随机子集,它们是构造多样性模型的数据原料。
这样的目的是在做集成学习的是,能够让不同的子学习器学习到数据的多样性,从而构建出多种学习大都不同数据分布特征的模型,再进行集成的时候效果会更好。即增加了模型之间的差异性,多样性。


行列采样

算法的同质和异质

构建“好而不同”的模型,除了上述的随机样本和随机特征,不同的算法也是关键。当子模型都是基于同一种模型构建而成的,称这样的集成是同质的。例如随机森林算法中的基模型都是决策树,随机森林是不同决策树组成的森林,所以随机森林是同质的。
随机森林

当子模型基于不同算法构建成的,称这样的集成是异质的,例如使用Stacking方法时,是将多个不同性质的模型结合起来。比如FackBook经典的GBDT+LR的组合。
GBDT+LR

集成方法

集成学习构造的是一个层次化的模型组织,层次化的组合结构必然涉及如何组织、顶层如何汇聚这两个步骤。目前常见的汇聚方法有投票组合法,例如Bagging;有关错误样例的方法例如Boosting等。本文主要介绍这两种主流的集成学习方法。

Bagging方法

Bagging是“Bootstrap Aggregating”的组合缩写,全称翻译为“自举聚合”。使用Bagging方法集成模型,正如这两个单词的意思一样,一共分为两步:

  • 采样行采样,且是有放回的随机采样来扩充数据集
  • 使用投票组合法来集成各个子模型

有放回的随机采样得到的样本称为Bootstrap样本。有放回采样的特点是每个样本都可能会被重复抽中,有的样本可能一直未被抽中。换句话说,Bagging集成方法中某些样本在一个训练样本中出现了多次,而有的样本则一直未被训练。通过对训练样本多次采样,并分别训练出多个不同模型,然后进行综合,来减小集成分类器的方差。但是这都基于一个前提,那就是在样本量足够的情况下,有放回的采样才靠谱。否则如果样本很少,有放回的随机采样,最后构造的样本子集几乎都是重复的数据,那这样无非是在无中生有,自欺欺人。
在进行有放回的随机采样的时候,加入训练样本总数为,当趋于无穷大时,可得到样本中某一个样本一直未被抽样到的概率为0.368,计算方式如下:

这是一个不低的占比,常用来被作为测试。使用Bagging方法本身就能提供模型性能估计,而不必使用交叉验证。使用有放回的随机采样得到不同的样本子集之后,针对每一个子集都训练一个模型,每个模型都进行单独学习,学习的内容可以相同,也可以不同,也可以部分重叠。但是由于个体之间存在差异性,最终做出的判断不会完全一致。在最终做决策时,每个个体单独作出判断,再通过投票的方式做出最后的集体决策。Bagging的集成过程如下:

Bagging集成过程示意图
从上图中可以看出,Bagging方法中需要训练多个子模型,训练时计算量很大,但是好在每个模型彼此之间没有影响,可以并行训练,从而在一定程度上减少了计算大这一劣势。Bagging模型涉及到多个模型的集成,导致原本容易解释的模型在集成之后变成了难以解释的黑箱模型。当然这是所有集成模型的共性——模型难以解释。
Bagging集成方法中最具有代表性的算法就是随机森林(Random Forest),可以认为RF算法是Bagging模型的集大成者,具有广泛的应用。它具有以下特性:

  • 使用随机样本和随机特征,即随机行和列,减少了基模型的相关性
  • 使用实现简单、高效和高精度的决策树作为基模型
  • 可以解决分类和回归问题,也可以直接处理分类和数值特征
  • Bagging抗过拟合和稳定的特性,使得随机森林能在偏差和方差之间权衡

sklearn中包含了随机森林(Random Forest)的实现,使用RF来进行莺尾花分类的代码如下:

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

iris = load_iris()
X_train, X_test, Y_train, Y_test = train_test_split(iris.data, iris.target, random_state=2)

clf = RandomForestClassifier(max_depth=2, random_state=0)
clf.fit(X_train, Y_train)

X_pred = clf.predict(X_test)
print("accuracy rate: ", accuracy_score(Y_test, X_pred))
测试集准确率

随机森林算法的集成示意图如下:

Boosting方法

Boosting方法训练基分类器时采用串行的方式,各个基分类器之间有依赖。它的基本思路是将基分类器层层叠加,每一层在训练的时候,对前一层基分类器分错的样本,给予更高的权重。测试时,根据各层分类器的结果的加权得到最终结果。
Boosting的过程很类似于人类学习的过程,我们学习新知识的过 程往往是迭代式的,第一遍学习的时候,我们会记住一部分知识,但往往也会犯 一些错误,对于这些错误,我们的印象会很深。第二遍学习的时候,就会针对犯 过错误的知识加强学习,以减少类似的错误发生。不断循环往复,直到犯错误的次数减少到很低的程度。

Boosting算法
Boosting提供了一种可操作性的,权衡偏差和方差的实践方法:使用弱学习器获得简单的模型的成本较低,偏差较大,Boosting方法能够根据某种策略不断地将弱学习器提升为强学习器,最终得到偏差和方差平衡的模型。
Boosting方法中最具代表性的算法是AdaBoost(自适应提升)算法,其核心是不断构建新的模型,后一级模型训练的样本是根据前一级模型预测错误情况调整各个样本的权重之后形成的新样本,是一个串行的迭代过程。其中的迭代次数和基模型数量事先指定。随着迭代次数的增加,经验风险趋近于0。Adaboost算法的流程如下图:
Adaboost示意图
注意这是集成的过程,模型的训练过程应该是串行的,前一个训练完,修改样本权重,接着训练下一个模型,最后再集成起来。
Sklearn中也有对应的Adaboost算法的实现,如下:

import numpy as np
from sklearn.datasets import make_moons
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_iris

n = 200
x, y = make_moons(n, noise=0.1, random_state=42)
X_train, X_test, Y_train, Y_test = train_test_split(x, y, random_state=2)

# 单颗决策树
clf = DecisionTreeClassifier(max_depth=3)

# adaboost算法
ada = AdaBoostClassifier(clf, # 基分类器为决策树
                        n_estimators=10,  # 一共使用10个
                        learning_rate=0.2,
                        random_state=4
                        )

# 使用单决策树进行拟合
clf.fit(X_train, Y_train)
X_pred = clf.predict(X_test)
print("single decision tree accuracy rate: ", accuracy_score(Y_test, X_pred))

# 使用adaBoost算法,包含10棵决策树
ada.fit(X_train, Y_train)
X_pred_1 = ada.predict(X_test)
print("adaBoost accuracy rate: ", accuracy_score(Y_test, X_pred_1))

可以看到adaBoost使用了10棵树,准确率是要比单颗决策树提升了不少。
当然Boosting学习方法中还包括梯度提升树等学习算法,这里不赘述,读者有兴趣可以参考GBDT原理分析以及XGBoost代码实践。

Boosting和Bagging的方差与偏差分析

集成学习可以有效地提升弱学习器的性能,即所谓的“三个臭皮匠赛过一个诸葛亮”。那集成学习是如何做到的?本节将从偏差和方差的角度来解释这背后的原理。
首先需要了解一下偏差和方差的基本概念:

  • 偏差指的是由所有采样得到的大小为m的训练数据集训练出的所有模型的输出的平均值和真实模型输出之间的差别。
    偏差通常是由于我们对学习算法做了错误的假设所导致的,比如真实模型是某个二次函数,但我们假设模型是一次函数。 由偏差带来的误差通常在训练误差上就能体现出来。
  • 方差指的是由所有采样得到的大小为m的训练数据集训练出的所有模型的输出的方差。
    方差通常是由于模型的复杂度相对于训练样本数m过高导致的,比如一共有100个训练样本,而我们假设模型是阶数不大于200的多项式函数。由方差带来的误差通常体现在测试误差相对于训练误差的增量上。

上述定义不够直观,直白点说,偏差代表了模型输出结果的准确程度,方差代表了模型输出结构的分散程度。

为了更清晰的理解偏差和方差,我们用一个射击的例子来进一步描述这二者的区别和联系。假设一次射击就是一个机器学 习模型对一个样本进行预测。射中靶心位置代表预测准确,偏离靶心越远代表预 测误差越大。 我们通过n次采样得到n个大小为m的训练样本集合,训练出n个模 型,对同一个样本做预测,相当于我们做了n次射击,射击结果如图所示。我们最期望的结果就是左上角的结果,射击结果又准确又集中,说明模型的偏差和 方差都很小;右上图虽然射击结果的中心在靶心周围,但分布比较分散,说明模 型的偏差较小但方差较大;同理,左下图说明模型方差较小,偏差较大;右下图 说明模型方差较大,偏差也较大。
偏差和方差示意图

先说结论,Bagging能够提高弱分类器性能的原因是降低了方差,Boosting能够提升弱分类器性能的原因是降低了偏差。
对于Bagging方法,假设有个独立的随机变量,各自的方差记为,那么它们的均值对应方差为。推导过程具体可以参考概率论,为什么样本均值的方差为n分之D(X)?。这说明,模型平均能够起到降低模型估计方差的效果。
再从模型的角度理解这个问题,对个独立不相关的模型的预测结果取平均, 方差是原来单个模型的。这个描述不甚严谨,但原理已经讲得很清楚了。当然,模型之间不可能完全独立。为了追求模型的独立性,诸多Bagging的方法做了 不同的改进。比如在随机森林算法中,每次选取节点分裂属性时,会随机抽取一个属性子集,而不是从所有属性中选取最优属性,这就是为了避免弱分类器之间过强的相关性。通过训练集的重采样也能够带来弱分类器之间的一定独立性,从而降低Bagging后模型的方差。

Bagging方法增加了子模型之间的独立性,在集成之后降低了模型的方差。

对于Boosting方法,在训练好一个弱分类器后,我们需要计算弱分类器的错误或者残差,作为下一个分类器的输入。这个过程本身就是在不断减小损失函数,来使模型不断逼近“靶心”,使得模型偏差不断降低,即每一轮都能提高模型预测的准确率。但Boosting的过程并不会显著降低方差。这是因为Boosting的训练过程使得各弱分类器之间是强相关的,缺乏独立性,所以并不会对降低方差有作用。

Boosting方法串行迭代迭代计算,增加了弱分类器的准确率,减少了偏差。

关于泛化误差、偏差、方差和模型复杂度的关系如下图所示。不难看出,方 差和偏差是相辅相成,矛盾又统一的,二者并不能完全独立的存在。对于给定的 学习任务和训练数据集,我们需要对模型的复杂度做合理的假设。如果模型复杂度过低,虽然方差很小,但是偏差会很高;如果模型复杂度过高,虽然偏差降低了,但是方差会很高。所以需要综合考虑偏差和方差选择合适复杂度的模型进行训练。
泛化误差、偏差、方差和模型复杂度的关系

参考

  • 《机器学习--软件工程的实现与方法》
  • 《百面机器学习》
  • https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html

你可能感兴趣的:(集成学习——Bagging和Boosting)