机器学习实战(七):Ensemble Learning and Random Forests

集成学习:聚合一组预测器的预测,通常预测结果比最好的单个预测其要好,聚合的预测器叫做集成。
随机森林:训练一组决策树分类器,每一棵树都基于训练集不同的随机子集训练,聚合所有树的预测得票最高的类别作为预测类别。

本章将探讨几种最流行的集成方法:bagging、boosting、stacking,也将探索随机森林。

Voting Classifiers

如图所示,有一些训练好的分类器,每个准确度80%左右,要创造更好的分类器,最简单办法就是聚合每个分类器预测,将得票最高的结果作为预测类别:硬投票分类器
在这里插入图片描述
此情况下,投票分类器的准确度比集成中最好的分类器还要高:即使每个分类器都是弱学习器,但通过集成依然可以实现强学习器(数量足够大)。前提:所有分类器都是完全独立的,彼此之间错误毫不相关(显然不可能,在相同训练集上训练导致继承准确率有降低)
如何提高集成准确度:预测器尽可能独立,获得多种分类器(使用不同的算法训练)

如下使用Scikit-Learn创造三分类投票决策器:
引数据:

#Voting Classifiers引数据

from sklearn.model_selection import train_test_split
from sklearn.datasets import make_moons

X, y = make_moons(n_samples=500, noise=0.30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

训练:


from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

log_clf = LogisticRegression(solver="liblinear", random_state=42)
rnd_clf = RandomForestClassifier(n_estimators=10, random_state=42)
svm_clf = SVC(gamma="auto", random_state=42)

voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='hard')
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))

软投票器:若所有分类器都能估算类别概率(有predict_proba()方法),可以将概率在所有分类器平均,让Scikit-Learn给出平均改了最高类别预测。比硬投票效果更优(赋予权值)——voting = “soft”。SVC类需要将probability设为True(交叉验证估算概率,速度变慢)。

Bagging and Pasting

获得不同种类分类器方法之一是用不同的训练算法,还有一种是若预测器使用预测算法相同,则在不同的训练集随机子集上训练。采样样本放回:bagging;不放回:pasting。

集成可以简单聚合所有预测器的预测,对新实例预测。聚合函数用统计法分类,平均法回归。与直接在原始训练集上训练相比,集成偏差相近,方差更低。

bagging和pasting非常利于拓展。

Bagging and Pasting in Scikit-Learn

Scikit-Learn可以利用 BaggingClassifier去bagging 和 pasting(BaggingRegressor用于回归)。下列代码训练了一个500个决策树的集成,每次采样100个实例训练再放回(bootstrap = True:bagging False:pasting):

from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

bag_clf = BaggingClassifier(
    DecisionTreeClassifier(random_state=42), n_estimators=500,
    max_samples=100, bootstrap=True, n_jobs=-1, random_state=42)
#bootstrap=True:begging,False:pasting;n_jobs=-1 :都能用核
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)

准确度对比从全部训练集训练好很多:

from sklearn.metrics import accuracy_score
print(accuracy_score(y_test, y_pred))#0.904

tree_clf = DecisionTreeClassifier(random_state=42)
tree_clf.fit(X_train, y_train)
y_pred_tree = tree_clf.predict(X_test)
print(accuracy_score(y_test, y_pred_tree))#0.856

在这里插入图片描述
画图对比(代码见jupyter)决策边界,可见bagging集成要比单独决策泛化好点(偏差相近,方差更小——错误数量差不多,集成决策边界更规则)

ps:若基础分类器能够估算类别概率(支持predict_proba()),则BaggingClassifier自动执行软投票法。

bagging相比于pasting偏差略高,预测器关联度更低,集成偏差降低,也更受欢迎(CPU和时间充足也可以利用交叉验证对比bagging和pasting)

Out-of-Bag Evaluation(包外评估)

bagging这个方法可也能造成有的实例被抽到,有的没被抽到,平均63%被抽到采样,37%就被称为包外(obb)实例。

拿正好可以用这些实例进行评估,不需要在单独的验证集或交叉验证。每个预测器在其包外实例评估结果上平均,就可以得到对集成的评估。

Scikit-Learn在创造BaggingClassifier时,设置obb_score = True可以在训练结束后自动包外评估:

beg_clf = BaggingClassifier(
    DecisionTreeClassifier(),n_estimators=500,
    bootstrap=True,n_jobs=-1,oob_score=True
)
beg_clf.fit(X_train,y_train)
beg_clf.oob_score_

通过oob_score_得到的评估分数与测试集准确度非常接近:

from sklearn.metrics import accuracy_score
y_pred =beg_clf.predict(X_test)
accuracy_score(y_test,y_pred)

每个训练实例的包外决策函数可以通过oob_decision_function_获得,返回的是每个类别概率。

Random Patches and Random Subspaces(随即补丁和随机子空间)

BaggingClassifier支持对特征抽样,通过max_features和bootstrap_features控制。每个预测器将用输入特征的随机子集进行训练。

对于处理高位输入(图像视频)很管用

  • Random Patches方法:对训练实例和特征都进行抽样
  • Random Subspaces:保留所有训练实例(bootstrap = False 且max_samples =1.0)但对特征抽样(bootstrap = R=True 且max_samples <1.0)

特征抽样以略高偏差换取更低方差。

Random Forests

随机森林就是决策树的集成,通常用bagging训练(sometimes pasting),max_samples控制训练集大小。
RandomForestClassifier类(RandomForestRegressor同理)可以一体化训练决策(比另一种方法(建一个BaggingClassifier训练再把结果传到DecisionTreeClassifier)优化决策树更多,还简单)

下方例子用所有能用cpu内核,训练500棵树随机森林分类器(每颗最多16节点)
法一:

bag_clf = BaggingClassifier(
    DecisionTreeClassifier(splitter="random", max_leaf_nodes=16, random_state=42),
    n_estimators=500, max_samples=1.0, bootstrap=True, n_jobs=-1, random_state=42)

bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)

法二(直接RandomForestClassifier):

from sklearn.ensemble import RandomForestClassifier

rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, n_jobs=-1, random_state=42)
rnd_clf.fit(X_train, y_train)

y_pred_rf = rnd_clf.predict(X_test)

两者相同比例高达97.6%:np.sum(y_pred == y_pred_rf) / len(y_pred)

RandomForestClassifier具有DecisionTreeClassifier所有超参数(控制树的成长),BaggingClassifier所有超参数(控制集成本身)。

随机森林在树的生长随机性更高:分裂节点在一个随机生成的特征子集搜索最好的特征。性能更优,多样性更大(用更高偏差换更低方差)

Extra-Trees(极端随机树)

随机森林在分裂节点时仅考虑一个随机子集包含特征,对每个特征使用随机阈值(而不是搜索最佳阈值),使得决策树生长更随机。

Extra-Trees由极端随机决策树集成,同样以更高偏差换取更低方差,训练非常快(常规随机森林搜索每个节点每个特征非常耗时)

Scikit-Learn 的ExtraTressClassifer可以创造一个极端随机树分类器,API同RandomForestClassifier(两者~Regressor同理)

区分ExtraTressClassifer和RandomForestClassifier的效果好坏的唯一方法:两者都尝试,使用交叉验证(网格搜索调参)比较

Feature Importance

重要特征更靠近根节点位置,不重要更可能靠近叶节点(或者不出现)。所以一个特征在森林中所有树的平均深度可以估算一个特征重要程度。Scikit-Learn(feature_importance_)可以自动算出每个特征重要性。

以下代码训练RandomForestClassifier并输出每个特征重要性:

#Feature Importance
from sklearn.datasets import load_iris
iris = load_iris()
rnd_clf = RandomForestClassifier(n_estimators=500, n_jobs=-1, random_state=42)
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.11249225099876375
sepal width (cm) 0.02311928828251033
petal length (cm) 0.4410304643639577
petal width (cm) 0.4233579963547682

花瓣长宽比花萼长宽重要得多。

用MNIST训练一个随机森林分类器,绘制每个像素重要性:

#引入MNIST
try:
    from sklearn.datasets import fetch_openml
    mnist = fetch_openml('mnist_784', version=1)
    mnist.target = mnist.target.astype(np.int64)
except ImportError:
    from sklearn.datasets import fetch_mldata
    mnist = fetch_mldata('MNIST original')

训练RandomForestClassifier:

rnd_clf = RandomForestClassifier(n_estimators=10, random_state=42)
rnd_clf.fit(mnist["data"], mnist["target"])

定义画图函数:

def plot_digit(data):
    image = data.reshape(28, 28)
    plt.imshow(image, cmap = mpl.cm.hot,
               interpolation="nearest")
    plt.axis("off")
plot_digit(rnd_clf.feature_importances_)
cbar = plt.colorbar(ticks=[rnd_clf.feature_importances_.min(), rnd_clf.feature_importances_.max()])
cbar.ax.set_yticklabels(['Not important', 'Very important'])
plt.show()

在这里插入图片描述
随机森林是一个了解特征重要度非常便利的方法,特别是执行特征选择时。

Boosting(提升法)

Boosting:将几个弱学习器合成一个强学习器的任意集成方法。大多数Boosting总思路是循环训练预测器,每次对前序更正。最流行的Boosting:AdaBoost(自适应提升法)和GB(梯度提升)。

AdaBoost(自适应提升法)

新预测器对其前序纠正方法之一:更多关注前序拟合不足的训练实例,使新预测器不断专注难缠问题。

构建一个AdaBoost分类器:

  1. 训练一个基础分类器(eg:决策树),对训练集训练
  2. 对错误分类实例增加权重
  3. 用新的权重对第二个分类器训练
  4. 再次训练,更新权重,不断循环

在这里插入图片描述

如下图所示,moons dataset上5个连续预测器的决策边界(预测器用RBF核函数的高度正则化SVM分类器),随着实验进行,错误示例权重提升。AdaBoost与GB区别在于,不再是调整单个预测其参数使成本函数最小化,而是不断加入预测器中,使模型优化。

一旦全部预测器训练完成,集体预测与bagging,passting方法相同(除非预测器权重和不同)

缺陷:不能并行,在拓展方面不如bagging,passting。

m = len(X_train)

plt.figure(figsize=(11, 4))
for subplot, learning_rate in ((121, 1), (122, 0.5)):
    sample_weights = np.ones(m)
    plt.subplot(subplot)
    for i in range(5):
        svm_clf = SVC(kernel="rbf", C=0.05, gamma="auto", random_state=42)
        svm_clf.fit(X_train, y_train, sample_weight=sample_weights)
        y_pred = svm_clf.predict(X_train)
        sample_weights[y_pred != y_train] *= (1 + learning_rate)
        plot_decision_boundary(svm_clf, X, y, alpha=0.2)
        plt.title("learning_rate = {}".format(learning_rate), fontsize=16)
    if subplot == 121:
        plt.text(-0.7, -0.65, "1", fontsize=14)
        plt.text(-0.6, -0.10, "2", fontsize=14)
        plt.text(-0.5,  0.10, "3", fontsize=14)
        plt.text(-0.4,  0.55, "4", fontsize=14)
        plt.text(-0.3,  0.90, "5", fontsize=14)

在这里插入图片描述

聚焦AdaBoosting算法
每个实例权重wi设置为1/m,第一个预测器训练后,计算其加权误差率r1:
y_hat _ i_j:第j个预测器对第i个实例做出的预测:在这里插入图片描述

预测器权重aj计算在这里插入图片描述
η为学习率(默认1),预测器准确率越高,权重越高:随机0,错多负。

权重更新,提升错误分类比重:在这里插入图片描述
然后所有权重归一化(/sum(wi))

最后用更新后的权重训练新的预测器,重复到达到所需数量预测器或得到完美预测器。

AdaBoost计算所有预测器的结果(aj权重加权):结果为大多数预测器所给预测类别。
在这里插入图片描述

Scikit-Learn使用的使AdaBoost的SAMME多分类版本,只有两个类别时=AdaBoost,若预测器可以估算概率(predict_proba()),Scikit-Learn使用的是SAMME变体SAMME.R(依赖概率预测,效果更好)

下列代码使用Scikit-Learn的AdaBoostClassifier训练一个AdaBoost分类器,基于200个单层预测树(max_depth=1的决策树,AdaBoostClassifier默认使用基础估算器)

#AdaBoost
from sklearn.ensemble import AdaBoostClassifier

ada_clf = AdaBoostClassifier(
    DecisionTreeClassifier(max_depth=1), n_estimators=200,
    algorithm="SAMME.R", learning_rate=0.5, random_state=42)
ada_clf.fit(X_train, y_train)

plot_decision_boundary(ada_clf, X, y)

在这里插入图片描述
过拟合则减少估算器数量,提升基础估算器正则化程度。

Gradient Boosting(梯度提升)

另一个非常受欢迎的Boosting,也是逐步在集成中添加预测器,每个对前序做改正,但它不迭代实例权重,而是针对前一个预测器残差拟合。

将决策树作为基础预测器完成回归,叫Gradient Tree Boosting, or Gradient Boosted Regression Trees (GBRT)。
首先,在训练集(带噪声的二次训练集)上拟合一个DecisionTreeRegressor:

np.random.seed(42)
X = np.random.rand(100, 1) - 0.5#100个-0.5--0.5
y = 3*X[:, 0]**2 + 0.05 * np.random.randn(100)#3*x^2+0.05误差


#拟合
from sklearn.tree import DecisionTreeRegressor

tree_reg1 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg1.fit(X, y)

针对第一个残差,训练第二个DecisionTreeRegressor:

y2 = y - tree_reg1.predict(X)
tree_reg2 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg2.fit(X, y2)

go on:

y3 = y2 - tree_reg2.predict(X)
tree_reg3 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg3.fit(X, y3)

集成将三个树的预测相加来预测新实例:

X_new = np.array([[0.8]])
y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))
y_pred 

如图所示(代码见jupyter),右侧树为前面树的预测之和,表现优化。
在这里插入图片描述
训练GBRT可用Scikit-Learn’s GradientBoostingRegressor,与RandomForestRegressor一样有控制决策树生长和控制集成的超参数:
上述代码实现

from sklearn.ensemble import GradientBoostingRegressor

gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0, random_state=42)
gbrt.fit(X, y)

learning_rate对每棵树贡献缩放,设为低值,需要更多的树来拟合训练集,但是预测泛化效果更好(收缩技术,用来正则化)。

gbrt_slow = GradientBoostingRegressor(max_depth=2, n_estimators=200, learning_rate=0.1, random_state=42)
gbrt_slow.fit(X, y)

将上述两个不同learn_rate画出:

plt.figure(figsize=(11,4))

plt.subplot(121)
plot_predictions([gbrt], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="Ensemble predictions")
plt.title("learning_rate={}, n_estimators={}".format(gbrt.learning_rate, gbrt.n_estimators), fontsize=14)

plt.subplot(122)
plot_predictions([gbrt_slow], X, y, axes=[-0.5, 0.5, -0.1, 0.8])
plt.title("learning_rate={}, n_estimators={}".format(gbrt_slow.learning_rate, gbrt_slow.n_estimators), fontsize=14)

在这里插入图片描述
如上图所示,左侧拟合不足(训练集树太少),右侧过度拟合(训练集树太多)

寻找树的最佳数量:早期停止法或staged_predict()。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, random_state=49)

gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=120, random_state=42)
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) + 1

gbrt_best = GradientBoostingRegressor(max_depth=2,n_estimators=bst_n_estimators, random_state=42)
gbrt_best.fit(X_train, y_train)

得到最优树的数量,训练画出:

min_error = np.min(errors)
plt.figure(figsize=(11, 4))

plt.subplot(121)
plt.plot(errors, "b.-")
plt.plot([bst_n_estimators, bst_n_estimators], [0, min_error], "k--")
plt.plot([0, 120], [min_error, min_error], "k--")
plt.plot(bst_n_estimators, min_error, "ko")
plt.text(bst_n_estimators, min_error*1.2, "Minimum", ha="center", fontsize=14)
plt.axis([0, 120, 0, 0.01])
plt.xlabel("Number of trees")
plt.title("Validation error", fontsize=14)

plt.subplot(122)
plot_predictions([gbrt_best], X, y, axes=[-0.5, 0.5, -0.1, 0.8])
plt.title("Best model (%d trees)" % bst_n_estimators, fontsize=14)

在这里插入图片描述
早期停止法,设置warm_start=True,fit()被调用时,Scikit-Learn会保留现有的树继续增量训练
下列代码在5次连续迭代未改善时直接停止训练:

gbrt = GradientBoostingRegressor(max_depth=2, warm_start=True, random_state=42)

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  # early stopping

GradientBoostingRegressor还支持设置每棵树用于训练的比例:subsample 。subsample=0.25,每棵树用25%随机选择实例训练(用更高的偏差换更低的方差,加快训练过程:Stochastic Gradient Boosting随机梯度提升)

GB同样可使用其他成本函数如超参数loss控制。

Stacking(堆叠法)

Stacking同样可用于集成,又叫层叠泛化法:用一个模型代替函数来聚合所有预测器的预测。在这里插入图片描述
如上图所示,回归集成预测。
训练混合器的常用方法是使用留存集。将训练集分为两个子集,第一个训练第一层预测器,用第一层训练出的预测器在第二个留存子集上进行预测,对于留存集中每个实例都有了三个预测值,将这些预测值作为输入特征,创建一个新的训练集,并保留目标值。在新的训练集上训练混合器,让学习第一层的预测来预测目标值。

在这里插入图片描述
在这里插入图片描述
训练除不同的混合器后(用线性回归,用随机森林回归)得到一个混合器层,如下图所示,分为三个子集:
在这里插入图片描述
但Scikit-Learn不支持Stacking,但可以自己堆或者开源(详见第9题)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值