Alex_McAvoy

想要成为渔夫的猎手

Bagging 袋装法与随机森林

References:

【引入】

对于集成学习来说,要想得到泛化性能较好的集成,个体学习器应尽可能的相互独立,虽然无法在实际应用中做到,但可以设法令个体学习器尽可能的具有较大的差异

对于给定的训练集,可以对训练样本进行采样,从而产生若干不同的训练子集,再从每个子集中训练出一个基学习器,这样一来,由于训练子集的不同,获得基学习器就可能有较大的差异

同时,基学习器不能太差,如果采样出的每个子集都完全不同,那么每个基学习器只用到了一小部分的训练数据,甚至不足以进行有效的学习,为此,可以考虑使用相互有交叠的采样子集

【Bagging】

Bagging 装袋法,是集成学习的经典算法之一,其本质是引入样本扰动,通过增加样本随机性,以降低方差

Bagging 基于自助采样法(Bootstrap Sampling),即:给定包含 $n$ 个样本的数据集 $D$,每轮有放回的随机选出一个样本放入采样集 $D’$ 中,经过 $n$ 次随机采样后,可以得到包含 $n$ 个样本的采样集 $D’$,有的样本在采样集中多次出现,有的样本在采样集中从未出现

通过自主采样法,初始数据集 $D$ 中约有 $36.8\%$ 的样本从未出现过采样数据集 $D’$ 中

这样一来,可以采样出 $T$ 个含 $n$ 个训练样本的采样集,然后基于每个采样集训练出一个基学习器,最后再将这些基学习器进行结合

对于分类任务,Bagging 常采用相对多数投票法作为结合策略,对于回归任务,则采用简单平均法作为结合策略

关于自助采样法,详见:机器学习的模型选择

【模型平均】

Bagging 是通过结合几个基学习器来降低泛化误差的技术,主要想法是分别训练几个不同的模型,然后让所有模型表决测试样例的输出,这种思想被称为模型平均(Model Averaging)

模型平均奏效的原因是不同的学习器通常不会在测试集上产生完全相同的误差,其是一种减少泛化误差的非常强大可靠的方法

此外,从偏差-方差分解的角度来看,Bagging 主要关注于降低方差,因此它在不剪枝的决策树、神经网络等容易受到样本扰动的学习器上效果更明显

【包外估计】

使用 Bagging 来产生训练数据集有一个好处是,没有必要再使用交叉验证或使用一个独立的测试集来获取误差的无偏估计

这是因为每个基学习器只使用了训练集中约 $63.2\%$ 的样本,剩下的 $36.8\%$ 的样本可用作验证集来对泛化性能进行评估,这种评估方式被称为包外估计(Out-of-bag Estimate)

记 $D_t$ 为基学习器 $f_t(\mathbf{x})$ 所实际使用的训练样本集,令 $H^{\text{oob}}(\mathbf{x})$ 为样本 $\mathbf{x}$ 的包外预测,即仅考虑未使用 $\mathbf{x}$ 训练的基学习器在 $\mathbf{x}$ 上的预测,有:

则 Bagging 泛化误差的包外估计为:

包外估计 $\varepsilon^{\text{oob}}$ 是对泛化误差的一个无偏估计,其结果近似于需要大量计算的 $k$ 折交叉验证

此外,包外样本还有诸多用途,例如:当基学习器是决策树时,可使用包外样本来辅助剪枝,或用于估价决策树中各结点的后验概率以辅助对训练样本结点的处理;当基学习器是神经网络时,可使用包外样本来辅助提前停止,以减小过拟合

【随机森林】

随机森林(Random Forest,RF)是通过 Bagging 将决策树作为基学习器的集成学习算法

随机森林中的随机,是指两个随机性,一个是对训练集采用 Bagging 生成采样训练集来训练基决策树,另一个是在决策树的基础上引入了随机特征选择

具体来说,传统决策树在选择划分特征时,是在当前结点的特征集合中选择一个最优特征

而随机森林是对于基决策树的每个结点,从该结点的 $d$ 个特征中随机选择一个包含 $k$ 个特征的子集,然后再从这个子集中选择一个最优特征用于划分

其中,$k$ 控制了随机性的引入程度,若 $k=d$,则基决策树的构建与传统决策树相同,若 $k=1$,则是随机选择一个特征进行划分,一般情况下,推荐 $k=\log_2 d$

【sklearn 实现】

Bagging

sklearn 中看,Bagging 有两种,一种是用于回归的 BaggingRegressor(),另一种是用于分类的 BaggingClassifier()

1
2
3
4
5
6
7
8
from sklearn.svm import SVC
from sklearn.ensemble import BaggingClassifier

# estimator: 指定的基学习器类型
# n_estimators: 基学习器个数
# oob_score: 进行包外估计
# bootstrap: 有放回的采样
model = BaggingClassifier(estimator=SVC(),n_estimators=10,oob_score=True,bootstrap=True)

随机森林

sklearn 中的鸢尾花数据集为例,选取其后两个特征来实现随机森林

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import confusion_matrix,accuracy_score,classification_report,precision_score,recall_score,f1_score
from matplotlib.colors import ListedColormap

# 特征提取
def deal_data():
iris = load_iris() # sklearn的鸢尾花数据集
# iris分为三类,前50行一类,51-100行一类,101-150行一类
X = iris.data[:, [2, 3]] # 选用后两个特征作为样本特征
y = iris.target #取species列,类别
return X,y

# 数据归一化
def standard_scaler(X_train,X_test):
sc = StandardScaler() # 初始化一个sc对象去对数据集作变换
scaler = sc.fit(X_train) # 归一化,存有计算出的均值和方差
X_train_std = scaler.transform(X_train) # 利用 scaler 进行标准化
X_test_std = scaler.transform(X_test) # 利用 scaler 进行标准化
return X_train_std, X_test_std

# 模型训练
def train_model(X_train_std, y_train):
# 建立随机森林模型
model = RandomForestClassifier(criterion='entropy', n_estimators=10, random_state=1)
# 训练
model.fit(X_train_std, y_train)
return model

# 模型评估
def estimate_model(y_pred, y_test, model):
# 混淆矩阵,三分类情况下,大小为 3*3
cm2 = confusion_matrix(y_test,y_pred)
# 准确率
acc = accuracy_score(y_test,y_pred)
# 正确分类的样本数
acc_num = accuracy_score(y_test,y_pred,normalize=False)
# macro 分类报告
macro_class_report = classification_report(y_test, y_pred,target_names=["类0","类1","类2"])
# 微精确率
micro_p = precision_score(y_test,y_pred,average='micro')
# 微召回率
micro_r = recall_score(y_test,y_pred,average='micro')
# 微F1得分
micro_f1 = f1_score(y_test,y_pred,average='micro')

indicators = {"cm2":cm2,"acc":acc,"acc_num":acc_num,"macro_class_report":macro_class_report,"micro_p":micro_p,"micro_r":micro_r,"micro_f1":micro_f1}
return indicators

# 可视化
def visualization(X, y, classifier, test_id=None, resolution=0.02):
# 创建 color map
markers = ('s', 'x', 'o', '^', 'v')
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(y))])

# 绘制决策边界
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1 #第一个特征取值范围作为横轴
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1 #第二个特征取值范围作为纵轴
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution)) # reolution为网格剖分粒度
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T) # 对组合的特征进行预测,ravel为数组展平
Z = Z.reshape(xx1.shape) # Z是列向量
plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap) # x和y为两个等长一维数组,z为二维数组,指定每一对xy所对应的z值
plt.xlim(xx1.min(), xx1.max()) #对等高线间的区域进行填充
plt.ylim(xx2.min(), xx2.max()) #对等高线间的区域进行填充

# 全数据集,不同类别样本点的特征作为坐标(x,y),用不同颜色画散点图
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.8, c=cmap(idx), marker=markers[idx], label=cl)

# 高亮测试集
if test_id:
X_test, y_test = X[test_id, :], y[test_id]
# c设置颜色,测试集不同类别的实例点画图不区别颜色
plt.scatter(x=X_test[:, 0], y=X_test[:, 1], alpha=1.0, c='gray', marker='^', linewidths=1, s=55, label='test set')

plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

if __name__ == "__main__":
# 特征提取
X, y = deal_data()

# 简单交叉验证
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

# 数据标准化
X_train_std, X_test_std = standard_scaler(X_train, X_test)

# 模型训练
model = train_model(X_train_std, y_train)

# 预测结果
y_pred = model.predict(X_test_std)
print("y test:", y_test) # 测试集y值
print("y pred:", y_pred) # 预测y值

# 模型评估
indicators = estimate_model(y_pred, y_test, model)
cm2 = indicators["cm2"]
print("混淆矩阵:\n", cm2)
acc = indicators["acc"]
print("准确率:", acc)
acc_num = indicators["acc_num"]
print("正确分类的样本数:", acc_num)
macro_class_report = indicators["macro_class_report"]
print("macro 分类报告:\n", macro_class_report)
micro_p = indicators["micro_p"]
print("微精确率:", micro_p)
micro_r = indicators["micro_r"]
print("微召回率:", micro_r)
micro_f1 = indicators["micro_f1"]
print("微F1得分:", micro_f1)

# 可视化
X_combined_std = np.vstack((X_train_std, X_test_std))
y_combined = np.hstack((y_train, y_test))
# classifier为分类器,test_id为测试集序号
visualization(X_combined_std, y_combined, classifier=model, test_id=range(105, 150))
感谢您对我的支持,让我继续努力分享有用的技术与知识点!