什么是 Bagging? 它的集成规则是?
如何运用它?它的提升原理是?有效的情况是什么?
集成学习下一篇文章: 机器学习集成算法:一文理解 随机森林(RandomForest) 模型使用以及参数含义
文章目录
前言 – Bagging 概述与基本思想
Bagging 又称为“装袋法”,它是所有集成学习方法当中最为著名、最为简单、也最为有效的操作之一。
在 Bagging 集成当中,我们并行建立多个弱评估器(通常是决策树,也可以是其他非线性算法) ,并综合多个弱评估器的结果进行输出。其规则是当集成算法目标是回归任务时,集成算法的输出结果是弱评估器输出的结果的平均值,当集成算法的目标是分类任务时,集成算法的输出结果是弱评估器输出的结果少数服从多数。
在 sklearn 当中,我们将会接触到两个 Bagging 集成算法,一个是随机森林(RandomForest) ,另一个是极端随机树(ExtraTrees) ,他们都是以决策树为弱评估器的有监督算法,可以被用于分类、回归等各种任务。同时,我们还可以使用 Bagging 的思路对其他算法进行集成,比如使用装袋法分类的类 BaggingClassifier
进行集成,或者自己手写。
注意 Bagging 算法策略中的弱评估器往往是并行生成的。学习器 f(n) 与 f(n-1) 之间没有关系。在自行构建时需要注意这一点。
同时,“少数服从多数” 如果遇到有数量一致的类别,有下面两种方法:
- 从数量一致的类别中随机返回一个类别(需要进行随机设置)
- 从数量一致的类别中,返回编码数字更小的类别(如果使用 argmax 函数)
过程 – Bagging 如何运作
Bagging 算法的运作过程非常简单:从提供的数据中有放回的随机抽样出不同的子集(一般是使用重抽样),用于并行建立多个不同的弱评估器,并按照 Bagging 的规则对单棵决策树的结果进行集成(回归任务是求平均值,分类任务则是少数服从多数)。
假设我们在进行回归任务,我们以下面的代码展示 Bagging 的构建,并帮助大家清晰的了解运行过程:
class bagging():
def __init__(self, baseLearner, n_bagging):
"""
baseLearner: 你选择的弱的评估器
n_bagging: 你要建立多少个不同的评估器
"""
self.learners = [clone(baseLearner) for _ in range(n_bagging)]
def fit(self, X, y):
for learner in self.learners:
examples =np.random.choice(
np.arange.len(X)),int(len(X)),replace=True)
learner.fit(X,iloc[examples,:], y.iloc[examples])
def predict(self, X):
y_preds = [learner.predict(X) for learner in self.learners]
return np.array(y_preds).mean(axis=0)
# 假设训练集为 X,标签为 y,测试集为 X_test
model = bagging(DecideTree, 10)
model.fit(X,y)
y_preds = model.predict(X_test)
当我们使用我们选择好的弱评估器(如代码中所选择的决策树)并传入想要建立的数目参数后,我们便可以得到一个简单的 bagging
集成算法。我们可以使用 bagging.fit(X, y)
进行训练,其中的 for
循环会使用每一个评估器来进行拟合重抽样出来的数据。训练完成后,使用 bagging.predict(X_test)
进行预测,最终结果由每一个评估器得到的结果求均值。
如果你希望知道会不会有更好的结果,可以逐步增加 n_bagging 的值,并结合交叉验证绘制学习曲线,选择最好的那个值
但在实际实践中(比如参加 kaggle 比赛时),我们有时会看到其他的非常规的变种运用,如下面代码所展示的将其和交叉验证结合起来:
def crossvalidate(model, X, Y, test, n_bags = 5, n_splits = 5, seed = 42):
# 记录开始时间
start_time = datetime.datetime.now()
scores = []
test_pred = np.zeros(len(test))
oof = np.zeros(len(X))
cv = KFold(n_splits=n_splits, shuffle=True, random_state=seed)
for fold, (train_index, val_index) in enumerate(cv.split(X, Y)):
X_train, X_val = X.iloc[train_index], X.iloc[val_index]
Y_train, Y_val = Y.iloc[train_index], Y.iloc[val_index]
for i in range(n_bags):
cols_temp = X_train.columns.to_list()
random.Random(i).shuffle(cols_temp)
X_train = X_train[cols_temp]
X_val = X_val[cols_temp]
test = test[cols_temp]
m = clone(model)
m.set_params(random_state = i + seed)
m.fit(X_train, Y_train,eval_set=[(X_val, Y_val)])
y_val_pred = m.predict(X_val)
y_test_pred = m.predict(test)
oof[val_index] += y_val_pred/n_bags
test_pred += y_test_pred/cv.get_n_splits()/n_bags
# 得到每 Fold 中,在 Bagging 上验证集的平均分数
val_score = np.sqrt(mean_squared_error(Y_val, oof[val_index]))
scores.append(val_score)
elapsed_time = datetime.datetime.now() - start_time
print(f"#RMSE: {np.array(scores)}, mean: {np.array(scores).mean():.7f} (+- {np.array(scores).std():.7f})")
print(f"#OOf score: {np.sqrt(mean_squared_error(Y, oof)):.7f} ({int(np.round(elapsed_time.total_seconds() / 60))} min)")
return test_pred, oof, scores
在这个代码中,并没有使用到重采样而是直接使用 KFold
随机采样后的每一折数据训练后直接用于预测。这种做法在某些时候会取得比较好的结果。
原理 – 为什么 Bagging 能取得好的结果?
Bagging 取得好的结果并非“团结就是力量”。在这之前我们需要补充一个知识:泛化误差。
泛化误差是模型在未知数据集上的误差,更低的泛化误差是所有机器学习/深度学习建模的根本目标。在机器学习当中,泛化误差一般被认为由偏差、方差和噪音构成。其中偏差是预测值与真实值之间的差异,衡量模型的精度。方差是模型在不同数据集上输出的结果的方差,衡量模型稳定性。噪音是数据收集过程当中不可避免的、与数据真实分布无关的信息。
以回归算法常用的模型衡量指标 MSE 为例,模型的泛化误差有如下定义:
(该公式可以通过泛化误差、偏差、方差与噪音的定义推导而得,且只适用于 MSE 作为指标的适合)
泛化误差
=
偏
差
2
+
方差
+
噪
音
2
泛化误差 = 偏差^2 + 方差 + 噪音^2
泛化误差=偏差2+方差+噪音2
而 Bagging 的基本思想是借助弱评估器之间的 “独立性” 来降低方差,从而降低整体的泛化误差。这个思想可以被推广到任意的并行使用弱分类器的算法或融合方式上。其中,“降低方差” 指的是 Bagging 算法输出结果的方差一定小于弱评估器输出结果的方差。这也是为什么在相同数据上随机森林往往比单棵决策树更加稳定,也正是因为这个原因,随机森林的泛化能力往往比单棵决策树更强。
所以当我们在使用其他模型的时候,如果发现模型的方差较大,可以尝试使用 Bagging 来得到更稳定的模型。
为什么 Bagging 可以降低方差?
实际上这个问题并不好依靠直觉或者日常生活中的例子来解释,但我们可以借助一点概率论的知识来回答它:
以随机森林为例,假设现在我们执行回归任务,训练后的随机森林中含有 n n n 个弱评估器( n n n 棵决策树),任意弱评估器上的输出结果是 X i X_i Xi,那么所有这些弱评估器输出结果的方差可以被表示为 V a r ( X i ) Var(X_i) Var(Xi)。根据 Bagging 的输出规则我们知道,森林的输出结果等于森林中所有树输出结果的平均值,因此森林的输出可以被表示为 X ˉ = ∑ X i n \bar{X} = \frac{\sum{X_i}}{n} Xˉ=n∑Xi,因此随机森林输出结果的方差可以被表示为 V a r ( X ˉ ) Var(\bar{X}) Var(Xˉ),也可以写作 v a r ( ∑ X i n ) var(\frac{\sum{X_i}}{n}) var(n∑Xi)。
在数学上我们很容易证明:
为了完成这个证明,我们需要几个概率论中常见的定理:
- V a r ( A + B ) = V a r ( A ) + V a r ( B ) Var(A+B)= Var(A)+ Var(B) Var(A+B)=Var(A)+Var(B),其中 A A A 和 B B B 是相互独立的随机变量
- V a r ( a B ) = a 2 V a r ( B ) Var(aB)= a^2 Var(B) Var(aB)=a2Var(B),其中 a a a 是任意常数
假设任意树输出的方差
V
a
r
(
X
i
)
=
σ
2
Var(X_i)=σ^2
Var(Xi)=σ2,则有:
V
a
r
(
X
ˉ
)
=
V
a
r
(
1
n
∑
i
=
1
n
X
i
)
=
1
n
2
V
a
r
(
∑
i
=
1
n
X
i
)
=
1
n
2
(
V
a
r
(
X
1
)
+
V
a
r
(
X
2
)
+
…
+
V
a
r
(
X
n
)
)
=
1
n
2
n
σ
2
=
σ
2
n
\begin{align*} Var(\bar{X}) &= Var\left(\frac{1}{n} \sum_{i=1}^{n} X_i\right) \\ &= \frac{1}{n^2} Var\left(\sum_{i=1}^{n} X_i\right) \\ &= \frac{1}{n^2} \left(Var(X_1) + Var(X_2) + \ldots + Var(X_n)\right) \\ &= \frac{1}{n^2} n \sigma^2 \\ &= \frac{\sigma^2}{n} \end{align*}
Var(Xˉ)=Var(n1i=1∑nXi)=n21Var(i=1∑nXi)=n21(Var(X1)+Var(X2)+…+Var(Xn))=n21nσ2=nσ2
因此,当 n n n 为正整数、且弱评估器之间相互独立时,必然有 V a r ( X ˉ ) Var(\bar{X}) Var(Xˉ) 永远小于 V a r ( X i ) Var(X_i) Var(Xi),这是随机森林的泛化能力总是强于单一决策树的根本原因。同样的,Bagging 降低方差的原理对分类是同样有效。
通过上面的知识,你就能回答为什么 Bagging 算法的效果比单个评估器更好?以及为什么 bagging 可以降低方差?
Bagging 常见问题
Q1: Bagging 有效的基本条件有哪些?Bagging 的效果总是强于弱评估器吗?
在上面的解释当中,我们已经隐隐约约涉及到了这个问题。 Bagging 当然不总是有效的,Bagging 能够提升模型效果的条件有以下三个:
- 弱评估器应是偏差较低的,具体地来说,弱分类器的准确率至少要达到 50%以上不能低于随机随机猜测。
- 弱评估器应是方差较高、不稳定的评估器
- 弱评估器之间相关性弱,最好相互独立
对于第一个条件非常容易解释。Bagging 集成算法是对评估器的预测结果进行平均或用多数表决原则来决定集成评估器的结果。在分类的例子中,如果我们建立了 25 棵树,对任何一个样本而言,平均或多数表决原则下,当且仅当有 13 棵以上(即一半数量)的树判断错误的时候,随机森林才会判断错误。如果单科树全部都只有 20% 的准确率,那就算使用 Bagging 效果也不会有很大改观。
对于第二个条件,在证明 Bagging 降低方差的数学过程中已经申明了,唯有弱评估器之间相互独立、弱评估器输出的结果相互独立时,方差计算公式的前提假设才能被满足,Bagging 才能享受降低方差的福利。
然而在现实中,Bagging 中的弱评估器很难完全相互独立,这有两个原因:
- 所有弱评估器都是在相同的数据上进行训练的
- 所有弱评估器的构建规则是相同的
对于模型构建规则我们是无法改变,因此我们常在数据上下功夫,比如前面定义中使用重抽样改变每个评估器学习的样本分布,来使得各个评估器之间的参数有较大差异,使得它们相对独立。
对于第三个条件,因为 Bagging 是作用于减少泛化误差中方差的集成手段,所以 Bagging 方法擅长处理方差大、偏差低的模型,而不擅长处理方差小、偏差大的模型,这能够解释 Bagging 有效的剩下两个原因。
对于任意算法而言,方差与偏差往往不可兼得,这也很容易理解。想要在当前数据集上获得低偏差,必然意味着需要重点学习当前数据集上的规律,就不可避免地会忽略末知数据集上的规律,因此在不同数据集上进行测试时,模型结果的方差往往很大。
Q2: Bagging 方法可以集成决策树之外的算法吗?
如问题1中所描述的,强大又复杂的算法如决策树、支持向量机等,往往学习能力较强,倾向于表现为偏差低、方差高,这些算法就比较适合于 Bagging。而线性回归、逻辑回归、KNN 等复杂度较低的算法,学习能力较弱但表现稳定,因此倾向于表现为偏差高,方差低,就不太适合被用于 Bagging。这也解答了另一个常见问题: Bagging 除了能用于决策树,还能用于其他弱评估器吗?
Q3: Bagging 如何增强弱评估器间的独立性?
同样在问题1中所讲,在实际使用数据进行训练时,我们很难让 Bagging 中的弱评估器完全相互独立。导致最终建立的弱评估器都大同小异,Bagging 的效力无法完整发挥出来。为了评估器构建规则一致的问题,我们才有了 Averaging 和 Voting 这样的模型融合方法。
不过,也并非完全没有办法,如问题1中所介绍的引入重采样那样,当我们不使用模型融合时,我们可以使用“随机性“来削弱弱分类器之间的联系、增强独立性、提升集成的效果。除了对样本进行抽样,甚至你也可以对特征进行随机抽样,更进一步增强随机性。