机器学习领域有一个强大的思路:集成学习,该方法在诸多机器学习竞赛中往往能够获得最优的结果。集成学习的基本思想实际上非常简单:三个臭皮匠顶一个诸葛亮,即将多个模型组合在一起获得的效果往往要强于单一模型。
目录
1.bagging(bootstrap aggregating)
集成学习概念
集成学习通常指训练多个模型并将他们结合起来解决相同问题,以此获得更好的结果。一般来训练的多个模型本身被称之为基础模型,基础模型通常都是弱学习器。弱学习器通常本身性能较差,要么偏差高(预测准确度差),要么方差高(容易过拟合)。选择弱学习器作为基础模型的一个重要原因是集成学习本身就可以获得较好的效果,如果使用强学习器作为基础模型的话将非常容易造成过拟合。
在集成学习中我们通常认为:多个弱学习器被正确组合时,可以消除原本基础模型存在的问题(高偏差/高方差),获得一个强学习器。这些弱学习器一般是相同的(如以 CART 等弱学习器作为基础模型),但是也有一些方法会使用不同的弱学习器。不过正确使用方法的重点在于我们组合的方法必须能够解决基础模型的问题。当我们使用高偏差低方差的模型作为基础模型时,我们的方法必须可以(或者最少是倾向于)减小偏差,反之亦然。
1.bagging(bootstrap aggregating)
Bagging即套袋法,在多数情况下,bagging 方法提供了一种非常简单的方式来对单一模型(同一个算法在不同的训练集上训练多个模型)进行改进,而无需修改背后的算法。
其算法过程如下:
- 从原始样本集中抽取训练集。每轮从原始样本集中使用Bootstraping的方法抽取n个训练样本(在训练集中,有些样本可能被多次抽取到,而有些样本可能一次都没有被抽中)。共进行k 轮抽取,得到k 个训练集。(k 个训练集之间是相互独立的)
- 每次使用一个训练集得到一个模型,k个训练集共得到k 个模型。(模型可以任意选择)
- 对分类问题:将上步得到的k个模型采用投票的方式得到分类结果;
- 对回归问题:计算上述模型的均值作为最后的结果。(所有模型的重要性相同)
1.1.随机森林
(Random Forest,RF)
随机森林是一种使用 Bagging 集成学习思想的模型,其中基础模型被限定为决策树。
在随机森林中,每棵树的按照如下规则生成:
- 如果训练集大小为 N,则对于每棵树来说,都会随机且有放回的从训练集中抽取 N 个样本作为这棵树的训练集(即 Bootstrap sample 方法)。
- 如果每个样本的特征维度为 M,则指定一个常数 m << M,随机的从 M 个特征中选取 m 个特征子集,每次树进行分裂时,从这 m 个特征中选择最优的进行分裂。
- 每棵树都尽最大可能生长,且没有剪枝过程。
袋外错误率(Out-of-Bag Error)
我们可以用一个表格来可视化 Bootstrap sample 的过程:
g1 | g2 | ... | gt | |
---|---|---|---|---|
(x1,y1) | D1 | * | ... | D1 |
(x2,y2) | * | D1 | ... | D1 |
... | ... | ... | ... | ... |
(xn,yn) | D1 | D1 | ... | * |
其中,* 代表了未被抽取到的样本,这些未被抽取到的样本就被称之为袋外样本(out-of-bag example)。我们可以通过具体的公式来计算一下没有被抽到的概率是多少:
这里 N1 是样本被抽中的概率,因此抽不中的概率就是 1−1/N。根据上面的计算,大约有 30% 的样本实际上是取不到的,而这些没有被取到的样本我们可以用来作为验证集对预测的结果进行验证。一般我们把这个过程称之为 self-validation。在 RF 中我们一般都会使用 self-validation 的结果作为最终的误差结果,符号为Eoob 。
特征选择(Feature Selection)
作为一种基于决策树的集成学习方法,RF 在很大程度上继承了决策树的特点,因此在 RF 中我们同样可以对特征重要性进行测试和筛选。
RF 中常见的特征选择思路可以概括为:
对于某个特征,使用随机值替换原本的特征值。如果在替换后模型表现更差了,那就说明这个特征是相对重要。相反如果模型表现没有太大区别,则说明这个特征没有这么重要。
替代值的选择一般有两个思路:
- 插入符合高斯分步的随机值
- 将特征内的值随机打乱
相对来说第二种方法更好一些,因为这样我们不用担心改变原有数据集的数据分布。在衡量打乱前后的表现时常用的方法是:训练时使用原有的样本集,但是在 OOB 验证时将 OOB 样本的对应特征随机打乱并进行验证。
特征选择的优点为:通过删去无关特征可以将模型简化,提高模型的解释性并防止由于特征过多而出现过拟合。
特征选择的缺点为:筛选特征时计算量较大,同时这个过程并不能保证选择出来的都是相关性大的特征,如果无关特征较多则会导致解释性变差。
1.2.代码示例
我们使用随机森林完成鸢尾花分类任务:
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
import numpy as np
from matplotlib.colors import ListedColormap
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use('TkAgg')
iris=load_iris()
print(iris.feature_names)
data=iris.data
print(data)
target=iris.target
print(np.unique(target))
X = iris.data[:,[2,3]]
y = iris.target
print('Class labels:', np.unique(y))
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1, stratify=y)
print(X_train.shape, y_train.shape)
def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02):
# setup marker generator and color map
markers = ('s', 'x', 'o', '^', 'v') # 定义不同的标记样式,用于不同类别的数据点
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan') # 定义颜色列表,用于不同类别的数据点
cmap = ListedColormap(colors[:len(np.unique(y))]) # 创建一个颜色映射,颜色数量与类别的数量相同
# plot the decision surface
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))
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T) # 使用分类器预测网格上每个点的类别
Z = Z.reshape(xx1.shape) # 将预测结果重塑为网格的形状
plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap) # 绘制决策区域的填充等高线图
plt.xlim(xx1.min(), xx1.max()) # 设置x轴的显示范围
plt.ylim(xx2.min(), xx2.max()) # 设置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=colors[idx], # 设置点的透明度和颜色
marker=markers[idx], label=cl, # 设置点的标记样式和标签
edgecolor='black') # 设置点边缘的颜色
# highlight test samples
if test_idx: # 如果提供了测试集索引
# plot all samples
X_test, y_test = X[test_idx, :], y[test_idx] # 获取测试集的数据和标签
plt.scatter(X_test[:, 0], X_test[:, 1], # 绘制测试集的数据点
c='white', edgecolor='black', alpha=1.0, # 设置点的颜色、边缘颜色和透明度
linewidth=1, marker='o', # 设置点的线宽和标记样式
s=100, label='test set') # 设置点的大小和标签
forest = RandomForestClassifier(criterion='gini', # 划分准则
n_estimators=25, # 25个基学习器(决策树)
random_state=1,
n_jobs=2) # 并行执行
forest.fit(X_train, y_train)
X_combined = np.vstack((X_train, X_test))
y_combined = np.hstack((y_train, y_test))
plot_decision_regions(X_combined, y_combined, classifier=forest, test_idx=range(105,150))
plt.xlabel('petal length')
plt.ylabel('petal width')
plt.legend(loc='upper left')
plt.show()
2.Boosting
增长,推进‘
boosting是组合多个弱学习器形成一个强学习器,
遵循下面几个步骤:
- 步骤1:所有分布下的基础学习器对于每个观测值都应该有相同的权重
- 步骤2:如果第一个基础的学习算法预测错误,则该点在下一次的基础学习算法中有更高的权重
- 步骤3:迭代第2步,直到到达预定的学习器数量或预定的预测精度。
最后,将输出的多个弱学习器组合成一个强的学习器,提高模型的整体预测精度。Boosting总是更加关注被错误分类的弱规则。
Boosting算法的底层可以是任何算法,关于boosting算法,我们需要知道其中最有名的3个算法:
- AdaBoost(Adaptive Boosting)
- GBM(Gradient Boosting Machine)
- XGBoost
2.1.AdaBoost
假设我们有一个分类问题,给定一个训练样本集,从这个分类问题中求出一个粗糙的分类规则(弱分类器)往往要比求一个精确的分类规则(强分类器)容易得多。提升方法便是从弱学习算法出发,反复学习得到一系列弱分类器(又称为基本分类器),然后通过组合这些弱分类器来构成一个强分类器。大多数的提升方法都是改变训练数据的概率分布(或说训练数据的权值分布),来针对不同的训练分布调用弱学习算法学习一系列弱分类器。这样一来,对于提升方法而言,就有了两个问题需要去解决:
- 在每一轮如何改变训练数据的权值或概率分布?
- 如何将弱分类器组合成一个强分类器?
1.AdaBoost针对第一个问题的做法是提高那些被前一轮弱分类器错误分类样本的权值,并降低那些被正确分类的样本的权值。经过一轮的权值加大后,后一轮的弱分类器就会更关注那些没有被正确分类的样本。持续下去,分类问题便会被一系列弱分类器“分而治之”。
2.而对于第二个问题,即弱分类器的组合,AdaBoost采取加权多数表决法,具体的所,就是加大误差率小的弱分类器的权值,使其在表决中起更大的作用,另一方面,减小分类误差率大的弱分类器的权值,使其在表决中起较小的作用。
下面这张图对Ada-boost做了恰当的解释:
- Box 1: 你可以看到我们假设所有的数据点有相同的权重(正号、负号的大小都一样),并用一个决策树桩D1将它们分为两部分。我们可以看到这个决策树桩将其中的三个正号标记的数据点分类错误,因此我们将这三个点赋予更大的权重交由下一个预测树桩进行分类。
- Box 2: 在这里你可以看到三个未被正确分类的(+)号的点的权重变大。在这种情况下,第二个决策树桩D2试图将这三个错误的点准确的分类,但是这又引起新的分类错误,将三个(-)号标记的点识别错误,因此在下一次分类中,这三个(-)号标记的点被赋予更大的权重。
- Box 3: 在这里三个被错误分类的(-)号标记的点被赋予更大的权重,利用决策树桩D3进行新的分类,这时候又产生了新的分类错误,图中用小圆圈圈起来的一个负号点和两个正号点
- Box 4: 在这里,我们将D1、D2和D3三个决策器组合起来形成一个复杂的规则,你可以看到这个组合后的决策器比它们任何一个弱分类器表现的都足够好。
该原理可同样用于回归算法。它在不同权重的训练数据集上生成一系列的弱学习器,最开始的时候所有的数据拥有相同的权重,对于第一个分类器没有正确分类的点则在下一个决策器中的权重将会加大,作为一个迭代的过程,直到分类器数量达到预定值或预测精度达到预定值。大多数情况下,我们在AdaBoost中使用decision stamps。但是如果它可以接受带有权重的训练集,我们也可以使用其他任何的机器学习算法作为基础学习器。我们可以使用AdaBoost算法解决分类和回归问题。
2.1.1.算法原理
Adaboost算法可以简述为三个步骤:
- 首先,是初始化训练数据的权值分布。假设有N个训练样本数据,则每一个训练样本最开始时,都被赋予相同的权值:w1=1/N。
- 然后,训练弱分类器hi。具体训练过程中是:如果某个训练样本点,被弱分类器hi准确地分类,那么在构造下一个训练集中,它对应的权值要减小;相反,如果某个训练样本点被错误分类,那么它的权值就应该增大。权值更新过的样本集被用于训练下一个分类器,整个训练过程如此迭代地进行下去。
- 最后,将各个训练得到的弱分类器组合成一个强分类器。各个弱分类器的训练过程结束后,加大分类误差率小的弱分类器的权重,使其在最终的分类函数中起着较大的决定作用,而降低分类误差率大的弱分类器的权重,使其在最终的分类函数中起着较小的决定作用。 换而言之,误差率低的弱分类器在最终分类器中占的权重较大,否则较小。
具体步骤:
步骤1. 首先,初始化训练数据的权值分布。每一个训练样本最开始时都被赋予相同的权值:1/N。
步骤2. 进行多轮迭代,用 j=1,2,...,M 表示迭代的第多少轮
- 使用具有权值分布 Dm 的训练数据集学习,得到基本分类器(选取让误差率最低的阈值来设计基本分类器):
- 对于每个分类器而言,分类误差率的计算:
ei 为误差率,及分类不正确的比例.
3. 基于得到的分类误差率,可以进一步计算得到第i个分类器的权重系数:
4.更新训练数据集的权值分布(目的:得到样本的新的权值分布),用于下一轮迭代
2.2.2.算法实例
👇下面我们通过给定的下列训练样本,用AdaBoost算法学习一个强分类器。
序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | X |
---|---|---|---|---|---|---|---|---|---|---|
X | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Y | 1 | 1 | 1 | -1 | -1 | -1 | 1 | 1 | 1 | -1 |
求解过程:初始化训练数据的权值分布,令每个权值W1i = 1/N = 0.1,其中,N = 10,i = 1,2, ..., 10,然后分别对于m = 1,2,3, ...等值进行迭代。
拿到这10个数据的训练样本后,根据 X 和 Y 的对应关系,要把这10个数据分为两类,一类是“ 1 ”,一类是“ -1 ”,根据数据的特点发现:“0 1 2” 这3个数据对应的类是“ 1 ”,“3 4 5”这3个数据对应的类是“ -1 ”,“6 7 8”这3个数据对应的类是“ 1 ”,9是比较孤独的,对应类“ -1 ”。抛开孤独的9不讲,“0 1 2”、“3 4 5”、“6 7 8”这是3类不同的数据,分别对应的类是1、-1、1,直观上推测可知,可以找到对应的数据分界点,比如2.5、5.5、8.5 将那几类数据分成两类。当然,这只是主观臆测,下面实际计算下这个过程。
迭代过程p1
对于m=1,在权值分布为D1(10个数据,每个数据的权值皆初始化为0.1)的训练数据上,经过计算可得:
- 阈值v取2.5时误差率为0.3(x < 2.5时取1,x > 2.5时取-1,则6 7 8分错,误差率为0.3),
- 阈值v取5.5时误差率最低为0.4(x < 5.5时取1,x > 5.5时取-1,则3 4 5 6 7 8皆分错,误差率0.6大于0.5,不可取。故令x > 5.5时取1,x < 5.5时取-1,则0 1 2 9分错,误差率为0.4),
- 阈值v取8.5时误差率为0.3(x < 8.5时取1,x > 8.5时取-1,则3 4 5分错,误差率为0.3)。
所以无论阈值v取2.5,还是8.5,总得分错3个样本,故可任取其中任意一个如2.5,弄成第一个基本分类器为:
ei=0.1*3=0.3,
G1系数:
α1 代表 G1 在最终的分类函数中所占的权重,为0.4236。接着更新训练数据的权值分布,用于下一轮迭代:
值得一提的是,由权值更新的公式可知,每个样本的新权值是变大还是变小,取决于它是被分错还是被分正确。即如果某个样本被分错了,则yi * Gm(xi)为负,负负等正,结果使得整个式子变大(样本权值变大),否则变小。
第一轮迭代后,最后得到各个数据新的权值分布 D2 = (0.0715, 0.0715, 0.0715, 0.0715, 0.0715, 0.0715, 0.1666, 0.1666, 0.1666, 0.0715)。由此可以看出,因为样本中是数据“6 7 8”被 G1 分错了,所以它们的权值由之前的0.1增大到0.1666,反之,其它数据皆被分正确,所以它们的权值皆由之前的0.1减小到0.0715。
分类函数:
迭代过程p2
对于m=2,在权值分布为 D2 = (0.0715, 0.0715, 0.0715, 0.0715, 0.0715, 0.0715, 0.1666, 0.1666, 0.1666, 0.0715)的训练数据上,经过计算可得:
- 阈值v取2.5时误差率为0.1666 * 3(x < 2.5时取1,x > 2.5时取-1,则6 7 8分错,误差率为0.1666 * 3),
- 阈值v取5.5时误差率最低为0.0715 * 4(x > 5.5时取1,x < 5.5时取-1,则0 1 2 9分错,误差率为0.07153 + 0.0715),
- 阈值v取8.5时误差率为0.0715 * 3(x < 8.5时取1,x > 8.5时取-1,则3 4 5分错,误差率为0.0715 * 3)。
所以,阈值v取8.5时误差率最低,故第二个基本分类器为:
3,4,5分错了:
2.2.3.算法的优缺点
Adaboost的主要优点有:
- Adaboost作为分类器时,分类精度很高
- 在Adaboost的框架下,可以使用各种回归分类模型来构建弱学习器,非常灵活。
- 作为简单的二元分类器时,构造简单,结果可理解。
- 不容易发生过拟合
Adaboost的主要缺点有:
- 对异常样本敏感,异常样本在迭代中可能会获得较高的权重,影响最终的强学习器的预测准确性。
2.2.4.代码示例
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn import datasets,model_selection
from sklearn.metrics import accuracy_score
# load data
def load_data():
iris=datasets.load_iris() # scikit-learn 自带的 iris 数据集
X_train=iris.data
y_train=iris.target
return model_selection.train_test_split(X_train, y_train,test_size=0.25,random_state=0,stratify=y_train)
X_train,X_test,y_train,y_test=load_data()
ada_clf = AdaBoostClassifier(DecisionTreeClassifier(max_depth=2), n_estimators=500)
ada_clf.fit(X_train, y_train)
y_pre = ada_clf.predict(X_test)
print(accuracy_score(y_test,y_pre))