跳到主要内容

Bagging

套袋算法 (Bagging) 是一种集成学习技术,与 MajorityVoteClassifier 相似,不同之处在于 bagging 不是每次使用相同的训练集来拟合单个分类器,而是对初始训练集进行 bootstrap 抽样。这也是 bagging 称为引导聚合 (Bootstrap Aggregating) 的原因。

基本概念

Bagging 的概念如下图:

为了更具体说明 bagging 分类器如何工作,参见下图中的示例。图中有 n = 7 个训练实例 (以索引 1 - 7 表示),在每轮 bagging 中进行有放回地抽样 k 次,然后使用这 k 次的聚合样本 (允许包含重复的样本) 来拟合分类器。如果聚合样本中的样本数等于实例,则称为 bootstrap 抽样。

bagging 一般使用在较弱的分类器中使用 (如未修剪的决策树),以降低其过拟合的问题。在对每个分类器进行拟合后,通过多数投票方法来对结果进行预测。

数据导入及预处理

为了对 bagging 方法进行说明,这里使用 Wine 数据集,并提取值为 2 和 3 的分类标签、特征为 Alcohol 和 OD280/OD315 的样本:

import pandas as pd

df_wine = pd.read_csv(
"https://archive.ics.uci.edu/ml/" "machine-learning-databases/wine/wine.data",
header=None,
)
df_wine.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",
]

df_wine = df_wine[df_wine["Class label"] != 1]

y = df_wine["Class label"].values
X = df_wine[["Alcohol", "OD280/OD315 of diluted wines"]].values

将标签进行编码,并将样本分为 80% 的训练集和 20% 的测试集:

from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split


le = LabelEncoder()
y = le.fit_transform(y)

X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=1, stratify=y
)

bagging 的 scikit-learn 实现

使用 sklearn.ensemble.BaggingClassifier 进行 bagging。

这里使用未修剪的决策树作为基础分类器,并建立 500 个由不同 bootstrap 样本得到的决策树的集合:

from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

tree = DecisionTreeClassifier(criterion="entropy", max_depth=None, random_state=1)

bag = BaggingClassifier(
base_estimator=tree,
n_estimators=500,
max_samples=1.0,
max_features=1.0,
bootstrap=True,
bootstrap_features=False,
n_jobs=1,
random_state=1,
)

接下来,计算训练集和测试集中的预测准确性,以比较 bagging 分类器与单个未裁剪决策树的表现:

from sklearn.metrics import accuracy_score

tree = tree.fit(X_train, y_train)
y_train_pred = tree.predict(X_train)
y_test_pred = tree.predict(X_test)

tree_train = accuracy_score(y_train, y_train_pred)
tree_test = accuracy_score(y_test, y_test_pred)
print("Decision tree train/test accuracies %.3f/%.3f" % (tree_train, tree_test))

bag = bag.fit(X_train, y_train)
y_train_pred = bag.predict(X_train)
y_test_pred = bag.predict(X_test)

bag_train = accuracy_score(y_train, y_train_pred)
bag_test = accuracy_score(y_test, y_test_pred)
print("Bagging train/test accuracies %.3f/%.3f" % (bag_train, bag_test))

得到结果如下:

Decision tree train/test accuracies 1.000/0.833
Bagging train/test accuracies 1.000/0.917

尽管 bagging 和决策树对于训练集的预测准确率均为 100%,但在测试集上 bagging 的准确率要高于测试集,泛化能力更优。

对 bagging 和决策树的决策边界分别绘图。

import numpy as np
import matplotlib.pyplot as plt

x_min = X_train[:, 0].min() - 1
x_max = X_train[:, 0].max() + 1
y_min = X_train[:, 1].min() - 1
y_max = X_train[:, 1].max() + 1

xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1), np.arange(y_min, y_max, 0.1))

f, axarr = plt.subplots(nrows=1, ncols=2, sharex="col", sharey="row", figsize=(8, 3))


for idx, clf, tt in zip([0, 1], [tree, bag], ["Decision tree", "Bagging"]):
clf.fit(X_train, y_train)

Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

axarr[idx].contourf(xx, yy, Z, alpha=0.3)
axarr[idx].scatter(
X_train[y_train == 0, 0], X_train[y_train == 0, 1], c="blue", marker="^"
)

axarr[idx].scatter(
X_train[y_train == 1, 0], X_train[y_train == 1, 1], c="green", marker="o"
)

axarr[idx].set_title(tt)

axarr[0].set_ylabel("Alcohol", fontsize=12)
plt.text(
10.2, -0.5, s="OD280/OD315 of diluted wines", ha="center", va="center", fontsize=12
)

plt.tight_layout()
# plt.savefig('images/07_08.png', dpi=300, bbox_inches='tight')
plt.show()

从上图可以看出,bagging 的决策边界相比决策树更为平滑。

在实践中,bagging 往往用于解决由于解决分类器模型的过拟合问题,降低模型的方差。需要注意的是,bagging 无法减少模型偏差 (如某些模型太简单,无法很好地捕捉数据中的趋势)。因此 bagging 更适用于低偏差的模型,如未修剪的决策树。