第7章 集成学习和随机森林
写在前面
参考书
《机器学习实战——基于Scikit-Learn和TensorFlow》
工具
python3.5.1,Jupyter Notebook, Pycharm
投票分类器
使用不同的训练方法训练同样的数据集。
from sklearn.ensemble import VotingClassifier:
voting_clf = VotingClassifier( estimators=[ ('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf) ], voting='hard' ) voting_clf.fit(X_train, y_train)
bagging和pasting
每个预测器使用的算法相同,但是在不同的训练集随机子集上进行训练。
采样时样本放回:bagging(boostrap aggregating,自举汇聚法),统计学中,放回重新采样称为自助法(bootstrapping)。
采样时样本不放回:pasting。
from sklearn.ensemble import BaggingClassifier
bag_clf = BaggingClassifier( DecisionTreeClassifier(), n_estimators=500, max_samples=100, bootstrap=True, n_jobs=-1 ) bag_clf.fit(X_train, y_train)
如果想使用pasting,只需要设置bootstrap=False
集成预测的泛化效果很可能比单独的分类器要好一些:二者偏差相近,但是集成的方差更小。(两边训练集上的错误数量差不多,但是集成的决策边界更规则)
由于自助法给每个预测器的训练子集引入了更高的多样性,所以最后bagging比pasting的偏差略高,但这也意味着预测器之间的关联度更低,所以集成的方差降低。
总之,bagging生成的模型通常更好。
包外评估
对于任意给定的预测器,使用bagging,有些实例可能会被采样多次,而有些实例则可能根本不被采样。
BaggingClassifier默认采样m个训练实例,然后放回样本(bootstrap=True),m是训练集的大小。这意味着对于每个预测器来说,平均只对63%的训练实例进行采样。(随着m增长,这个比率接近$1-exp(-1)\approx63.212%$)。剩余37%未被采样的训练实例成为包外(oob)实例。注意,对所有预测其来说,这是不一样的37%。
同个设置oob_score=True自动进行包外评估,通过变量oob_score_可以得到最终的评估分数。
bag_clf = BaggingClassifier( DecisionTreeClassifier(), n_estimators=500, bootstrap=True, n_jobs=-1, oob_score=True ) bag_clf.fit(X_train, y_train) bag_clf.oob_score_
每个训练实例的包外决策函数也可以通过变量oob_decision_function_获得。
bag_clf.oob_decision_function_
Random Patches和随机子空间
- BaggingClassifier也支持对特征进行抽样,这通过两个超参数控制:
max_features
和bootstrap_features
。它们的工作方式跟max_samples和bootstrap相同,只是抽样对象不再是实例,而是特征。因此,每个预测器将用输入特征的随机子集进行训练。 - 这对于处理高维输入特别有用(例如图像)。对训练实例和特征都进行抽样,被称为Random Patches方法。而保留所有训练实例(即bootstrap=False并且max_samples=1.0)但是对特征进行抽样(即bootstrap_features=True并且/或max_features<1.0),这被称为随机子空间法。
极端随机树(Extra-Trees)
- 随机森林里单棵树的生长过程中,每个节点在分裂时仅考虑到了一个随机子集所包含的特征。如果我们对每个特征使用随机阈值,而不是搜索得出的最佳阈值(如常规决策树),则可能让决策树生长得更加随机。
- 这种极端随机的决策树组成的森林,被称为极端随机树(Extra-Trees)。
- 它也是以更高的偏差换取了更低的方差。
- 极端随机树训练起来比常规随机森林要快很多,因为在每个节点上找到每个特征的最佳阈值是决策树生长中最耗时的任务之一。
- from sklearn.ensemble import ExtraTreesClassifier
特征重要性
- 如果你查看单个决策树会发现,重要的特征更可能出现靠近根节点的位置,而不重要的特征通常出现在靠近叶节点的位置(甚至根本不出现)。
- 因此可以通过计算一个特征在森林中所有树上的平均深度,可以估算一个特征的重要程度。
提升法(boosting)
- 提升法(Boosting,假设提升):是指可以将几个弱学习器结合成一个强学习器的任意集成方法。
- 大多数boosting的总体思想是循环训练预测器,每一次都对其前序做出一些改正。
- 可用的提升法有很多,但目前最流行的方法是AdaBoost(自适应提升法,Adaptive Boosting)和梯度提升。
AdaBoost(自适应提升法)
新预测器对其前序进行纠正的办法之一,就是更多的关注前序拟合不足的训练实例。从而使新的预测器不断地越来越专注于难缠的问题,这就是AdaBoost使用的技术。
例如,要构建一个AdaBoost分类器,首先需要训练一个基础分类器(比如决策树),用它对训练集进行预测。然后对错误分类的训练实例增加其相对权重,接着,使用这个最新的权重对第二个分类器进行训练,然后再次对训练集进行预测,继续更新权重,并不断循环向前。
AdaBoost这种依序循环的学习技术跟梯度下降有一些异曲同工之处,差别只在于——不再是调整单个预测器的参数使成本函数最小化,而是不断在集成中加入预测器,使模型越来越好。
这种依序学习技术有一个重要的缺陷就是无法并行,因为每个预测器只能在前一个预测器训练完成并评估之后才能开始训练。因此,在扩展方面,它的表现不如bagging和pasting方法。
循环过程:计算新预测器的权重,更新实例权重,然后对另一个预测器进行训练。当达到所需数量的预测器,或得到完美的预测器时,算法停止。
sklearn使用的时AdaBoost的一个多分类版本,叫做SAMME(基于多类指数损失函数的逐步添加模型)。当只有两个类时,SAMME即等同于AdaBoost。此外,如果预测器可以估算类别概率(即具有predict_proba()方法),sklearn会使用一种SAMME的变体,成为SAMME.R(R代表“Real”),它依赖的是类别概率而不是类别预测,通常表现得更好。
from sklearn.ensemble import AdaBoostClassifier from sklearn.tree import DecisionTreeClassifier ada_clf = AdaBoostClassifier(DecisionTreeClassifier(max_depth=1), n_estimators=200, algorithm="SAMME.R", learning_rate=0.5) ada_clf.fit(X_train, y_train)
如果你的AdaBoost集成过度拟合训练集,你可以试试减少estimator数量,或提高基础estimator的正则化程度。
梯度提升(Gradient Boosting)
跟AdaBoost一样,梯度提升也是逐渐在集成中添加预测器,每一个都对其前序做出改正。不同之处在于,它不是像AdaBoost那样在每个迭代中调整实例权重,而是让新的预测器针对前一个预测器的残差进行拟合。
使用决策树作为基础预测器,叫做梯度提升树(GBDT)或梯度提升回归树(GBRT)。
GBDT中的树是回归树(不是分类树),GBDT用来做回归预测,调整后也可以用于分类。
from sklearn.datasets import load_boston from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeRegressor import matplotlib.pyplot as plt # 建立数据 boston = load_boston() X = boston["data"] y = boston["target"] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) # 首先,在训练集上拟合一个DecisionTreeRegressor tree_reg1 = DecisionTreeRegressor(max_depth=2) tree_reg1.fit(X_train, y_train) # 现在,针对第一个预测器的残差,训练第二个DecisionTreeRegressor y2 = y_train - tree_reg1.predict(X_train) tree_reg2 = DecisionTreeRegressor(max_depth=2) tree_reg2.fit(X_train, y2) # 然后针对第二个预测器的残差,训练第三个回归器 y3 = y2 - tree_reg2.predict(X_train) tree_reg3 = DecisionTreeRegressor(max_depth=2) tree_reg3.fit(X_train, y3) # 现在,我们有了一个包含3棵树的集成。它将所有树的预测相加,从而对新实例进行预测。 y_pred = sum(tree.predict(X_test) for tree in (tree_reg1, tree_reg2, tree_reg3)) plt.plot(list(range(len(y_pred))), y_pred, 'r', label="pred", alpha=0.5) plt.plot(list(range(len(y_pred))), y_test, 'b', label="test", alpha=0.5) plt.legend()
训练GBDT集成有个简单的方法
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.ensemble import GradientBoostingRegressor gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0) gbrt.fit(X_train, y_train)
learning_rate:对每棵树的贡献进行缩放。如果你将其设置为低值,则需要更多的树来拟合训练集,但是预测的泛化效果通常更好。这是一种被称为收缩的正则化技术。如果learning_rate较低,那么estimator太少会导致欠拟合,过多会导致过拟合。
要找到树的最佳数量,可以使用早期停止法。
使用staged_predict()方法:它在训练的每个阶段(一棵树,两棵树…)都对集成的预测返回一个迭代器。
# 以下代码训练了一个拥有120棵树的GBRT集成,然后测量每个训练阶段的验证误差,从而找到树的最优数量,最后使用最优树数重新训练了一个GBRT集成。 import numpy as np from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error X_train, X_val, y_train, y_val = train_test_split(X, y) gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=120) gbrt.fit(X_train, y_train) errors = [mean_squared_error(y_val, y_pred) for y_pred in gbrt.staged_predict(X_val)] bst_n_estimators = np.argmin(errors) gbrt_best = GradientBoostingRegressor(max_depth=2, n_estimators=bst_n_estimators) gbrt_best.fit(X_train, y_train) print(bst_n_estimators)
实际上,要实现早期停止法,不一定需要先训练大量的树,然后再回头找最优的数字,还可以真的提前停止训练。设置warm_state=True,当fit()方法被调用时,sklearn会保留现有的树,从而允许增量训练。比如以下代码会在验证误差连续5次迭代未改善时,直接停止训练。
gbrt = GradientBoostingRegressor(max_depth=2, warm_start=True) min_val_error = float("inf") error_going_up = 0 for n_estimators in range(1, 120): gbrt.n_estimators = n_estimators gbrt.fit(X_train, y_train) y_pred = gbrt.predict(X_val) val_error = mean_squared_error(y_val, y_pred) if val_error < min_val_error: min_val_error = val_error error_going_up = 0 else: error_going_up += 1 if error_going_up == 5: # 早期停止 break print(val_error) print(n_estimators)
subsample:指定用于训练每棵树的实例的比例。如果subsample=0.25,则每棵树用25%的随机选择的实例进行训练。这也是用更高的偏差换取了更低的方差,同时在相当大的程度上加速了训练。这种技术称为随机梯度提升。
loss:梯度提升也可以使用其他成本函数。
堆叠法(stacking,层叠泛化法)
- 它基于一个简单的思想:与其使用一些简单的函数(比如说硬投票)来聚合集成中所有预测器的预测,我们为什么不训练一个模型来执行这个聚合呢?
- 训练混合器的常用方法是使用留存集(或者使用折外(out-of-fold)预测也可以。在某些情况下,这才被成为堆叠(stacking),而使用留存集被称为混合(blending)。但是对多数人而言,这二者是同义词。)
- 参考链接:ensamble之stacking详介以及Python代码实现,详解stacking过程
总结
- 集成学习主要包括(Bagging,Boosting,Stacking) 众所周知,计算方法分为并行,串行,树行,他们分别对应以上三个集成方法 并行的目的在于学习模型的稳定性 串行的目的在于解决并行中出现的泛化能力差之类问题 而树形即(stacking)的思想是什么呢? 个人给出两点: 1.人解决问题的思维是树形的,将模型树行化符合问题本身的逻辑,精确率和召回率呈稳态正相关 2.stacking使模型的融合更科学化,分层预测的计算结果远优于向量均值化和投票机制。
练习摘抄
硬投票分类器和软投票分类器有什么区别?
硬投票分类器只是统计每个分类器的投票,然后挑选出得票最多的类别。软投票分类器计算出每个类别的平均估算概率,然后选出概率最高的类别。它比硬投票法的表现更优,因为它给予那些高度自信的投票更高的权重。但是它要求每个分类器都能够估算出类别概率才可以正常工作。(例如,sklearn中的SVM分类器必须要设置probability=True)
是否可以通过在多个服务器上并行来加速bagging集成的训练?pasting集成呢?boosting集成呢?随机森林或stacking集成呢?
对于bagging集成来说,将其分布在多个服务器上能够有效加速训练过程,因为集成中的每个预测器都是独立工作的。
- 同理,对于pasting集成和随机森林来说也是如此。
- 但是,boosting集成的每个预测器都是基于前序的结果,因此训练过程必须是有序的,将其分布在多个服务器上毫无意义。
对于stacking集成来说,某个指定层的预测器之间彼此独立,因而可以在多台服务器上并行训练,但是某一层的预测器只能在其前一层的预测器全部训练完成之后,才能开始训练。
包外评估的好处是什么?
包外评估可以对bagging集成中的每个预测器使用其未经训练的实例进行评估。不需要额外的验证集,就可以对集成实施相当公正的评估。所以,如果训练使用的实例越多,集成的性能可以略有提升。
是什么让极端随机树比一般随机森林更加随机?这部分增加的随机性有什么用?极端随机树比一般随机森林快还是慢?
随机森林在生长过程中,每个节点的分裂仅考虑了特征的一个随机子集。极限随机树也是如此,它甚至走得更远:常规随机树会搜索出特征的最佳阈值,极限随机树直接对每个特征使用随机阈值。这种极限随机性就像是一种正则化的形式:如果随机森林对训练数据出现过拟合,那么极限随机树可能执行效果更好。更甚的是,极限随机树不需要计算最佳阈值,因此它训练起来比随机森林快得多。但是,在做预测的时候,相比随机森林它不快也不慢。
如果你的AdaBoost集成队训练数据拟合不足,你应该调整哪些超参数?怎么调整?
- 提升估算器的数量
- 降低基础估算器的正则化超参数
- 略微提升学习率
如果你的梯度提升集成对训练集过度拟合,你应该提升还是降低学习率?
降低学习率,也可以通过早停法来寻找合适的预测器数量(可能是因为预测器太多)。
我的CSDN:https://blog.csdn.net/qq_21579045
我的博客园:https://www.cnblogs.com/lyjun/
我的Github:https://github.com/TinyHandsome
纸上得来终觉浅,绝知此事要躬行~
欢迎大家过来OB~
by 李英俊小朋友