机器学习之集成学习和随机森林
一、集成学习
集成学习就是合并多个分类器的预测。一般会在一个项目快结束的时候使用集成算法,一旦建立了一些好的分类器,就可以使用集成把它们合并成一个更好的分类器。
著名的集成方法:投票分类、bogging、pasting、boosting、stacking、和一些其它算法。
1.1 投票分类(少数服从多数)
令人惊奇的是这种投票分类器得出的结果经常会比集成中最好的一个分类器结果更好。
事实上,即使每一个分类器都是一个弱学习器(意味着它们也就比瞎猜好点),集成后仍然是一个强学习器(高准确率),只要有足够数量的弱学习者,他们就足够多样化。
如果每一个分类器都在同一个数据集上训练,会导致犯同一种类型的错误。相比较而言,每个分类器在不同的数据集上训练,集成后的结果会更好。
下面使用moons数据集,训练三个分类器,使用集成算法。
from sklearn.datasets import make_moons from sklearn.model_selection import train_test_split moons = make_moons(noise=0.3, random_state=0) X, y = moons X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4, random_state=42) # print(X_train[10], y_train[0])
[-0.65805008 -0.12944211] 0
from sklearn.ensemble import RandomForestClassifier from sklearn.ensemble import VotingClassifier #软投票/多数规则分类器 from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC log_clf = LogisticRegression() rnd_clf = RandomForestClassifier() svm_clf = SVC() voting_clf = VotingClassifier(estimators=[("lr", log_clf), ("rf", rnd_clf), ("svc", svm_clf)], voting="hard") #voting:"soft"/"hard" #硬投票,默认"hrad"。"hard",使用预测的类标签进行多数规则投票。 #软投票,"soft",基于预测概率之和的argmax来预测类别标签,这推荐用于经过良好校准的分类器的集合。 # voting_clf.fit(X_train, y_train)
测一下准确率:
from sklearn.metrics import accuracy_score #分类准确度得分 for clf in (log_clf, rnd_clf, svm_clf, voting_clf): clf.fit(X_train, y_train) y_pred = clf.predict(X_test) print(clf.__class__.__name__, accuracy_score(y_test, y_pred))
LogisticRegression 0.875 RandomForestClassifier 0.925 SVC 0.95 VotingClassifier 0.95
1.2 Bagging和Pasting
对每一个分类器都使用相同的训练算法,但是在不同的训练集上去训练它们。有放回采样被称为装袋(Bagging,是 bootstrap aggregating 的缩写)。无放回采样称为粘贴(pasting)
聚合函数通常对分类是统计模式(例如硬投票分类器),对回归是平均
API:对分类是BaggingClassifier
对于回归是`BaggingRegressor
接下来的代码训练了一个 500 个决策树分类器的集成,每一个都是在数据集上有放回采样 100 个训练实例下进行训练(这是 Bagging 的例子,如果你想尝试 Pasting,就设置bootstrap=False)
n_jobs参数告诉 sklearn 用于训练和预测所需要 CPU 核的数量。(-1 代表着 sklearn 会使用所有空闲核)
总体而言,Bagging 通常会导致更好的模型
from sklearn.ensemble import BaggingClassifier from sklearn.tree import DecisionTreeClassifier bag_clf = BaggingClassifier(DecisionTreeClassifier(), n_estimators=500, max_samples=50, oob_score=True, bootstrap=True, n_jobs=-1) bag_clf.fit(X_train, y_train)
BaggingClassifier(base_estimator=DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None, max_features=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, presort=False, random_state=None, splitter='best'), bootstrap=True, bootstrap_features=False, max_features=1.0, max_samples=50, n_estimators=500, n_jobs=-1, oob_score=True, random_state=None, verbose=0, warm_start=False)
- 在 sklearn 中,你可以在训练后需要创建一个
BaggingClassifier
来自动评估时设置oob_score=True
来自动评估。而不需要使用交叉验证或者使用单独的验证集
bag_clf.oob_score_
0.8833333333333333
# 比较一下 y_pred = bag_clf.predict(X_test) accuracy_score(y_test, y_pred)
0.95
1.3 随机贴片和随机子空间
BaggingClassifier也支持采样特征。它被两个超参数max_features和bootstrap_features控制。他们的工作方式和max_samples和bootstrap一样,但这是对于特征采样而不是实例采样。因此,每一个分类器都会被在随机的输入特征内进行训练。
当你在处理高维度输入下(例如图片)此方法尤其有效。对训练实例和特征的采样被叫做随机贴片。保留了所有的训练实例(例如bootstrap=False和max_samples=1.0),但是对特征采样(bootstrap_features=True并且/或者max_features小于 1.0)叫做随机子空间。
采样特征导致更多的预测多样性,用高偏差换低方差。
1.4 boosting(提升)
提升(Boosting,最初称为假设增强)指的是可以将几个弱学习者组合成强学习者的集成方法。
对于大多数的提升方法的思想就是按顺序去训练分类器,每一个都要尝试修正前面的分类。
现如今已经有很多的提升方法了,但最著名的就是 Adaboost(适应性提升,是 Adaptive Boosting 的简称) 和 Gradient Boosting(梯度提升)。让我们先从 Adaboost 说起。
1.4.1 Adaboost
使一个新的分类器去修正之前分类结果的方法就是对之前分类结果不对的训练实例多加关注。这导致新的预测因子越来越多地聚焦于这种情况。这是 Adaboost 使用的技术。
举个例子,去构建一个 Adaboost 分类器,第一个基分类器(例如一个决策树)被训练然后在训练集上做预测,在误分类训练实例上的权重就增加了。第二个分类机使用更新过的权重然后再一次训练,权重更新,以此类推
sklearn 通常使用 Adaboost 的多分类版本 SAMME(这就代表了 分段加建模使用多类指数损失函数)。如果只有两类别,那么 SAMME 是与 Adaboost 相同的。如果分类器可以预测类别概率(例如如果它们有predict_proba()),如果 sklearn 可以使用 SAMME 叫做SAMME.R的变量(R 代表“REAL”),这种依赖于类别概率的通常比依赖于分类器的更好。
from sklearn.ensemble import AdaBoostClassifier ada_clf = AdaBoostClassifier(DecisionTreeClassifier(max_depth=1), n_estimators=200,algorithm="SAMME.R", learning_rate=0.5) ada_clf.fit(X_train, y_train)
AdaBoostClassifier(algorithm='SAMME.R', base_estimator=DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=1, max_features=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, presort=False, random_state=None, splitter='best'), learning_rate=0.5, n_estimators=200, random_state=None)
1.4.2 梯度提升(Gradient Boosting)
另一个非常著名的提升算法是梯度提升。与 Adaboost 一样,梯度提升也是通过向集成中逐步增加分类器运行的,每一个分类器都修正之前的分类结果。然而,它并不像 Adaboost 那样每一次迭代都更改实例的权重,这个方法是去使用新的分类器去拟合前面分类器预测的残差 。
(1)以决策树回归详解
from sklearn.tree import DecisionTreeRegressor # 第一个分类器 tree_reg1 = DecisionTreeRegressor(max_depth=2) tree_reg1.fit(X, y) # 在第一个分类器的残差上运行第二个分类器 y2 = y - tree_reg1.predict(X) tree_reg2 = DecisionTreeRegressor(max_depth=2) tree_reg2.fit(X, y2) # 在第二个分类器的残差上运行第三个分类器 y3 = y2 - tree_reg1.predict(X) tree_reg3 = DecisionTreeRegressor(max_depth=2) ree_reg3.fit(X, y3) # 它可以通过集成所有树的预测来在一个新的实例上进行预测。 y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))
(2)使用sklearn自带分类器实现
- 超参数
learning_rate
确立了每个树的贡献。如果你把它设置为一个很小的树,例
如 0.1,在集成中就需要更多的树去拟合训练集,但预测通常会更好。这个正则化技术叫做 shrinkage
from sklearn.ensemble import GradientBoostingRegressor gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0) gbrt.fit(X, y)
(3)更好的梯度提升
为了找到树的最优数量,最简单使用这个技术的方法就是使用staged_predict():它在训练的每个阶段(用一棵树,两棵树等)返回一个迭代器。接下来的代码用 120 个树训练了一个 GBRT 集成,然后在训练的每个阶段验证错误以找到树的最佳数量,最后使用 GBRT 树的最优数量训练另一个集成:
import numpy as np from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error from sklearn.ensemble import GradientBoostingRegressor 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) ## np.argmin表示最小值在数组中所在的位置 gbrt_best = GradientBoostingRegressor(max_depth=2,n_estimators=bst_n_estimators) gbrt_best.fit(X_train, y_train)
GradientBoostingRegressor(alpha=0.9, criterion='friedman_mse', init=None, learning_rate=0.1, loss='ls', max_depth=2, max_features=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=119, n_iter_no_change=None, presort='auto', random_state=None, subsample=1.0, tol=0.0001, validation_fraction=0.1, verbose=0, warm_start=False)
(4)第二种更好的梯度提升
你也可以早早的停止训练来实现早停(与先在一大堆树中训练,然后再回头去找最优数目相反)。你可以通过设置warm_start=True来实现 ,这使得当fit()方法被调用时 sklearn 保留现有树,并允许增量训练。接下来的代码在当一行中的五次迭代验证错误没有改善时会停止训练:
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:
1.5 Stacking
另外一个集成方法叫做 Stacking(stacked generalization 的缩写)。
这个算法基于一个简单的想法:不使用琐碎的函数(如硬投票)来聚合集合中所有分类器的预测,而是自己训练一个模型来执行这个聚合。
sklearn 并不直接支持 stacking ,但是你自己组建是很容易的(看接下来的练习)。或者你也可以使用开源的项目例如 brew (网址为 <https: brew="" github.com="" viisar="">)</https:>
二、 随机森林
2.1 实现随机森林
随机森林是决策树的一种集成,**通常是通过 bagging 方法(有时是 pasting 方法)**进行训练,通常用max_samples设置为训练集的大小与建立一个BaggingClassifier然后把它放入 DecisionTreeClassifier 相反,你可以使用更方便的也是对决策树优化够的RandomForestClassifier(对于回归是RandomForestRegressor)。接下来的代码训练了带有 50个树(每个被限制为 16 叶子结点)的决策森林,使用所有空闲的 CPU 核:
from sklearn.ensemble import RandomForestClassifier rnd_clf = RandomForestClassifier(n_estimators=50, max_leaf_nodes=16, n_jobs=-1) rnd_clf.fit(X_train, y_train) y_pred_rf = rnd_clf.predict(X_test)
2.2 极端随机树
当你在随机森林上生长树时,在每个结点分裂时只考虑随机特征集上的特征(正如之前讨论过的一样)。相比于找到更好的特征我们可以通过使用对特征使用随机阈值使树更加随机(像规则决策树一样)。
这种极端随机的树被简称为 Extremely Randomized Trees(极端随机树),或者更简单的称为 Extra-Tree。再一次用高偏差换低方差。它还使得 Extra-Tree 比规则的随机森林更快地训练,因为在每个节点上找到每个特征的最佳阈值是生长树最耗时的任务之一。
你可以使用 sklearn 的ExtraTreesClassifier来创建一个 Extra-Tree 分类器。他的 API 跟RandomForestClassifier是相同的,相似的, ExtraTreesRegressor 跟RandomForestRegressor也是相同的 API。
我们很难去分辨ExtraTreesClassifier和RandomForestClassifier到底哪个更好。通常情况下是通过交叉验证来比较它们(使用网格搜索调整超参数)。
2.3 特征重要度
如果你观察一个单一决策树,重要的特征会出现在更靠近根部的位置,而不重要的特征会经常出现在靠近叶子的位置。因此我们可以通过计算一个特征在森林的全部树中出现的平均深度来预测特征的重要性。sklearn 在训练后会自动计算每个特征的重要度。你可以通过feature_importances_变量来查看结果。下面以鸢尾花数据为例,得出最重要的特征是花瓣长度(44%)和宽度(42%)。而萼片长度和宽度相对比较是不重要的(分别为 11% 和 2%)
from sklearn.datasets import load_iris iris = load_iris() rnd_clf = RandomForestClassifier(n_estimators=500, n_jobs=-1) rnd_clf.fit(iris["data"], iris["target"]) for name, score in zip(iris["feature_names"], rnd_clf.feature_importances_): print(name, score)
sepal length (cm) 0.10318363296580253 sepal width (cm) 0.024861953583854814 petal length (cm) 0.43069959942052854 petal width (cm) 0.4412548140298142