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 的样本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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% 的测试集:

1
2
3
4
5
6
7
8
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 样本得到的决策树的集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 分类器与单个未裁剪决策树的表现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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))

得到结果如下:

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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 更适用于低偏差的模型,如未修剪的决策树。